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

package remote

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

	"github.com/go-kit/log"
	"github.com/prometheus/client_golang/prometheus"
	"github.com/prometheus/client_golang/prometheus/promauto"

	"github.com/prometheus/prometheus/config"
	"github.com/prometheus/prometheus/pkg/exemplar"
	"github.com/prometheus/prometheus/pkg/histogram"
	"github.com/prometheus/prometheus/pkg/labels"
	"github.com/prometheus/prometheus/storage"
	"github.com/prometheus/prometheus/tsdb/wal"
)

var (
	samplesIn = promauto.NewCounter(prometheus.CounterOpts{
		Namespace: namespace,
		Subsystem: subsystem,
		Name:      "samples_in_total",
		Help:      "Samples in to remote storage, compare to samples out for queue managers.",
	})
	exemplarsIn = promauto.NewCounter(prometheus.CounterOpts{
		Namespace: namespace,
		Subsystem: subsystem,
		Name:      "exemplars_in_total",
		Help:      "Exemplars in to remote storage, compare to exemplars out for queue managers.",
	})
)

// WriteStorage represents all the remote write storage.
type WriteStorage struct {
	logger log.Logger
	reg    prometheus.Registerer
	mtx    sync.Mutex

	watcherMetrics    *wal.WatcherMetrics
	liveReaderMetrics *wal.LiveReaderMetrics
	externalLabels    labels.Labels
	walDir            string
	queues            map[string]*QueueManager
	samplesIn         *ewmaRate
	flushDeadline     time.Duration
	interner          *pool
	scraper           ReadyScrapeManager

	// For timestampTracker.
	highestTimestamp *maxTimestamp
}

// NewWriteStorage creates and runs a WriteStorage.
func NewWriteStorage(logger log.Logger, reg prometheus.Registerer, walDir string, flushDeadline time.Duration, sm ReadyScrapeManager) *WriteStorage {
	if logger == nil {
		logger = log.NewNopLogger()
	}
	rws := &WriteStorage{
		queues:            make(map[string]*QueueManager),
		watcherMetrics:    wal.NewWatcherMetrics(reg),
		liveReaderMetrics: wal.NewLiveReaderMetrics(reg),
		logger:            logger,
		reg:               reg,
		flushDeadline:     flushDeadline,
		samplesIn:         newEWMARate(ewmaWeight, shardUpdateDuration),
		walDir:            walDir,
		interner:          newPool(),
		scraper:           sm,
		highestTimestamp: &maxTimestamp{
			Gauge: prometheus.NewGauge(prometheus.GaugeOpts{
				Namespace: namespace,
				Subsystem: subsystem,
				Name:      "highest_timestamp_in_seconds",
				Help:      "Highest timestamp that has come into the remote storage via the Appender interface, in seconds since epoch.",
			}),
		},
	}
	if reg != nil {
		reg.MustRegister(rws.highestTimestamp)
	}
	go rws.run()
	return rws
}

func (rws *WriteStorage) run() {
	ticker := time.NewTicker(shardUpdateDuration)
	defer ticker.Stop()
	for range ticker.C {
		rws.samplesIn.tick()
	}
}

// ApplyConfig updates the state as the new config requires.
// Only stop & create queues which have changes.
func (rws *WriteStorage) ApplyConfig(conf *config.Config) error {
	rws.mtx.Lock()
	defer rws.mtx.Unlock()

	// Remote write queues only need to change if the remote write config or
	// external labels change.
	externalLabelUnchanged := labels.Equal(conf.GlobalConfig.ExternalLabels, rws.externalLabels)
	rws.externalLabels = conf.GlobalConfig.ExternalLabels

	newQueues := make(map[string]*QueueManager)
	newHashes := []string{}
	for _, rwConf := range conf.RemoteWriteConfigs {
		hash, err := toHash(rwConf)
		if err != nil {
			return err
		}

		// Don't allow duplicate remote write configs.
		if _, ok := newQueues[hash]; ok {
			return fmt.Errorf("duplicate remote write configs are not allowed, found duplicate for URL: %s", rwConf.URL)
		}

		// Set the queue name to the config hash if the user has not set
		// a name in their remote write config so we can still differentiate
		// between queues that have the same remote write endpoint.
		name := hash[:6]
		if rwConf.Name != "" {
			name = rwConf.Name
		}

		c, err := NewWriteClient(name, &ClientConfig{
			URL:              rwConf.URL,
			Timeout:          rwConf.RemoteTimeout,
			HTTPClientConfig: rwConf.HTTPClientConfig,
			SigV4Config:      rwConf.SigV4Config,
			Headers:          rwConf.Headers,
			RetryOnRateLimit: rwConf.QueueConfig.RetryOnRateLimit,
		})
		if err != nil {
			return err
		}

		queue, ok := rws.queues[hash]
		if externalLabelUnchanged && ok {
			// Update the client in case any secret configuration has changed.
			queue.SetClient(c)
			newQueues[hash] = queue
			delete(rws.queues, hash)
			continue
		}

		endpoint := rwConf.URL.String()
		newQueues[hash] = NewQueueManager(
			newQueueManagerMetrics(rws.reg, name, endpoint),
			rws.watcherMetrics,
			rws.liveReaderMetrics,
			rws.logger,
			rws.walDir,
			rws.samplesIn,
			rwConf.QueueConfig,
			rwConf.MetadataConfig,
			conf.GlobalConfig.ExternalLabels,
			rwConf.WriteRelabelConfigs,
			c,
			rws.flushDeadline,
			rws.interner,
			rws.highestTimestamp,
			rws.scraper,
			rwConf.SendExemplars,
		)
		// Keep track of which queues are new so we know which to start.
		newHashes = append(newHashes, hash)
	}

	// Anything remaining in rws.queues is a queue who's config has
	// changed or was removed from the overall remote write config.
	for _, q := range rws.queues {
		q.Stop()
	}

	for _, hash := range newHashes {
		newQueues[hash].Start()
	}

	rws.queues = newQueues

	return nil
}

// Appender implements storage.Storage.
func (rws *WriteStorage) Appender(_ context.Context) storage.Appender {
	return &timestampTracker{
		writeStorage:         rws,
		highestRecvTimestamp: rws.highestTimestamp,
	}
}

// Close closes the WriteStorage.
func (rws *WriteStorage) Close() error {
	rws.mtx.Lock()
	defer rws.mtx.Unlock()
	for _, q := range rws.queues {
		q.Stop()
	}
	return nil
}

type timestampTracker struct {
	writeStorage         *WriteStorage
	samples              int64
	exemplars            int64
	highestTimestamp     int64
	highestRecvTimestamp *maxTimestamp
}

// Append implements storage.Appender.
func (t *timestampTracker) Append(_ uint64, _ labels.Labels, ts int64, _ float64) (uint64, error) {
	t.samples++
	if ts > t.highestTimestamp {
		t.highestTimestamp = ts
	}
	return 0, nil
}

func (t *timestampTracker) AppendExemplar(_ uint64, _ labels.Labels, _ exemplar.Exemplar) (uint64, error) {
	t.exemplars++
	return 0, nil
}

func (t *timestampTracker) AppendHistogram(_ uint64, _ labels.Labels, _ int64, _ histogram.SparseHistogram) (uint64, error) {
	return 0, errors.New("not implemented")
}

// Commit implements storage.Appender.
func (t *timestampTracker) Commit() error {
	t.writeStorage.samplesIn.incr(t.samples + t.exemplars)

	samplesIn.Add(float64(t.samples))
	exemplarsIn.Add(float64(t.exemplars))
	t.highestRecvTimestamp.Set(float64(t.highestTimestamp / 1000))
	return nil
}

// Rollback implements storage.Appender.
func (*timestampTracker) Rollback() error {
	return nil
}
<commit_msg>Fix scraping of sparse histograms (#9031)<commit_after>// Copyright 2017 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package remote

import (
	"context"
	"fmt"
	"sync"
	"time"

	"github.com/go-kit/log"
	"github.com/prometheus/client_golang/prometheus"
	"github.com/prometheus/client_golang/prometheus/promauto"

	"github.com/prometheus/prometheus/config"
	"github.com/prometheus/prometheus/pkg/exemplar"
	"github.com/prometheus/prometheus/pkg/histogram"
	"github.com/prometheus/prometheus/pkg/labels"
	"github.com/prometheus/prometheus/storage"
	"github.com/prometheus/prometheus/tsdb/wal"
)

var (
	samplesIn = promauto.NewCounter(prometheus.CounterOpts{
		Namespace: namespace,
		Subsystem: subsystem,
		Name:      "samples_in_total",
		Help:      "Samples in to remote storage, compare to samples out for queue managers.",
	})
	exemplarsIn = promauto.NewCounter(prometheus.CounterOpts{
		Namespace: namespace,
		Subsystem: subsystem,
		Name:      "exemplars_in_total",
		Help:      "Exemplars in to remote storage, compare to exemplars out for queue managers.",
	})
)

// WriteStorage represents all the remote write storage.
type WriteStorage struct {
	logger log.Logger
	reg    prometheus.Registerer
	mtx    sync.Mutex

	watcherMetrics    *wal.WatcherMetrics
	liveReaderMetrics *wal.LiveReaderMetrics
	externalLabels    labels.Labels
	walDir            string
	queues            map[string]*QueueManager
	samplesIn         *ewmaRate
	flushDeadline     time.Duration
	interner          *pool
	scraper           ReadyScrapeManager

	// For timestampTracker.
	highestTimestamp *maxTimestamp
}

// NewWriteStorage creates and runs a WriteStorage.
func NewWriteStorage(logger log.Logger, reg prometheus.Registerer, walDir string, flushDeadline time.Duration, sm ReadyScrapeManager) *WriteStorage {
	if logger == nil {
		logger = log.NewNopLogger()
	}
	rws := &WriteStorage{
		queues:            make(map[string]*QueueManager),
		watcherMetrics:    wal.NewWatcherMetrics(reg),
		liveReaderMetrics: wal.NewLiveReaderMetrics(reg),
		logger:            logger,
		reg:               reg,
		flushDeadline:     flushDeadline,
		samplesIn:         newEWMARate(ewmaWeight, shardUpdateDuration),
		walDir:            walDir,
		interner:          newPool(),
		scraper:           sm,
		highestTimestamp: &maxTimestamp{
			Gauge: prometheus.NewGauge(prometheus.GaugeOpts{
				Namespace: namespace,
				Subsystem: subsystem,
				Name:      "highest_timestamp_in_seconds",
				Help:      "Highest timestamp that has come into the remote storage via the Appender interface, in seconds since epoch.",
			}),
		},
	}
	if reg != nil {
		reg.MustRegister(rws.highestTimestamp)
	}
	go rws.run()
	return rws
}

func (rws *WriteStorage) run() {
	ticker := time.NewTicker(shardUpdateDuration)
	defer ticker.Stop()
	for range ticker.C {
		rws.samplesIn.tick()
	}
}

// ApplyConfig updates the state as the new config requires.
// Only stop & create queues which have changes.
func (rws *WriteStorage) ApplyConfig(conf *config.Config) error {
	rws.mtx.Lock()
	defer rws.mtx.Unlock()

	// Remote write queues only need to change if the remote write config or
	// external labels change.
	externalLabelUnchanged := labels.Equal(conf.GlobalConfig.ExternalLabels, rws.externalLabels)
	rws.externalLabels = conf.GlobalConfig.ExternalLabels

	newQueues := make(map[string]*QueueManager)
	newHashes := []string{}
	for _, rwConf := range conf.RemoteWriteConfigs {
		hash, err := toHash(rwConf)
		if err != nil {
			return err
		}

		// Don't allow duplicate remote write configs.
		if _, ok := newQueues[hash]; ok {
			return fmt.Errorf("duplicate remote write configs are not allowed, found duplicate for URL: %s", rwConf.URL)
		}

		// Set the queue name to the config hash if the user has not set
		// a name in their remote write config so we can still differentiate
		// between queues that have the same remote write endpoint.
		name := hash[:6]
		if rwConf.Name != "" {
			name = rwConf.Name
		}

		c, err := NewWriteClient(name, &ClientConfig{
			URL:              rwConf.URL,
			Timeout:          rwConf.RemoteTimeout,
			HTTPClientConfig: rwConf.HTTPClientConfig,
			SigV4Config:      rwConf.SigV4Config,
			Headers:          rwConf.Headers,
			RetryOnRateLimit: rwConf.QueueConfig.RetryOnRateLimit,
		})
		if err != nil {
			return err
		}

		queue, ok := rws.queues[hash]
		if externalLabelUnchanged && ok {
			// Update the client in case any secret configuration has changed.
			queue.SetClient(c)
			newQueues[hash] = queue
			delete(rws.queues, hash)
			continue
		}

		endpoint := rwConf.URL.String()
		newQueues[hash] = NewQueueManager(
			newQueueManagerMetrics(rws.reg, name, endpoint),
			rws.watcherMetrics,
			rws.liveReaderMetrics,
			rws.logger,
			rws.walDir,
			rws.samplesIn,
			rwConf.QueueConfig,
			rwConf.MetadataConfig,
			conf.GlobalConfig.ExternalLabels,
			rwConf.WriteRelabelConfigs,
			c,
			rws.flushDeadline,
			rws.interner,
			rws.highestTimestamp,
			rws.scraper,
			rwConf.SendExemplars,
		)
		// Keep track of which queues are new so we know which to start.
		newHashes = append(newHashes, hash)
	}

	// Anything remaining in rws.queues is a queue who's config has
	// changed or was removed from the overall remote write config.
	for _, q := range rws.queues {
		q.Stop()
	}

	for _, hash := range newHashes {
		newQueues[hash].Start()
	}

	rws.queues = newQueues

	return nil
}

// Appender implements storage.Storage.
func (rws *WriteStorage) Appender(_ context.Context) storage.Appender {
	return &timestampTracker{
		writeStorage:         rws,
		highestRecvTimestamp: rws.highestTimestamp,
	}
}

// Close closes the WriteStorage.
func (rws *WriteStorage) Close() error {
	rws.mtx.Lock()
	defer rws.mtx.Unlock()
	for _, q := range rws.queues {
		q.Stop()
	}
	return nil
}

type timestampTracker struct {
	writeStorage         *WriteStorage
	samples              int64
	exemplars            int64
	histograms           int64
	highestTimestamp     int64
	highestRecvTimestamp *maxTimestamp
}

// Append implements storage.Appender.
func (t *timestampTracker) Append(_ uint64, _ labels.Labels, ts int64, _ float64) (uint64, error) {
	t.samples++
	if ts > t.highestTimestamp {
		t.highestTimestamp = ts
	}
	return 0, nil
}

func (t *timestampTracker) AppendExemplar(_ uint64, _ labels.Labels, _ exemplar.Exemplar) (uint64, error) {
	t.exemplars++
	return 0, nil
}

func (t *timestampTracker) AppendHistogram(_ uint64, _ labels.Labels, ts int64, _ histogram.SparseHistogram) (uint64, error) {
	t.histograms++
	if ts > t.highestTimestamp {
		t.highestTimestamp = ts
	}
	return 0, nil
}

// Commit implements storage.Appender.
func (t *timestampTracker) Commit() error {
	t.writeStorage.samplesIn.incr(t.samples + t.exemplars)

	samplesIn.Add(float64(t.samples))
	exemplarsIn.Add(float64(t.exemplars))
	t.highestRecvTimestamp.Set(float64(t.highestTimestamp / 1000))
	return nil
}

// Rollback implements storage.Appender.
func (*timestampTracker) Rollback() error {
	return nil
}
<|endoftext|>


<commit_before>// Copyright 2014 Unknwon
//
// 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 pongo2 is a middleware that provides pongo2 template engine of Macaron.
package pongo2

import (
	"fmt"
	"net/http"
	"os"
	"path/filepath"
	"strings"
	"sync"
	"time"

	"github.com/Unknwon/macaron"
	"github.com/flosch/pongo2"
)

const (
	ContentType    = "Content-Type"
	ContentLength  = "Content-Length"
	ContentBinary  = "application/octet-stream"
	ContentJSON    = "application/json"
	ContentHTML    = "text/html"
	ContentXHTML   = "application/xhtml+xml"
	ContentXML     = "text/xml"
	defaultCharset = "UTF-8"
)

var (
	renderOpt Options
	tplMap    map[string]*pongo2.Template
	lock      sync.RWMutex // Go map is not safe.
)

func prepareCharset(charset string) string {
	if len(charset) != 0 {
		return "; charset=" + charset
	}

	return "; charset=" + defaultCharset
}

func getExt(s string) string {
	if strings.Index(s, ".") == -1 {
		return ""
	}
	return "." + strings.Join(strings.Split(s, ".")[1:], ".")
}

func compile(options Options) {
	lock.Lock()
	defer lock.Unlock()

	dir := options.Directory
	tplMap = make(map[string]*pongo2.Template)

	filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
		r, err := filepath.Rel(dir, path)
		if err != nil {
			return err
		}

		ext := getExt(r)

		for _, extension := range options.Extensions {
			if ext == extension {
				name := (r[0 : len(r)-len(ext)])
				// Bomb out if parse fails. We don't want any silent server starts.
				t, err := pongo2.FromFile(path)
				if err != nil {
					panic(fmt.Errorf("\"%s\": %v", path, err))
				}
				tplMap[strings.Replace(name, "\\", "/", -1)] = t
				break
			}
		}

		return nil
	})
}

// Options represents a struct for specifying configuration options for the Render middleware.
type Options struct {
	// Directory to load templates. Default is "templates"
	Directory string
	// Extensions to parse template files from. Defaults to [".tmpl", ".html"]
	Extensions []string
	// Funcs is a slice of FuncMaps to apply to the template upon compilation. This is useful for helper functions. Defaults to [].
	// Funcs []template.FuncMap
	// Appends the given charset to the Content-Type header. Default is "UTF-8".
	Charset string
	// Outputs human readable JSON
	IndentJSON bool
	// Outputs human readable XML
	IndentXML bool
	// Prefixes the JSON output with the given bytes.
	PrefixJSON []byte
	// Prefixes the XML output with the given bytes.
	PrefixXML []byte
	// Allows changing of output to XHTML instead of HTML. Default is "text/html"
	HTMLContentType string
}

func prepareOptions(options []Options) Options {
	var opt Options
	if len(options) > 0 {
		opt = options[0]
	}

	// Defaults
	if len(opt.Directory) == 0 {
		opt.Directory = "templates"
	}
	if len(opt.Extensions) == 0 {
		opt.Extensions = []string{".tmpl", ".html"}
	}
	if len(opt.HTMLContentType) == 0 {
		opt.HTMLContentType = ContentHTML
	}

	return opt
}

// Pongoer is a Middleware that maps a macaron.Render service into the Macaron handler chain.
// An single variadic pongo2.Options struct can be optionally provided to configure
// HTML rendering. The default directory for templates is "templates" and the default
// file extension is ".tmpl" and ".html".
//
// If MACARON_ENV is set to "" or "development" then templates will be recompiled on every request. For more performance, set the
// MACARON_ENV environment variable to "production".
func Pongoer(options ...Options) macaron.Handler {
	renderOpt = prepareOptions(options)
	cs := prepareCharset(renderOpt.Charset)
	compile(renderOpt)

	return func(ctx *macaron.Context, rw http.ResponseWriter, req *http.Request) {
		if macaron.Env == macaron.DEV {
			compile(renderOpt)
		}
		r := &render{
			TplRender: &macaron.TplRender{
				ResponseWriter: rw,
				Req:            req,
				Opt: macaron.RenderOptions{
					IndentJSON: renderOpt.IndentJSON,
					IndentXML:  renderOpt.IndentXML,
					PrefixJSON: renderOpt.PrefixJSON,
					PrefixXML:  renderOpt.PrefixXML,
				},
				CompiledCharset: cs,
			},
			ResponseWriter:  rw,
			opt:             renderOpt,
			compiledCharset: cs,
		}
		ctx.Render = r
		ctx.MapTo(r, (*macaron.Render)(nil))
	}
}

type render struct {
	*macaron.TplRender
	http.ResponseWriter
	opt             Options
	compiledCharset string

	startTime time.Time
}

func data2Context(data interface{}) pongo2.Context {
	return pongo2.Context(data.(map[string]interface{}))
}

func (r *render) HTML(status int, name string, data interface{}, _ ...macaron.HTMLOptions) {
	r.startTime = time.Now()

	lock.RLock()
	defer lock.RUnlock()

	t := tplMap[name]
	if t == nil {
		http.Error(r, "pongo2: \""+name+"\" is undefined", http.StatusInternalServerError)
		return
	}

	r.Header().Set(ContentType, r.opt.HTMLContentType+r.compiledCharset)
	r.WriteHeader(status)
	if err := t.ExecuteWriter(data2Context(data), r); err != nil {
		http.Error(r, err.Error(), http.StatusInternalServerError)
		return
	}
}

func (r *render) HTMLString(name string, data interface{}, _ ...macaron.HTMLOptions) (string, error) {
	lock.RLock()
	defer lock.RUnlock()

	t := tplMap[name]
	if t == nil {
		http.Error(r, "pongo2: \""+name+"\" is undefined", http.StatusInternalServerError)
		return "", nil
	}

	out, err := t.Execute(data2Context(data))
	if err != nil {
		return "", err
	}

	return out, nil
}

// SetTemplatePath changes templates path.
func (r *render) SetTemplatePath(newPath string) {
	renderOpt.Directory = newPath
	compile(renderOpt)
}
<commit_msg>FIXME<commit_after>// Copyright 2014 Unknwon
//
// 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 pongo2 is a middleware that provides pongo2 template engine of Macaron.
package pongo2

import (
	"fmt"
	"net/http"
	"os"
	"path/filepath"
	"strings"
	"sync"
	"time"

	"github.com/Unknwon/macaron"
	"github.com/flosch/pongo2"
)

const (
	ContentType    = "Content-Type"
	ContentLength  = "Content-Length"
	ContentBinary  = "application/octet-stream"
	ContentJSON    = "application/json"
	ContentHTML    = "text/html"
	ContentXHTML   = "application/xhtml+xml"
	ContentXML     = "text/xml"
	defaultCharset = "UTF-8"
)

var (
	renderOpt Options
	tplMap    map[string]*pongo2.Template
	lock      sync.RWMutex // Go map is not safe.
)

func prepareCharset(charset string) string {
	if len(charset) != 0 {
		return "; charset=" + charset
	}

	return "; charset=" + defaultCharset
}

func getExt(s string) string {
	if strings.Index(s, ".") == -1 {
		return ""
	}
	return "." + strings.Join(strings.Split(s, ".")[1:], ".")
}

func compile(options Options) {
	lock.Lock()
	defer lock.Unlock()

	dir := options.Directory
	tplMap = make(map[string]*pongo2.Template)

	filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
		r, err := filepath.Rel(dir, path)
		if err != nil {
			return err
		}

		ext := getExt(r)

		for _, extension := range options.Extensions {
			if ext == extension {
				name := (r[0 : len(r)-len(ext)])
				// Bomb out if parse fails. We don't want any silent server starts.
				t, err := pongo2.FromFile(path)
				if err != nil {
					panic(fmt.Errorf("\"%s\": %v", path, err))
				}
				tplMap[strings.Replace(name, "\\", "/", -1)] = t
				break
			}
		}

		return nil
	})
}

// Options represents a struct for specifying configuration options for the Render middleware.
type Options struct {
	// Directory to load templates. Default is "templates"
	Directory string
	// Extensions to parse template files from. Defaults to [".tmpl", ".html"]
	Extensions []string
	// Funcs is a slice of FuncMaps to apply to the template upon compilation. This is useful for helper functions. Defaults to [].
	// Funcs []template.FuncMap
	// Appends the given charset to the Content-Type header. Default is "UTF-8".
	Charset string
	// Outputs human readable JSON
	IndentJSON bool
	// Outputs human readable XML
	IndentXML bool
	// Prefixes the JSON output with the given bytes.
	PrefixJSON []byte
	// Prefixes the XML output with the given bytes.
	PrefixXML []byte
	// Allows changing of output to XHTML instead of HTML. Default is "text/html"
	HTMLContentType string
}

func prepareOptions(options []Options) Options {
	var opt Options
	if len(options) > 0 {
		opt = options[0]
	}

	// Defaults
	if len(opt.Directory) == 0 {
		opt.Directory = "templates"
	}
	if len(opt.Extensions) == 0 {
		opt.Extensions = []string{".tmpl", ".html"}
	}
	if len(opt.HTMLContentType) == 0 {
		opt.HTMLContentType = ContentHTML
	}

	return opt
}

// Pongoer is a Middleware that maps a macaron.Render service into the Macaron handler chain.
// An single variadic pongo2.Options struct can be optionally provided to configure
// HTML rendering. The default directory for templates is "templates" and the default
// file extension is ".tmpl" and ".html".
//
// If MACARON_ENV is set to "" or "development" then templates will be recompiled on every request. For more performance, set the
// MACARON_ENV environment variable to "production".
func Pongoer(options ...Options) macaron.Handler {
	renderOpt = prepareOptions(options)
	cs := prepareCharset(renderOpt.Charset)
	compile(renderOpt)

	return func(ctx *macaron.Context, rw http.ResponseWriter, req *http.Request) {
		if macaron.Env == macaron.DEV {
			compile(renderOpt)
		}
		r := &render{
			TplRender: &macaron.TplRender{
				ResponseWriter: rw,
				Req:            req,
				Opt: macaron.RenderOptions{
					IndentJSON: renderOpt.IndentJSON,
					IndentXML:  renderOpt.IndentXML,
					PrefixJSON: renderOpt.PrefixJSON,
					PrefixXML:  renderOpt.PrefixXML,
				},
				CompiledCharset: cs,
			},
			ResponseWriter:  rw,
			opt:             renderOpt,
			compiledCharset: cs,
		}
		ctx.Render = r
		ctx.MapTo(r, (*macaron.Render)(nil))
	}
}

type render struct {
	*macaron.TplRender
	http.ResponseWriter
	opt             Options
	compiledCharset string

	startTime time.Time
}

func data2Context(data interface{}) pongo2.Context {
	return pongo2.Context(data.(map[string]interface{}))
}

func (r *render) HTML(status int, name string, data interface{}, _ ...macaron.HTMLOptions) {
	r.startTime = time.Now()

	lock.RLock()
	defer lock.RUnlock()

	t := tplMap[name]
	if t == nil {
		http.Error(r, "pongo2: \""+name+"\" is undefined", http.StatusInternalServerError)
		return
	}

	r.Header().Set(ContentType, r.opt.HTMLContentType+r.compiledCharset)
	r.WriteHeader(status)
	if err := t.ExecuteWriter(data2Context(data), r); err != nil {
		http.Error(r, err.Error(), http.StatusInternalServerError)
		return
	}
}

func (r *render) HTMLString(name string, data interface{}, _ ...macaron.HTMLOptions) (string, error) {
	lock.RLock()
	defer lock.RUnlock()

	t := tplMap[name]
	if t == nil {
		http.Error(r, "pongo2: \""+name+"\" is undefined", http.StatusInternalServerError)
		return "", nil
	}

	out, err := t.Execute(data2Context(data))
	if err != nil {
		return "", err
	}

	return out, nil
}

// SetTemplatePath changes templates path.
// FIXME: does not support Host Switcher.
func (r *render) SetTemplatePath(newPath string) {
	renderOpt.Directory = newPath
	compile(renderOpt)
}
<|endoftext|>


<commit_before>package postal

import (
	"net/smtp"
)

// MailRecord represents a single record of the SMTP transaction sent
type MailRecord struct {
	Host string
	Auth smtp.Auth
	From string
	To   []string
	Body []byte
}

// Postal represents a mocked SMTP mailer
type Postal struct {
	mailed      int
	mailRecords []MailRecord
	err         error
}

// New initializes and returns a new Postal object
func New() *Postal {
	return &Postal{}
}

// Mailed returns the number of messages mailed
func (p *Postal) Mailed() int {
	return p.mailed
}

// Mailer returns a mocked smtp mailer method capture and force actions for assertions
func (p *Postal) Mailer() func(string, smtp.Auth, string, []string, []byte) error {
	return func(host string, auth smtp.Auth, from string, to []string, body []byte) error {
		if p.err != nil {
			return p.err
		}

		record := MailRecord{
			Host: host,
			Auth: auth,
			From: from,
			To:   to,
			Body: body,
		}
		p.mailRecords = append(p.mailRecords, record)
		p.mailed++

		return nil
	}
}

// MailRecords returns the collection of mail records Postal has captured
func (p *Postal) MailRecords() []MailRecord {
	return p.mailRecords
}

// SetError sets the error the mailer is to return when called
func (p *Postal) SetError(err error) {
	p.err = err
}
<commit_msg>updating package description<commit_after>//Package postal is a mock SMTP mailer
package postal

import (
	"net/smtp"
)

// MailRecord represents a single record of the SMTP transaction sent
type MailRecord struct {
	Host string
	Auth smtp.Auth
	From string
	To   []string
	Body []byte
}

// Postal represents a mocked SMTP mailer
type Postal struct {
	mailed      int
	mailRecords []MailRecord
	err         error
}

// New initializes and returns a new Postal object
func New() *Postal {
	return &Postal{}
}

// Mailed returns the number of messages mailed
func (p *Postal) Mailed() int {
	return p.mailed
}

// Mailer returns a mocked smtp mailer method capture and force actions for assertions
func (p *Postal) Mailer() func(string, smtp.Auth, string, []string, []byte) error {
	return func(host string, auth smtp.Auth, from string, to []string, body []byte) error {
		if p.err != nil {
			return p.err
		}

		record := MailRecord{
			Host: host,
			Auth: auth,
			From: from,
			To:   to,
			Body: body,
		}
		p.mailRecords = append(p.mailRecords, record)
		p.mailed++

		return nil
	}
}

// MailRecords returns the collection of mail records Postal has captured
func (p *Postal) MailRecords() []MailRecord {
	return p.mailRecords
}

// SetError sets the error the mailer is to return when called
func (p *Postal) SetError(err error) {
	p.err = err
}
<|endoftext|>


<commit_before>package godebug

import (
	"bytes"
	"fmt"
	"io"
	"os"

	"github.com/pschlump/MiscLib"
)

/*
TODO
	1. Add in JSON format
	2. Add in %(LINE) %(FUNC) etc.

func x() {
	var buffer bytes.Buffer

	for i := 0; i < 1000; i++ {
		buffer.WriteString("a")
	}

	fmt.Println(buffer.String())
}
*/

var ColorTab map[string]string

func init() {
	ColorTab = make(map[string]string)

	ColorTab["red"] = MiscLib.ColorRed
	ColorTab["green"] = MiscLib.ColorGreen
	ColorTab["yellow"] = MiscLib.ColorYellow
	ColorTab["blue"] = MiscLib.ColorBlue
	ColorTab["cyan"] = MiscLib.ColorCyan
	ColorTab["reset"] = MiscLib.ColorReset

	ColorTab["Red"] = MiscLib.ColorRed
	ColorTab["Green"] = MiscLib.ColorGreen
	ColorTab["Yellow"] = MiscLib.ColorYellow
	ColorTab["Blue"] = MiscLib.ColorBlue
	ColorTab["Cyan"] = MiscLib.ColorCyan
	ColorTab["Reset"] = MiscLib.ColorReset

	ColorTab["!"] = MiscLib.ColorReset
}

func ProcessFormat(format string) string {
	var buffer bytes.Buffer
	colorFound := false
	var i, j int
	for i = 0; i < len(format); i++ {
		if format[i] == '%' && i+1 < len(format) && format[i+1] == '(' {
			color := "Red"
			for j = i + 2; j < len(format); j++ {
				if format[j] == ')' && i+2 < j {
					// fmt.Printf("Pick of %d:%d\n", i+2, j)
					color = format[i+2 : j]
					break
				}
			}
			i = j
			var ct string
			var ok bool
			switch color {
			case "LF":
				ct = LF(3)
			default:
				// fmt.Printf("--->%s<---\n", color)
				ct, ok = ColorTab[color]
				if !ok {
					ct = ColorTab["Red"]
				}
				colorFound = true
			}
			buffer.WriteString(ct)

			if color == "!" || color == "Reset" {
				colorFound = false
			}

			//		} else if format[i] == '%' && i+1 < len(format) && format[i+1] == 'J' {
			//			buffer.WriteString(SVarI(arg[nth]))
			//			removeNthArg()
			//		} else if format[i] == '%' && i+1 < len(format) && format[i+1] == 'j' {
			//			buffer.WriteString(SVarI(arg[nth]))
			//			removeNthArg()

		} else {
			buffer.WriteByte(format[i])
		}
	}
	if colorFound {
		buffer.WriteString(ColorTab["Reset"])
	}
	return buffer.String()
}

func Printf(format string, a ...interface{}) (n int, err error) {
	return fmt.Printf(ProcessFormat(format), a...)
}

func Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) {
	return fmt.Fprintf(w, ProcessFormat(format), a...)
}

func DbPf(db bool, format string, a ...interface{}) (n int, err error) {
	if db {
		return fmt.Printf(ProcessFormat(format), a...)
	}
	return 0, nil
}

func DbPfb(db bool, format string, a ...interface{}) (n int, err error) {
	if db {
		ff := ProcessFormat(format)
		fmt.Fprintf(os.Stderr, ff, a...)
		return fmt.Printf(ff, a...)
	}
	return 0, nil
}

func DbPfe(envVar string, format string, a ...interface{}) (n int, err error) {
	return DbPfb(ChkEnv(envVar), format, a...)
}
<commit_msg>Updated print function.<commit_after>package godebug

import (
	"bytes"
	"fmt"
	"io"
	"os"

	"github.com/pschlump/MiscLib"
)

/*
TODO
	1. Add in JSON format
	2. Add in %(LINE) %(FUNC) etc.
	3. Add in %(TB:4) 	Trace back 4 levels

func x() {
	var buffer bytes.Buffer

	for i := 0; i < 1000; i++ {
		buffer.WriteString("a")
	}

	fmt.Println(buffer.String())
}
*/

var ColorTab map[string]string

func init() {
	ColorTab = make(map[string]string)

	ColorTab["red"] = MiscLib.ColorRed
	ColorTab["green"] = MiscLib.ColorGreen
	ColorTab["yellow"] = MiscLib.ColorYellow
	ColorTab["blue"] = MiscLib.ColorBlue
	ColorTab["cyan"] = MiscLib.ColorCyan
	ColorTab["reset"] = MiscLib.ColorReset

	ColorTab["Red"] = MiscLib.ColorRed
	ColorTab["Green"] = MiscLib.ColorGreen
	ColorTab["Yellow"] = MiscLib.ColorYellow
	ColorTab["Blue"] = MiscLib.ColorBlue
	ColorTab["Cyan"] = MiscLib.ColorCyan
	ColorTab["Reset"] = MiscLib.ColorReset

	ColorTab["!"] = MiscLib.ColorReset
}

func ProcessFormat(format string) string {
	var buffer bytes.Buffer
	colorFound := false
	var i, j int
	for i = 0; i < len(format); i++ {
		if format[i] == '%' && i+1 < len(format) && format[i+1] == '(' {
			color := "Red"
			for j = i + 2; j < len(format); j++ {
				if format[j] == ')' && i+2 < j {
					// fmt.Printf("Pick of %d:%d\n", i+2, j)
					color = format[i+2 : j]
					break
				}
			}
			i = j
			var ct string
			var ok bool
			switch color {
			case "LF":
				ct = LF(3)
			default:
				// fmt.Printf("--->%s<---\n", color)
				ct, ok = ColorTab[color]
				if !ok {
					ct = ColorTab["Red"]
				}
				colorFound = true
			}
			buffer.WriteString(ct)

			if color == "!" || color == "Reset" {
				colorFound = false
			}

			//		} else if format[i] == '%' && i+1 < len(format) && format[i+1] == 'J' {
			//			buffer.WriteString(SVarI(arg[nth]))
			//			removeNthArg()
			//		} else if format[i] == '%' && i+1 < len(format) && format[i+1] == 'j' {
			//			buffer.WriteString(SVarI(arg[nth]))
			//			removeNthArg()

		} else {
			buffer.WriteByte(format[i])
		}
	}
	if colorFound {
		buffer.WriteString(ColorTab["Reset"])
	}
	return buffer.String()
}

func Printf(format string, a ...interface{}) (n int, err error) {
	return fmt.Printf(ProcessFormat(format), a...)
}

func Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) {
	return fmt.Fprintf(w, ProcessFormat(format), a...)
}

func DbPf(db bool, format string, a ...interface{}) (n int, err error) {
	if db {
		return fmt.Printf(ProcessFormat(format), a...)
	}
	return 0, nil
}

func DbPfb(db bool, format string, a ...interface{}) (n int, err error) {
	if db {
		ff := ProcessFormat(format)
		fmt.Fprintf(os.Stderr, ff, a...)
		return fmt.Printf(ff, a...)
	}
	return 0, nil
}

func DbPfe(envVar string, format string, a ...interface{}) (n int, err error) {
	return DbPfb(ChkEnv(envVar), format, a...)
}
<|endoftext|>


<commit_before>package main

import "github.com/jason0x43/go-alfred"

type LogoutCommand struct{}

func (c LogoutCommand) Keyword() string {
	return "logout"
}

func (c LogoutCommand) IsEnabled() bool {
	return config.ApiKey != ""
}

func (c LogoutCommand) MenuItem() alfred.Item {
	return alfred.Item{
		Title:        c.Keyword(),
		Autocomplete: c.Keyword(),
		SubtitleAll:  "Logout of Toggl, but keep any local config",
	}
}

func (c LogoutCommand) Items(prefix, query string) ([]alfred.Item, error) {
	item := c.MenuItem()
	item.Arg = "logout"
	return []alfred.Item{item}, nil
}

func (c LogoutCommand) Do(query string) (string, error) {
	config.ApiKey = ""
	err := alfred.SaveJson(configFile, &config)
	if err != nil {
		return "", err
	}

	workflow.ShowMessage("You are now logged out of Toggl")
	return "", nil
}
<commit_msg>Make partially-entered logout command work<commit_after>package main

import "github.com/jason0x43/go-alfred"

type LogoutCommand struct{}

func (c LogoutCommand) Keyword() string {
	return "logout"
}

func (c LogoutCommand) IsEnabled() bool {
	return config.ApiKey != ""
}

func (c LogoutCommand) MenuItem() alfred.Item {
	return alfred.Item{
		Title:        c.Keyword(),
		Autocomplete: c.Keyword(),
		SubtitleAll:  "Logout of Toggl, but keep any local config",
		Arg:          "logout",
	}
}

func (c LogoutCommand) Items(prefix, query string) ([]alfred.Item, error) {
	return []alfred.Item{c.MenuItem()}, nil
}

func (c LogoutCommand) Do(query string) (string, error) {
	config.ApiKey = ""
	err := alfred.SaveJson(configFile, &config)
	if err != nil {
		return "", err
	}

	workflow.ShowMessage("You are now logged out of Toggl")
	return "", nil
}
<|endoftext|>


<commit_before>// Copyright 2016 The Cockroach 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.
//
// Author: Nathan VanBenschoten (nvanbenschoten@gmail.com)

package sql

import (
	"fmt"
	"sort"

	"github.com/cockroachdb/cockroach/keys"
	"github.com/cockroachdb/cockroach/sql/parser"
	"github.com/cockroachdb/cockroach/sql/sqlbase"
)

//
// Programmer interface to define virtual schemas.
//

// virtualSchema represents a database with a set of virtual tables. Virtual
// tables differ from standard tables in that they are not persisted to storage,
// and instead their contents are populated whenever they are queried.
//
// The virtual database and its virtual tables also differ from standard databases
// and tables in that their descriptors are not distributed, but instead live statically
// in code. This means that they are accessed separately from standard descriptors.
type virtualSchema struct {
	name   string
	tables []virtualSchemaTable
}

// virtualSchemaTable represents a table within a virtualSchema.
type virtualSchemaTable struct {
	schema   string
	populate func(p *planner, addRow func(...parser.Datum) error) error
}

// virtualSchemas holds a slice of statically registered virtualSchema objects.
//
// When adding a new virtualSchema, define a virtualSchema in a separate file, and
// add that object to this slice.
var virtualSchemas = []virtualSchema{
	informationSchema,
	pgCatalog,
}

//
// SQL-layer interface to work with virtual schemas.
//

// virtualSchemaHolder is a type used to provide convenient access to virtual
// database and table descriptors. virtualSchemaHolder, virtualSchemaEntry,
// and virtualTableEntry make up the generated data structure which the
// virtualSchemas slice is mapped to. Because of this, they should not be
// created directly, but instead will be populated in a post-startup hook
// on an Executor.
type virtualSchemaHolder struct {
	entries      map[string]virtualSchemaEntry
	orderedNames []string
}

type virtualSchemaEntry struct {
	desc              *sqlbase.DatabaseDescriptor
	tables            map[string]virtualTableEntry
	orderedTableNames []string
}

func (e virtualSchemaEntry) tableNames() parser.TableNames {
	var res parser.TableNames
	for _, tableName := range e.orderedTableNames {
		tn := parser.TableName{
			DatabaseName: parser.Name(e.desc.Name),
			TableName:    parser.Name(tableName),
		}
		res = append(res, tn)
	}
	return res
}

type virtualTableEntry struct {
	tableDef virtualSchemaTable
	desc     *sqlbase.TableDescriptor
}

// getValuesNode returns a new valuesNode for the virtual table using the
// provided planner.
func (e virtualTableEntry) getValuesNode(p *planner) (*valuesNode, error) {
	var columns ResultColumns
	for _, col := range e.desc.Columns {
		columns = append(columns, ResultColumn{
			Name: col.Name,
			Typ:  col.Type.ToDatumType(),
		})
	}
	v := p.newContainerValuesNode(columns, 0)

	err := e.tableDef.populate(p, func(datum ...parser.Datum) error {
		if r, c := len(datum), len(v.columns); r != c {
			panic(fmt.Sprintf("datum row count and column count differ: %d vs %d", r, c))
		}
		return v.rows.AddRow(datum)
	})
	if err != nil {
		v.Close()
		return nil, err
	}
	return v, nil
}

func (vs *virtualSchemaHolder) init(p *planner) error {
	*vs = virtualSchemaHolder{
		entries:      make(map[string]virtualSchemaEntry, len(virtualSchemas)),
		orderedNames: make([]string, len(virtualSchemas)),
	}
	for i, schema := range virtualSchemas {
		dbName := schema.name
		dbDesc := initVirtualDatabaseDesc(dbName)
		tables := make(map[string]virtualTableEntry, len(schema.tables))
		orderedTableNames := make([]string, 0, len(schema.tables))
		for _, table := range schema.tables {
			tableDesc, err := initVirtualTableDesc(p, table)
			if err != nil {
				return err
			}
			tables[tableDesc.Name] = virtualTableEntry{
				tableDef: table,
				desc:     &tableDesc,
			}
			orderedTableNames = append(orderedTableNames, tableDesc.Name)
		}
		sort.Strings(orderedTableNames)
		vs.entries[dbName] = virtualSchemaEntry{
			desc:              dbDesc,
			tables:            tables,
			orderedTableNames: orderedTableNames,
		}
		vs.orderedNames[i] = dbName
	}
	sort.Strings(vs.orderedNames)
	return nil
}

// Virtual databases and tables each have an empty set of privileges. In practice,
// all users have SELECT privileges on the database/tables, but this is handled
// separately from normal SELECT privileges, because the virtual schemas need more
// fine-grained access control. For instance, information_schema will only expose
// rows to a given user which that user has access to.
var emptyPrivileges = &sqlbase.PrivilegeDescriptor{}

func initVirtualDatabaseDesc(name string) *sqlbase.DatabaseDescriptor {
	return &sqlbase.DatabaseDescriptor{
		Name:       name,
		ID:         keys.VirtualDescriptorID,
		Privileges: emptyPrivileges,
	}
}

func initVirtualTableDesc(p *planner, t virtualSchemaTable) (sqlbase.TableDescriptor, error) {
	stmt, err := parser.ParseOneTraditional(t.schema)
	if err != nil {
		return sqlbase.TableDescriptor{}, err
	}
	create := stmt.(*parser.CreateTable)
	return p.makeTableDesc(create, 0, keys.VirtualDescriptorID, emptyPrivileges, nil)
}

// getVirtualSchemaEntry retrieves a virtual schema entry given a database name.
func (vs *virtualSchemaHolder) getVirtualSchemaEntry(name string) (virtualSchemaEntry, bool) {
	if vs == nil {
		return virtualSchemaEntry{}, false
	}
	e, ok := vs.entries[name]
	return e, ok
}

// getVirtualDatabaseDesc checks if the provided name matches a virtual database,
// and if so, returns that database's descriptor.
func (vs *virtualSchemaHolder) getVirtualDatabaseDesc(name string) *sqlbase.DatabaseDescriptor {
	if e, ok := vs.getVirtualSchemaEntry(name); ok {
		return e.desc
	}
	return nil
}

// isVirtualDatabase checks if the provided name corresponds to a virtual database.
func (vs *virtualSchemaHolder) isVirtualDatabase(name string) bool {
	_, ok := vs.getVirtualSchemaEntry(name)
	return ok
}

// IsVirtualDatabase checks if the provided name corresponds to a virtual database,
// exposing this information on the Executor object itself.
func (e *Executor) IsVirtualDatabase(name string) bool {
	return e.virtualSchemas.isVirtualDatabase(name)
}

// getVirtualTableEntry checks if the provided name matches a virtual database/table
// pair. The function will return the table's virtual table entry if the name matches
// a specific table. It will return an error if the name references a virtual database
// but the table is non-existent.
func (vs *virtualSchemaHolder) getVirtualTableEntry(
	tn *parser.TableName,
) (virtualTableEntry, error) {
	if db, ok := vs.getVirtualSchemaEntry(tn.Database()); ok {
		if t, ok := db.tables[sqlbase.NormalizeName(tn.TableName)]; ok {
			return t, nil
		}
		return virtualTableEntry{}, sqlbase.NewUndefinedTableError(tn.String())
	}
	return virtualTableEntry{}, nil
}

// getVirtualTableDesc checks if the provided name matches a virtual database/table
// pair, and returns its descriptor if it does.
func (vs *virtualSchemaHolder) getVirtualTableDesc(
	tn *parser.TableName,
) (*sqlbase.TableDescriptor, error) {
	t, err := vs.getVirtualTableEntry(tn)
	if err != nil {
		return nil, err
	}
	return t.desc, nil
}

// isVirtualDescriptor checks if the provided DescriptorProto is an instance of
// a Virtual Descriptor.
func isVirtualDescriptor(desc sqlbase.DescriptorProto) bool {
	return desc.GetID() == keys.VirtualDescriptorID
}
<commit_msg>sql: Assert correct types on virtual schema row population<commit_after>// Copyright 2016 The Cockroach 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.
//
// Author: Nathan VanBenschoten (nvanbenschoten@gmail.com)

package sql

import (
	"fmt"
	"sort"

	"github.com/cockroachdb/cockroach/keys"
	"github.com/cockroachdb/cockroach/sql/parser"
	"github.com/cockroachdb/cockroach/sql/sqlbase"
)

//
// Programmer interface to define virtual schemas.
//

// virtualSchema represents a database with a set of virtual tables. Virtual
// tables differ from standard tables in that they are not persisted to storage,
// and instead their contents are populated whenever they are queried.
//
// The virtual database and its virtual tables also differ from standard databases
// and tables in that their descriptors are not distributed, but instead live statically
// in code. This means that they are accessed separately from standard descriptors.
type virtualSchema struct {
	name   string
	tables []virtualSchemaTable
}

// virtualSchemaTable represents a table within a virtualSchema.
type virtualSchemaTable struct {
	schema   string
	populate func(p *planner, addRow func(...parser.Datum) error) error
}

// virtualSchemas holds a slice of statically registered virtualSchema objects.
//
// When adding a new virtualSchema, define a virtualSchema in a separate file, and
// add that object to this slice.
var virtualSchemas = []virtualSchema{
	informationSchema,
	pgCatalog,
}

//
// SQL-layer interface to work with virtual schemas.
//

// virtualSchemaHolder is a type used to provide convenient access to virtual
// database and table descriptors. virtualSchemaHolder, virtualSchemaEntry,
// and virtualTableEntry make up the generated data structure which the
// virtualSchemas slice is mapped to. Because of this, they should not be
// created directly, but instead will be populated in a post-startup hook
// on an Executor.
type virtualSchemaHolder struct {
	entries      map[string]virtualSchemaEntry
	orderedNames []string
}

type virtualSchemaEntry struct {
	desc              *sqlbase.DatabaseDescriptor
	tables            map[string]virtualTableEntry
	orderedTableNames []string
}

func (e virtualSchemaEntry) tableNames() parser.TableNames {
	var res parser.TableNames
	for _, tableName := range e.orderedTableNames {
		tn := parser.TableName{
			DatabaseName: parser.Name(e.desc.Name),
			TableName:    parser.Name(tableName),
		}
		res = append(res, tn)
	}
	return res
}

type virtualTableEntry struct {
	tableDef virtualSchemaTable
	desc     *sqlbase.TableDescriptor
}

// getValuesNode returns a new valuesNode for the virtual table using the
// provided planner.
func (e virtualTableEntry) getValuesNode(p *planner) (*valuesNode, error) {
	var columns ResultColumns
	for _, col := range e.desc.Columns {
		columns = append(columns, ResultColumn{
			Name: col.Name,
			Typ:  col.Type.ToDatumType(),
		})
	}
	v := p.newContainerValuesNode(columns, 0)

	err := e.tableDef.populate(p, func(datums ...parser.Datum) error {
		if r, c := len(datums), len(v.columns); r != c {
			panic(fmt.Sprintf("datum row count and column count differ: %d vs %d", r, c))
		}
		for i, col := range v.columns {
			datum := datums[i]
			if !(datum == parser.DNull || datum.TypeEqual(col.Typ)) {
				panic(fmt.Sprintf("datum column %q expected to be type %s; found type %s",
					col.Name, col.Typ.Type(), datum.Type()))
			}
		}
		return v.rows.AddRow(datums)
	})
	if err != nil {
		v.Close()
		return nil, err
	}
	return v, nil
}

func (vs *virtualSchemaHolder) init(p *planner) error {
	*vs = virtualSchemaHolder{
		entries:      make(map[string]virtualSchemaEntry, len(virtualSchemas)),
		orderedNames: make([]string, len(virtualSchemas)),
	}
	for i, schema := range virtualSchemas {
		dbName := schema.name
		dbDesc := initVirtualDatabaseDesc(dbName)
		tables := make(map[string]virtualTableEntry, len(schema.tables))
		orderedTableNames := make([]string, 0, len(schema.tables))
		for _, table := range schema.tables {
			tableDesc, err := initVirtualTableDesc(p, table)
			if err != nil {
				return err
			}
			tables[tableDesc.Name] = virtualTableEntry{
				tableDef: table,
				desc:     &tableDesc,
			}
			orderedTableNames = append(orderedTableNames, tableDesc.Name)
		}
		sort.Strings(orderedTableNames)
		vs.entries[dbName] = virtualSchemaEntry{
			desc:              dbDesc,
			tables:            tables,
			orderedTableNames: orderedTableNames,
		}
		vs.orderedNames[i] = dbName
	}
	sort.Strings(vs.orderedNames)
	return nil
}

// Virtual databases and tables each have an empty set of privileges. In practice,
// all users have SELECT privileges on the database/tables, but this is handled
// separately from normal SELECT privileges, because the virtual schemas need more
// fine-grained access control. For instance, information_schema will only expose
// rows to a given user which that user has access to.
var emptyPrivileges = &sqlbase.PrivilegeDescriptor{}

func initVirtualDatabaseDesc(name string) *sqlbase.DatabaseDescriptor {
	return &sqlbase.DatabaseDescriptor{
		Name:       name,
		ID:         keys.VirtualDescriptorID,
		Privileges: emptyPrivileges,
	}
}

func initVirtualTableDesc(p *planner, t virtualSchemaTable) (sqlbase.TableDescriptor, error) {
	stmt, err := parser.ParseOneTraditional(t.schema)
	if err != nil {
		return sqlbase.TableDescriptor{}, err
	}
	create := stmt.(*parser.CreateTable)
	return p.makeTableDesc(create, 0, keys.VirtualDescriptorID, emptyPrivileges, nil)
}

// getVirtualSchemaEntry retrieves a virtual schema entry given a database name.
func (vs *virtualSchemaHolder) getVirtualSchemaEntry(name string) (virtualSchemaEntry, bool) {
	if vs == nil {
		return virtualSchemaEntry{}, false
	}
	e, ok := vs.entries[name]
	return e, ok
}

// getVirtualDatabaseDesc checks if the provided name matches a virtual database,
// and if so, returns that database's descriptor.
func (vs *virtualSchemaHolder) getVirtualDatabaseDesc(name string) *sqlbase.DatabaseDescriptor {
	if e, ok := vs.getVirtualSchemaEntry(name); ok {
		return e.desc
	}
	return nil
}

// isVirtualDatabase checks if the provided name corresponds to a virtual database.
func (vs *virtualSchemaHolder) isVirtualDatabase(name string) bool {
	_, ok := vs.getVirtualSchemaEntry(name)
	return ok
}

// IsVirtualDatabase checks if the provided name corresponds to a virtual database,
// exposing this information on the Executor object itself.
func (e *Executor) IsVirtualDatabase(name string) bool {
	return e.virtualSchemas.isVirtualDatabase(name)
}

// getVirtualTableEntry checks if the provided name matches a virtual database/table
// pair. The function will return the table's virtual table entry if the name matches
// a specific table. It will return an error if the name references a virtual database
// but the table is non-existent.
func (vs *virtualSchemaHolder) getVirtualTableEntry(
	tn *parser.TableName,
) (virtualTableEntry, error) {
	if db, ok := vs.getVirtualSchemaEntry(tn.Database()); ok {
		if t, ok := db.tables[sqlbase.NormalizeName(tn.TableName)]; ok {
			return t, nil
		}
		return virtualTableEntry{}, sqlbase.NewUndefinedTableError(tn.String())
	}
	return virtualTableEntry{}, nil
}

// getVirtualTableDesc checks if the provided name matches a virtual database/table
// pair, and returns its descriptor if it does.
func (vs *virtualSchemaHolder) getVirtualTableDesc(
	tn *parser.TableName,
) (*sqlbase.TableDescriptor, error) {
	t, err := vs.getVirtualTableEntry(tn)
	if err != nil {
		return nil, err
	}
	return t.desc, nil
}

// isVirtualDescriptor checks if the provided DescriptorProto is an instance of
// a Virtual Descriptor.
func isVirtualDescriptor(desc sqlbase.DescriptorProto) bool {
	return desc.GetID() == keys.VirtualDescriptorID
}
<|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 provider

import (
	"fmt"
	"sort"
	"strings"

	"cloud.google.com/go/compute/metadata"
	"github.com/linki/instrumented_http"
	log "github.com/sirupsen/logrus"

	dns "google.golang.org/api/dns/v1"

	"golang.org/x/net/context"
	"golang.org/x/oauth2/google"

	googleapi "google.golang.org/api/googleapi"

	"github.com/kubernetes-incubator/external-dns/endpoint"
	"github.com/kubernetes-incubator/external-dns/plan"
)

const (
	googleRecordTTL = 300
)

type managedZonesCreateCallInterface interface {
	Do(opts ...googleapi.CallOption) (*dns.ManagedZone, error)
}

type managedZonesListCallInterface interface {
	Pages(ctx context.Context, f func(*dns.ManagedZonesListResponse) error) error
}

type managedZonesServiceInterface interface {
	Create(project string, managedzone *dns.ManagedZone) managedZonesCreateCallInterface
	List(project string) managedZonesListCallInterface
}

type resourceRecordSetsListCallInterface interface {
	Pages(ctx context.Context, f func(*dns.ResourceRecordSetsListResponse) error) error
}

type resourceRecordSetsClientInterface interface {
	List(project string, managedZone string) resourceRecordSetsListCallInterface
}

type changesCreateCallInterface interface {
	Do(opts ...googleapi.CallOption) (*dns.Change, error)
}

type changesServiceInterface interface {
	Create(project string, managedZone string, change *dns.Change) changesCreateCallInterface
}

type resourceRecordSetsService struct {
	service *dns.ResourceRecordSetsService
}

func (r resourceRecordSetsService) List(project string, managedZone string) resourceRecordSetsListCallInterface {
	return r.service.List(project, managedZone)
}

type managedZonesService struct {
	service *dns.ManagedZonesService
}

func (m managedZonesService) Create(project string, managedzone *dns.ManagedZone) managedZonesCreateCallInterface {
	return m.service.Create(project, managedzone)
}

func (m managedZonesService) List(project string) managedZonesListCallInterface {
	return m.service.List(project)
}

type changesService struct {
	service *dns.ChangesService
}

func (c changesService) Create(project string, managedZone string, change *dns.Change) changesCreateCallInterface {
	return c.service.Create(project, managedZone, change)
}

// GoogleProvider is an implementation of Provider for Google CloudDNS.
type GoogleProvider struct {
	// The Google project to work in
	project string
	// Enabled dry-run will print any modifying actions rather than execute them.
	dryRun bool
	// only consider hosted zones managing domains ending in this suffix
	domainFilter DomainFilter
	// only consider hosted zones ending with this zone id
	zoneIDFilter ZoneIDFilter
	// A client for managing resource record sets
	resourceRecordSetsClient resourceRecordSetsClientInterface
	// A client for managing hosted zones
	managedZonesClient managedZonesServiceInterface
	// A client for managing change sets
	changesClient changesServiceInterface
}

// NewGoogleProvider initializes a new Google CloudDNS based Provider.
func NewGoogleProvider(project string, domainFilter DomainFilter, zoneIDFilter ZoneIDFilter, dryRun bool) (*GoogleProvider, error) {
	gcloud, err := google.DefaultClient(context.TODO(), dns.NdevClouddnsReadwriteScope)
	if err != nil {
		return nil, err
	}

	gcloud = instrumented_http.NewClient(gcloud, &instrumented_http.Callbacks{
		PathProcessor: func(path string) string {
			parts := strings.Split(path, "/")
			return parts[len(parts)-1]
		},
	})

	dnsClient, err := dns.New(gcloud)
	if err != nil {
		return nil, err
	}

	if project == "" {
		mProject, mErr := metadata.ProjectID()
		if mErr == nil {
			log.Infof("Google project auto-detected: %s", mProject)
			project = mProject
		}
	}

	provider := &GoogleProvider{
		project:                  project,
		domainFilter:             domainFilter,
		zoneIDFilter:             zoneIDFilter,
		dryRun:                   dryRun,
		resourceRecordSetsClient: resourceRecordSetsService{dnsClient.ResourceRecordSets},
		managedZonesClient:       managedZonesService{dnsClient.ManagedZones},
		changesClient:            changesService{dnsClient.Changes},
	}

	return provider, nil
}

// Zones returns the list of hosted zones.
func (p *GoogleProvider) Zones() (map[string]*dns.ManagedZone, error) {
	zones := make(map[string]*dns.ManagedZone)

	f := func(resp *dns.ManagedZonesListResponse) error {
		for _, zone := range resp.ManagedZones {
			if p.domainFilter.Match(zone.DnsName) && p.zoneIDFilter.Match(fmt.Sprintf("%v", zone.Id)) {
				zones[zone.Name] = zone
				log.Debugf("Matched %s (zone: %s)", zone.DnsName, zone.Name)
			} else {
				log.Debugf("Filtered %s (zone: %s)", zone.DnsName, zone.Name)
			}
		}

		return nil
	}

	log.Debugf("Matching zones against domain filters: %v", p.domainFilter.filters)
	if err := p.managedZonesClient.List(p.project).Pages(context.TODO(), f); err != nil {
		return nil, err
	}

	if len(zones) == 0 {
		if p.domainFilter.IsConfigured() {
			log.Warnf("No zones in the project, %s, match domain filters: %v", p.project, p.domainFilter.filters)
		} else {
			log.Warnf("No zones found in the project, %s", p.project)
		}
	}

	for _, zone := range zones {
		log.Debugf("Considering zone: %s (domain: %s)", zone.Name, zone.DnsName)
	}

	return zones, nil
}

// Records returns the list of records in all relevant zones.
func (p *GoogleProvider) Records() (endpoints []*endpoint.Endpoint, _ error) {
	zones, err := p.Zones()
	if err != nil {
		return nil, err
	}

	f := func(resp *dns.ResourceRecordSetsListResponse) error {
		for _, r := range resp.Rrsets {
			if !supportedRecordType(r.Type) {
				continue
			}
			targets := make(endpoint.Targets, 0, len(r.Rrdatas))
			ep := endpoint.NewEndpointWithTTL(strings.TrimSuffix(r.Name, "."), r.Type, endpoint.TTL(r.Ttl), targets...)

			for _, rr := range r.Rrdatas {
				// each page is processed sequentially, no need for a mutex here.
				ep.Targets = append(ep.Targets, strings.TrimSuffix(rr, "."))
			}
			sort.Sort(ep.Targets)
			endpoints = append(endpoints, ep)
		}

		return nil
	}

	for _, z := range zones {
		if err := p.resourceRecordSetsClient.List(p.project, z.Name).Pages(context.TODO(), f); err != nil {
			return nil, err
		}
	}

	return endpoints, nil
}

// CreateRecords creates a given set of DNS records in the given hosted zone.
func (p *GoogleProvider) CreateRecords(endpoints []*endpoint.Endpoint) error {
	change := &dns.Change{}

	change.Additions = append(change.Additions, p.newFilteredRecords(endpoints)...)

	return p.submitChange(change)
}

// UpdateRecords updates a given set of old records to a new set of records in a given hosted zone.
func (p *GoogleProvider) UpdateRecords(records, oldRecords []*endpoint.Endpoint) error {
	change := &dns.Change{}

	change.Additions = append(change.Additions, p.newFilteredRecords(records)...)
	change.Deletions = append(change.Deletions, p.newFilteredRecords(oldRecords)...)

	return p.submitChange(change)
}

// DeleteRecords deletes a given set of DNS records in a given zone.
func (p *GoogleProvider) DeleteRecords(endpoints []*endpoint.Endpoint) error {
	change := &dns.Change{}

	change.Deletions = append(change.Deletions, p.newFilteredRecords(endpoints)...)

	return p.submitChange(change)
}

// ApplyChanges applies a given set of changes in a given zone.
func (p *GoogleProvider) ApplyChanges(changes *plan.Changes) error {
	change := &dns.Change{}

	change.Additions = append(change.Additions, p.newFilteredRecords(changes.Create)...)

	change.Additions = append(change.Additions, p.newFilteredRecords(changes.UpdateNew)...)
	change.Deletions = append(change.Deletions, p.newFilteredRecords(changes.UpdateOld)...)

	change.Deletions = append(change.Deletions, p.newFilteredRecords(changes.Delete)...)

	return p.submitChange(change)
}

// newFilteredRecords returns a collection of RecordSets based on the given endpoints and domainFilter.
func (p *GoogleProvider) newFilteredRecords(endpoints []*endpoint.Endpoint) []*dns.ResourceRecordSet {
	records := []*dns.ResourceRecordSet{}

	for _, endpoint := range endpoints {
		if p.domainFilter.Match(endpoint.DNSName) {
			records = append(records, newRecord(endpoint))
		}
	}

	return records
}

// submitChange takes a zone and a Change and sends it to Google.
func (p *GoogleProvider) submitChange(change *dns.Change) error {
	if len(change.Additions) == 0 && len(change.Deletions) == 0 {
		log.Info("All records are already up to date")
		return nil
	}

	zones, err := p.Zones()
	if err != nil {
		return err
	}

	// separate into per-zone change sets to be passed to the API.
	changes := separateChange(zones, change)

	for z, c := range changes {
		log.Infof("Change zone: %v", z)
		for _, del := range c.Deletions {
			log.Infof("Del records: %s %s %s %d", del.Name, del.Type, del.Rrdatas, del.Ttl)
		}
		for _, add := range c.Additions {
			log.Infof("Add records: %s %s %s %d", add.Name, add.Type, add.Rrdatas, add.Ttl)
		}
	}

	if p.dryRun {
		return nil
	}

	for z, c := range changes {
		if _, err := p.changesClient.Create(p.project, z, c).Do(); err != nil {
			return err
		}
	}

	return nil
}

// separateChange separates a multi-zone change into a single change per zone.
func separateChange(zones map[string]*dns.ManagedZone, change *dns.Change) map[string]*dns.Change {
	changes := make(map[string]*dns.Change)
	zoneNameIDMapper := zoneIDName{}
	for _, z := range zones {
		zoneNameIDMapper[z.Name] = z.DnsName
		changes[z.Name] = &dns.Change{
			Additions: []*dns.ResourceRecordSet{},
			Deletions: []*dns.ResourceRecordSet{},
		}
	}
	for _, a := range change.Additions {
		if zoneName, _ := zoneNameIDMapper.FindZone(ensureTrailingDot(a.Name)); zoneName != "" {
			changes[zoneName].Additions = append(changes[zoneName].Additions, a)
		} else {
			log.Warnf("No matching zone for record addition: %s %s %s %d", a.Name, a.Type, a.Rrdatas, a.Ttl)
		}
	}

	for _, d := range change.Deletions {
		if zoneName, _ := zoneNameIDMapper.FindZone(ensureTrailingDot(d.Name)); zoneName != "" {
			changes[zoneName].Deletions = append(changes[zoneName].Deletions, d)
		} else {
			log.Warnf("No matching zone for record deletion: %s %s %s %d", d.Name, d.Type, d.Rrdatas, d.Ttl)
		}
	}

	// separating a change could lead to empty sub changes, remove them here.
	for zone, change := range changes {
		if len(change.Additions) == 0 && len(change.Deletions) == 0 {
			delete(changes, zone)
		}
	}

	return changes
}

// newRecord returns a RecordSet based on the given endpoint.
func newRecord(ep *endpoint.Endpoint) *dns.ResourceRecordSet {
	// TODO(linki): works around appending a trailing dot to TXT records. I think
	// we should go back to storing DNS names with a trailing dot internally. This
	// way we can use it has is here and trim it off if it exists when necessary.
	targets := make([]string, len(ep.Targets))
	copy(targets, []string(ep.Targets))
	if ep.RecordType == endpoint.RecordTypeCNAME {
		targets[0] = ensureTrailingDot(targets[0])
	}

	// no annotation results in a Ttl of 0, default to 300 for backwards-compatability
	var ttl int64 = googleRecordTTL
	if ep.RecordTTL.IsConfigured() {
		ttl = int64(ep.RecordTTL)
	}

	return &dns.ResourceRecordSet{
		Name:    ensureTrailingDot(ep.DNSName),
		Rrdatas: targets,
		Ttl:     ttl,
		Type:    ep.RecordType,
	}
}
<commit_msg>Remove trim suffix<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 provider

import (
	"fmt"
	"sort"
	"strings"

	"cloud.google.com/go/compute/metadata"
	"github.com/linki/instrumented_http"
	log "github.com/sirupsen/logrus"

	dns "google.golang.org/api/dns/v1"

	"golang.org/x/net/context"
	"golang.org/x/oauth2/google"

	googleapi "google.golang.org/api/googleapi"

	"github.com/kubernetes-incubator/external-dns/endpoint"
	"github.com/kubernetes-incubator/external-dns/plan"
)

const (
	googleRecordTTL = 300
)

type managedZonesCreateCallInterface interface {
	Do(opts ...googleapi.CallOption) (*dns.ManagedZone, error)
}

type managedZonesListCallInterface interface {
	Pages(ctx context.Context, f func(*dns.ManagedZonesListResponse) error) error
}

type managedZonesServiceInterface interface {
	Create(project string, managedzone *dns.ManagedZone) managedZonesCreateCallInterface
	List(project string) managedZonesListCallInterface
}

type resourceRecordSetsListCallInterface interface {
	Pages(ctx context.Context, f func(*dns.ResourceRecordSetsListResponse) error) error
}

type resourceRecordSetsClientInterface interface {
	List(project string, managedZone string) resourceRecordSetsListCallInterface
}

type changesCreateCallInterface interface {
	Do(opts ...googleapi.CallOption) (*dns.Change, error)
}

type changesServiceInterface interface {
	Create(project string, managedZone string, change *dns.Change) changesCreateCallInterface
}

type resourceRecordSetsService struct {
	service *dns.ResourceRecordSetsService
}

func (r resourceRecordSetsService) List(project string, managedZone string) resourceRecordSetsListCallInterface {
	return r.service.List(project, managedZone)
}

type managedZonesService struct {
	service *dns.ManagedZonesService
}

func (m managedZonesService) Create(project string, managedzone *dns.ManagedZone) managedZonesCreateCallInterface {
	return m.service.Create(project, managedzone)
}

func (m managedZonesService) List(project string) managedZonesListCallInterface {
	return m.service.List(project)
}

type changesService struct {
	service *dns.ChangesService
}

func (c changesService) Create(project string, managedZone string, change *dns.Change) changesCreateCallInterface {
	return c.service.Create(project, managedZone, change)
}

// GoogleProvider is an implementation of Provider for Google CloudDNS.
type GoogleProvider struct {
	// The Google project to work in
	project string
	// Enabled dry-run will print any modifying actions rather than execute them.
	dryRun bool
	// only consider hosted zones managing domains ending in this suffix
	domainFilter DomainFilter
	// only consider hosted zones ending with this zone id
	zoneIDFilter ZoneIDFilter
	// A client for managing resource record sets
	resourceRecordSetsClient resourceRecordSetsClientInterface
	// A client for managing hosted zones
	managedZonesClient managedZonesServiceInterface
	// A client for managing change sets
	changesClient changesServiceInterface
}

// NewGoogleProvider initializes a new Google CloudDNS based Provider.
func NewGoogleProvider(project string, domainFilter DomainFilter, zoneIDFilter ZoneIDFilter, dryRun bool) (*GoogleProvider, error) {
	gcloud, err := google.DefaultClient(context.TODO(), dns.NdevClouddnsReadwriteScope)
	if err != nil {
		return nil, err
	}

	gcloud = instrumented_http.NewClient(gcloud, &instrumented_http.Callbacks{
		PathProcessor: func(path string) string {
			parts := strings.Split(path, "/")
			return parts[len(parts)-1]
		},
	})

	dnsClient, err := dns.New(gcloud)
	if err != nil {
		return nil, err
	}

	if project == "" {
		mProject, mErr := metadata.ProjectID()
		if mErr == nil {
			log.Infof("Google project auto-detected: %s", mProject)
			project = mProject
		}
	}

	provider := &GoogleProvider{
		project:                  project,
		domainFilter:             domainFilter,
		zoneIDFilter:             zoneIDFilter,
		dryRun:                   dryRun,
		resourceRecordSetsClient: resourceRecordSetsService{dnsClient.ResourceRecordSets},
		managedZonesClient:       managedZonesService{dnsClient.ManagedZones},
		changesClient:            changesService{dnsClient.Changes},
	}

	return provider, nil
}

// Zones returns the list of hosted zones.
func (p *GoogleProvider) Zones() (map[string]*dns.ManagedZone, error) {
	zones := make(map[string]*dns.ManagedZone)

	f := func(resp *dns.ManagedZonesListResponse) error {
		for _, zone := range resp.ManagedZones {
			if p.domainFilter.Match(zone.DnsName) && p.zoneIDFilter.Match(fmt.Sprintf("%v", zone.Id)) {
				zones[zone.Name] = zone
				log.Debugf("Matched %s (zone: %s)", zone.DnsName, zone.Name)
			} else {
				log.Debugf("Filtered %s (zone: %s)", zone.DnsName, zone.Name)
			}
		}

		return nil
	}

	log.Debugf("Matching zones against domain filters: %v", p.domainFilter.filters)
	if err := p.managedZonesClient.List(p.project).Pages(context.TODO(), f); err != nil {
		return nil, err
	}

	if len(zones) == 0 {
		if p.domainFilter.IsConfigured() {
			log.Warnf("No zones in the project, %s, match domain filters: %v", p.project, p.domainFilter.filters)
		} else {
			log.Warnf("No zones found in the project, %s", p.project)
		}
	}

	for _, zone := range zones {
		log.Debugf("Considering zone: %s (domain: %s)", zone.Name, zone.DnsName)
	}

	return zones, nil
}

// Records returns the list of records in all relevant zones.
func (p *GoogleProvider) Records() (endpoints []*endpoint.Endpoint, _ error) {
	zones, err := p.Zones()
	if err != nil {
		return nil, err
	}

	f := func(resp *dns.ResourceRecordSetsListResponse) error {
		for _, r := range resp.Rrsets {
			if !supportedRecordType(r.Type) {
				continue
			}
			targets := make(endpoint.Targets, 0, len(r.Rrdatas))

			for _, rr := range r.Rrdatas {
				// each page is processed sequentially, no need for a mutex here.
				targets = append(targets, rr)
			}
			sort.Sort(targets)
			endpoints = append(endpoints, endpoint.NewEndpointWithTTL(r.Name, r.Type, endpoint.TTL(r.Ttl), targets...))
		}

		return nil
	}

	for _, z := range zones {
		if err := p.resourceRecordSetsClient.List(p.project, z.Name).Pages(context.TODO(), f); err != nil {
			return nil, err
		}
	}

	return endpoints, nil
}

// CreateRecords creates a given set of DNS records in the given hosted zone.
func (p *GoogleProvider) CreateRecords(endpoints []*endpoint.Endpoint) error {
	change := &dns.Change{}

	change.Additions = append(change.Additions, p.newFilteredRecords(endpoints)...)

	return p.submitChange(change)
}

// UpdateRecords updates a given set of old records to a new set of records in a given hosted zone.
func (p *GoogleProvider) UpdateRecords(records, oldRecords []*endpoint.Endpoint) error {
	change := &dns.Change{}

	change.Additions = append(change.Additions, p.newFilteredRecords(records)...)
	change.Deletions = append(change.Deletions, p.newFilteredRecords(oldRecords)...)

	return p.submitChange(change)
}

// DeleteRecords deletes a given set of DNS records in a given zone.
func (p *GoogleProvider) DeleteRecords(endpoints []*endpoint.Endpoint) error {
	change := &dns.Change{}

	change.Deletions = append(change.Deletions, p.newFilteredRecords(endpoints)...)

	return p.submitChange(change)
}

// ApplyChanges applies a given set of changes in a given zone.
func (p *GoogleProvider) ApplyChanges(changes *plan.Changes) error {
	change := &dns.Change{}

	change.Additions = append(change.Additions, p.newFilteredRecords(changes.Create)...)

	change.Additions = append(change.Additions, p.newFilteredRecords(changes.UpdateNew)...)
	change.Deletions = append(change.Deletions, p.newFilteredRecords(changes.UpdateOld)...)

	change.Deletions = append(change.Deletions, p.newFilteredRecords(changes.Delete)...)

	return p.submitChange(change)
}

// newFilteredRecords returns a collection of RecordSets based on the given endpoints and domainFilter.
func (p *GoogleProvider) newFilteredRecords(endpoints []*endpoint.Endpoint) []*dns.ResourceRecordSet {
	records := []*dns.ResourceRecordSet{}

	for _, endpoint := range endpoints {
		if p.domainFilter.Match(endpoint.DNSName) {
			records = append(records, newRecord(endpoint))
		}
	}

	return records
}

// submitChange takes a zone and a Change and sends it to Google.
func (p *GoogleProvider) submitChange(change *dns.Change) error {
	if len(change.Additions) == 0 && len(change.Deletions) == 0 {
		log.Info("All records are already up to date")
		return nil
	}

	zones, err := p.Zones()
	if err != nil {
		return err
	}

	// separate into per-zone change sets to be passed to the API.
	changes := separateChange(zones, change)

	for z, c := range changes {
		log.Infof("Change zone: %v", z)
		for _, del := range c.Deletions {
			log.Infof("Del records: %s %s %s %d", del.Name, del.Type, del.Rrdatas, del.Ttl)
		}
		for _, add := range c.Additions {
			log.Infof("Add records: %s %s %s %d", add.Name, add.Type, add.Rrdatas, add.Ttl)
		}
	}

	if p.dryRun {
		return nil
	}

	for z, c := range changes {
		if _, err := p.changesClient.Create(p.project, z, c).Do(); err != nil {
			return err
		}
	}

	return nil
}

// separateChange separates a multi-zone change into a single change per zone.
func separateChange(zones map[string]*dns.ManagedZone, change *dns.Change) map[string]*dns.Change {
	changes := make(map[string]*dns.Change)
	zoneNameIDMapper := zoneIDName{}
	for _, z := range zones {
		zoneNameIDMapper[z.Name] = z.DnsName
		changes[z.Name] = &dns.Change{
			Additions: []*dns.ResourceRecordSet{},
			Deletions: []*dns.ResourceRecordSet{},
		}
	}
	for _, a := range change.Additions {
		if zoneName, _ := zoneNameIDMapper.FindZone(ensureTrailingDot(a.Name)); zoneName != "" {
			changes[zoneName].Additions = append(changes[zoneName].Additions, a)
		} else {
			log.Warnf("No matching zone for record addition: %s %s %s %d", a.Name, a.Type, a.Rrdatas, a.Ttl)
		}
	}

	for _, d := range change.Deletions {
		if zoneName, _ := zoneNameIDMapper.FindZone(ensureTrailingDot(d.Name)); zoneName != "" {
			changes[zoneName].Deletions = append(changes[zoneName].Deletions, d)
		} else {
			log.Warnf("No matching zone for record deletion: %s %s %s %d", d.Name, d.Type, d.Rrdatas, d.Ttl)
		}
	}

	// separating a change could lead to empty sub changes, remove them here.
	for zone, change := range changes {
		if len(change.Additions) == 0 && len(change.Deletions) == 0 {
			delete(changes, zone)
		}
	}

	return changes
}

// newRecord returns a RecordSet based on the given endpoint.
func newRecord(ep *endpoint.Endpoint) *dns.ResourceRecordSet {
	// TODO(linki): works around appending a trailing dot to TXT records. I think
	// we should go back to storing DNS names with a trailing dot internally. This
	// way we can use it has is here and trim it off if it exists when necessary.
	targets := make([]string, len(ep.Targets))
	copy(targets, []string(ep.Targets))
	if ep.RecordType == endpoint.RecordTypeCNAME {
		targets[0] = ensureTrailingDot(targets[0])
	}

	// no annotation results in a Ttl of 0, default to 300 for backwards-compatability
	var ttl int64 = googleRecordTTL
	if ep.RecordTTL.IsConfigured() {
		ttl = int64(ep.RecordTTL)
	}

	return &dns.ResourceRecordSet{
		Name:    ensureTrailingDot(ep.DNSName),
		Rrdatas: targets,
		Ttl:     ttl,
		Type:    ep.RecordType,
	}
}
<|endoftext|>


<commit_before>package pd1

import (
	"encoding/binary"
	"fmt"

	"github.com/jwilder/encoding/simple8b"
)

type Int64Encoder interface {
	Write(v int64)
	Bytes() ([]byte, error)
}

type Int64Decoder interface {
	Next() bool
	Read() int64
}

type int64Encoder struct {
	values []uint64
}

func NewInt64Encoder() Int64Encoder {
	return &int64Encoder{}
}

func (e *int64Encoder) Write(v int64) {
	e.values = append(e.values, ZigZagEncode(v))
}

func (e *int64Encoder) Bytes() ([]byte, error) {
	for _, v := range e.values {
		// Value is too large to encode using packed format
		if v > simple8b.MaxValue {
			return e.encodeUncompressed()
		}
	}

	return e.encodePacked()
}

func (e *int64Encoder) encodePacked() ([]byte, error) {
	encoded, err := simple8b.Encode(e.values)
	if err != nil {
		return nil, err
	}

	b := make([]byte, 1+len(encoded)*8)
	// 4 high bits of first byte store the encoding type for the block
	b[0] = byte(EncodingPackedSimple) << 4

	for i, v := range encoded {
		binary.BigEndian.PutUint64(b[1+i*8:1+i*8+8], v)
	}
	return b, nil
}

func (e *int64Encoder) encodeUncompressed() ([]byte, error) {
	b := make([]byte, 1+len(e.values)*8)
	// 4 high bits of first byte store the encoding type for the block
	b[0] = byte(EncodingUncompressed) << 4

	for i, v := range e.values {
		binary.BigEndian.PutUint64(b[1+i*8:1+i*8+8], v)
	}
	return b, nil
}

type int64Decoder struct {
	values []uint64
	bytes  []byte
	i      int
	n      int

	encoding byte
}

func NewInt64Decoder(b []byte) Int64Decoder {
	d := &int64Decoder{
		values: make([]uint64, 240),
	}

	d.SetBytes(b)
	return d
}

func (d *int64Decoder) SetBytes(b []byte) {
	if len(b) > 0 {
		d.encoding = b[0] >> 4
		d.bytes = b[1:]
	}
	d.i = 0
	d.n = 0
}

func (d *int64Decoder) Next() bool {
	if d.i >= d.n && len(d.bytes) == 0 {
		return false
	}

	d.i += 1

	if d.i >= d.n {
		switch d.encoding {
		case EncodingUncompressed:
			d.decodeUncompressed()
		case EncodingPackedSimple:
			d.decodePacked()
		default:
			panic(fmt.Sprintf("unknown encoding %v", d.encoding))
		}
	}
	return d.i < d.n
}

func (d *int64Decoder) Read() int64 {
	return ZigZagDecode(d.values[d.i])
}

func (d *int64Decoder) decodePacked() {
	if len(d.bytes) == 0 {
		return
	}

	v := binary.BigEndian.Uint64(d.bytes[0:8])
	n, _ := simple8b.DecodeSingle(d.values, v)

	d.n = n
	d.i = 0
	d.bytes = d.bytes[8:]
}

func (d *int64Decoder) decodeUncompressed() {
	d.values[0] = binary.BigEndian.Uint64(d.bytes[0:8])
	d.i = 0
	d.n = 1
	d.bytes = d.bytes[8:]
}
<commit_msg>Update simple8b api usage<commit_after>package pd1

import (
	"encoding/binary"
	"fmt"

	"github.com/jwilder/encoding/simple8b"
)

type Int64Encoder interface {
	Write(v int64)
	Bytes() ([]byte, error)
}

type Int64Decoder interface {
	Next() bool
	Read() int64
}

type int64Encoder struct {
	values []uint64
}

func NewInt64Encoder() Int64Encoder {
	return &int64Encoder{}
}

func (e *int64Encoder) Write(v int64) {
	e.values = append(e.values, ZigZagEncode(v))
}

func (e *int64Encoder) Bytes() ([]byte, error) {
	for _, v := range e.values {
		// Value is too large to encode using packed format
		if v > simple8b.MaxValue {
			return e.encodeUncompressed()
		}
	}

	return e.encodePacked()
}

func (e *int64Encoder) encodePacked() ([]byte, error) {
	encoded, err := simple8b.EncodeAll(e.values)
	if err != nil {
		return nil, err
	}

	b := make([]byte, 1+len(encoded)*8)
	// 4 high bits of first byte store the encoding type for the block
	b[0] = byte(EncodingPackedSimple) << 4

	for i, v := range encoded {
		binary.BigEndian.PutUint64(b[1+i*8:1+i*8+8], v)
	}
	return b, nil
}

func (e *int64Encoder) encodeUncompressed() ([]byte, error) {
	b := make([]byte, 1+len(e.values)*8)
	// 4 high bits of first byte store the encoding type for the block
	b[0] = byte(EncodingUncompressed) << 4

	for i, v := range e.values {
		binary.BigEndian.PutUint64(b[1+i*8:1+i*8+8], v)
	}
	return b, nil
}

type int64Decoder struct {
	values []uint64
	bytes  []byte
	i      int
	n      int

	encoding byte
}

func NewInt64Decoder(b []byte) Int64Decoder {
	d := &int64Decoder{
		values: make([]uint64, 240),
	}

	d.SetBytes(b)
	return d
}

func (d *int64Decoder) SetBytes(b []byte) {
	if len(b) > 0 {
		d.encoding = b[0] >> 4
		d.bytes = b[1:]
	}
	d.i = 0
	d.n = 0
}

func (d *int64Decoder) Next() bool {
	if d.i >= d.n && len(d.bytes) == 0 {
		return false
	}

	d.i += 1

	if d.i >= d.n {
		switch d.encoding {
		case EncodingUncompressed:
			d.decodeUncompressed()
		case EncodingPackedSimple:
			d.decodePacked()
		default:
			panic(fmt.Sprintf("unknown encoding %v", d.encoding))
		}
	}
	return d.i < d.n
}

func (d *int64Decoder) Read() int64 {
	return ZigZagDecode(d.values[d.i])
}

func (d *int64Decoder) decodePacked() {
	if len(d.bytes) == 0 {
		return
	}

	v := binary.BigEndian.Uint64(d.bytes[0:8])
	n, _ := simple8b.Decode(d.values, v)

	d.n = n
	d.i = 0
	d.bytes = d.bytes[8:]
}

func (d *int64Decoder) decodeUncompressed() {
	d.values[0] = binary.BigEndian.Uint64(d.bytes[0:8])
	d.i = 0
	d.n = 1
	d.bytes = d.bytes[8:]
}
<|endoftext|>


<commit_before>package ActiveHttpProxy

import (
	"fmt"
	"net/rpc"
	//	"net/rpc"
	log "RollLoger"
	"StoppableListener"
	"net"
)

type ClusterChannle struct {
	Port     string
	sl       *StoppableListener.StoppableListener
	Partners []string
}

func (p *ClusterChannle) FlushLog(req *string, res *string) error {
	fmt.Println("FlushLog")
	*res = "ok"
	return nil
}

func (p *ClusterChannle) Reload(req *string, res *string) error {
	fmt.Println("Reload")
	*res = "ok"
	return nil
}

func (p *ClusterChannle) CallPartner(cmd string) {

	address, err := net.ResolveTCPAddr("tcp", "192.168.1.129:11123")
	if err != nil {
		panic(err)
	}

	conn, _ := net.DialTCP("tcp", nil, address)
	defer conn.Close()

	client := rpc.NewClient(conn)
	defer client.Close()

	var res string
	err = client.Call("ClusterChannle.FlushLog", "haha", &res)
	if err != nil {
		t.Fatal(err)
	} else {
		t.Log(res)
	}

}

func (p *ClusterChannle) Begin() {

	go func() {
		rpc.Register(p)

		originalListener, err := net.Listen("tcp", ":"+p.Port)
		if err != nil {
			log.Fatal(err)
		}
		log.Debug("start clusterChannle tcp ", p.Port)
		p.sl, err = StoppableListener.New(originalListener)
		if err != nil {
			log.Fatal(err)
		}

		for {
			conn, err := p.sl.Accept()
			if err != nil {
				log.Error("rpc accept error ", err.Error())
				return
			}
			go rpc.ServeConn(conn)
		}
	}()

}

func (p *ClusterChannle) Stop() {
	p.sl.Stop()
}
<commit_msg>修订编译错误<commit_after>package ActiveHttpProxy

import (
	"fmt"
	"net/rpc"
	//	"net/rpc"
	log "RollLoger"
	"StoppableListener"
	"net"
)

type ClusterChannle struct {
	Port     string
	sl       *StoppableListener.StoppableListener
	Partners []string
}

func (p *ClusterChannle) FlushLog(req *string, res *string) error {
	fmt.Println("FlushLog")
	*res = "ok"
	return nil
}

func (p *ClusterChannle) Reload(req *string, res *string) error {
	fmt.Println("Reload")
	*res = "ok"
	return nil
}

func (p *ClusterChannle) CallPartner(cmd string) {

	//	address, err := net.ResolveTCPAddr("tcp", "192.168.1.129:11123")
	//	if err != nil {
	//		panic(err)
	//	}

	//	conn, _ := net.DialTCP("tcp", nil, address)
	//	defer conn.Close()

	//	client := rpc.NewClient(conn)
	//	defer client.Close()

	//	var res string
	//	err = client.Call("ClusterChannle.FlushLog", "haha", &res)
	//	if err != nil {
	//		t.Fatal(err)
	//	} else {
	//		t.Log(res)
	//	}

}

func (p *ClusterChannle) Begin() {

	go func() {
		rpc.Register(p)

		originalListener, err := net.Listen("tcp", ":"+p.Port)
		if err != nil {
			log.Fatal(err)
		}
		log.Debug("start clusterChannle tcp ", p.Port)
		p.sl, err = StoppableListener.New(originalListener)
		if err != nil {
			log.Fatal(err)
		}

		for {
			conn, err := p.sl.Accept()
			if err != nil {
				log.Error("rpc accept error ", err.Error())
				return
			}
			go rpc.ServeConn(conn)
		}
	}()

}

func (p *ClusterChannle) Stop() {
	p.sl.Stop()
}
<|endoftext|>


<commit_before>package dnsimple

import (
	"fmt"
	"strconv"
)

type RecordResponse struct {
	Record Record `json:"record"`
}

// Record is used to represent a retrieved Record. All properties
// are set as strings.
type Record struct {
	Name       string `json:"name"`
	Content    string `json:"content"`
	DomainId   int64  `json:"domain_id"`
	Id         int64  `json:"id"`
	Prio       int64  `json:"prio"`
	RecordType string `json:"record_type"`
	Ttl        int64  `json:"ttl"`
}

// Returns the domain id
func (r *Record) StringDomainId() string {
	return strconv.FormatInt(r.DomainId, 10)
}

// Returns the id
func (r *Record) StringId() string {
	return strconv.FormatInt(r.Id, 10)
}

// Returns the string for prio
func (r *Record) StringPrio() string {
	return strconv.FormatInt(r.Prio, 10)
}

// Returns the string for Locked
func (r *Record) StringTtl() string {
	return strconv.FormatInt(r.Ttl, 10)
}

// ChangeRecord contains the request parameters to create or update a
// record.
type ChangeRecord struct {
	Name  string // name of the record
	Value string // where the record points
	Type  string // type, i.e a, mx
	Ttl   string // TTL of record
}

// CreateRecord creates a record from the parameters specified and
// returns an error if it fails. If no error and an ID is returned,
// the Record was succesfully created.
func (c *Client) CreateRecord(domain string, opts *ChangeRecord) (string, error) {
	// Make the request parameters
	params := make(map[string]interface{})

	params["name"] = opts.Name
	params["record_type"] = opts.Type
	params["content"] = opts.Value

	if opts.Ttl != "" {
		ttl, err := strconv.ParseInt(opts.Ttl, 0, 0)
		if err != nil {
			return "", nil
		}
		params["ttl"] = ttl
	}

	endpoint := fmt.Sprintf("/domains/%s/records", domain)

	req, err := c.NewRequest(params, "POST", endpoint)
	if err != nil {
		return "", err
	}

	resp, err := checkResp(c.Http.Do(req))
	if err != nil {
		return "", fmt.Errorf("Error creating record: %s", err)
	}

	record := new(RecordResponse)

	err = decodeBody(resp, &record)

	if err != nil {
		return "", fmt.Errorf("Error parsing record response: %s", err)
	}

	// The request was successful
	return record.Record.StringId(), nil
}

// UpdateRecord updated a record from the parameters specified and
// returns an error if it fails.
func (c *Client) UpdateRecord(domain string, id string, opts *ChangeRecord) (string, error) {
	// Make the request parameters
	params := make(map[string]interface{})

	if opts.Name != "" {
		params["name"] = opts.Name
	}

	if opts.Type != "" {
		params["record_type"] = opts.Type
	}

	if opts.Value != "" {
		params["content"] = opts.Value
	}

	if opts.Ttl != "" {
		ttl, err := strconv.ParseInt(opts.Ttl, 0, 0)
		if err != nil {
			return "", nil
		}
		params["ttl"] = ttl
	}

	endpoint := fmt.Sprintf("/domains/%s/records/%s", domain, id)

	req, err := c.NewRequest(params, "PUT", endpoint)
	if err != nil {
		return "", err
	}

	resp, err := checkResp(c.Http.Do(req))
	if err != nil {
		return "", fmt.Errorf("Error updating record: %s", err)
	}

	record := new(RecordResponse)

	err = decodeBody(resp, &record)

	if err != nil {
		return "", fmt.Errorf("Error parsing record response: %s", err)
	}

	// The request was successful
	return record.Record.StringId(), nil
}

// DestroyRecord destroys a record by the ID specified and
// returns an error if it fails. If no error is returned,
// the Record was succesfully destroyed.
func (c *Client) DestroyRecord(domain string, id string) error {
	var body map[string]interface{}
	req, err := c.NewRequest(body, "DELETE", fmt.Sprintf("/domains/%s/records/%s", domain, id))

	if err != nil {
		return err
	}

	_, err = checkResp(c.Http.Do(req))

	if err != nil {
		return fmt.Errorf("Error destroying record: %s", err)
	}

	// The request was successful
	return nil
}

// RetrieveRecord gets  a record by the ID specified and
// returns a Record and an error. An error will be returned for failed
// requests with a nil Record.
func (c *Client) RetrieveRecord(domain string, id string) (*Record, error) {
	var body map[string]interface{}
	req, err := c.NewRequest(body, "GET", fmt.Sprintf("/domains/%s/records/%s", domain, id))
	if err != nil {
		return nil, err
	}

	resp, err := checkResp(c.Http.Do(req))
	if err != nil {
		return nil, fmt.Errorf("Error retrieving record: %s", err)
	}

	recordResp := RecordResponse{}

	err = decodeBody(resp, &recordResp)

	if err != nil {
		return nil, fmt.Errorf("Error decoding record response: %s", err)
	}

	// The request was successful
	return &recordResp.Record, nil
}

// GetRecords retrieves all the records for the given domain.
func (c *Client) GetRecords(domain string) ([]Record, error) {
	req, err := c.NewRequest(nil, "GET", "/domains/"+domain+"/records")
	if err != nil {
		return nil, err
	}
	resp, err := c.Http.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	recordResponses := make([]RecordResponse, 10)
	err = decode(resp.Body, &recordResponses)
	if err != nil {
		return nil, err
	}
	records := make([]Record, len(recordResponses))
	for i, rr := range recordResponses {
		records[i] = rr.Record
	}
	return records, nil
}
<commit_msg>Return the parse error if parsing of the TTL parameter failed<commit_after>package dnsimple

import (
	"fmt"
	"strconv"
)

type RecordResponse struct {
	Record Record `json:"record"`
}

// Record is used to represent a retrieved Record. All properties
// are set as strings.
type Record struct {
	Name       string `json:"name"`
	Content    string `json:"content"`
	DomainId   int64  `json:"domain_id"`
	Id         int64  `json:"id"`
	Prio       int64  `json:"prio"`
	RecordType string `json:"record_type"`
	Ttl        int64  `json:"ttl"`
}

// Returns the domain id
func (r *Record) StringDomainId() string {
	return strconv.FormatInt(r.DomainId, 10)
}

// Returns the id
func (r *Record) StringId() string {
	return strconv.FormatInt(r.Id, 10)
}

// Returns the string for prio
func (r *Record) StringPrio() string {
	return strconv.FormatInt(r.Prio, 10)
}

// Returns the string for Locked
func (r *Record) StringTtl() string {
	return strconv.FormatInt(r.Ttl, 10)
}

// ChangeRecord contains the request parameters to create or update a
// record.
type ChangeRecord struct {
	Name  string // name of the record
	Value string // where the record points
	Type  string // type, i.e a, mx
	Ttl   string // TTL of record
}

// CreateRecord creates a record from the parameters specified and
// returns an error if it fails. If no error and an ID is returned,
// the Record was succesfully created.
func (c *Client) CreateRecord(domain string, opts *ChangeRecord) (string, error) {
	// Make the request parameters
	params := make(map[string]interface{})

	params["name"] = opts.Name
	params["record_type"] = opts.Type
	params["content"] = opts.Value

	if opts.Ttl != "" {
		ttl, err := strconv.ParseInt(opts.Ttl, 0, 0)
		if err != nil {
			return "", fmt.Errorf("Error parsing ttl: %s", err.Error())
		}
		params["ttl"] = ttl
	}

	endpoint := fmt.Sprintf("/domains/%s/records", domain)

	req, err := c.NewRequest(params, "POST", endpoint)
	if err != nil {
		return "", err
	}

	resp, err := checkResp(c.Http.Do(req))
	if err != nil {
		return "", fmt.Errorf("Error creating record: %s", err)
	}

	record := new(RecordResponse)

	err = decodeBody(resp, &record)

	if err != nil {
		return "", fmt.Errorf("Error parsing record response: %s", err)
	}

	// The request was successful
	return record.Record.StringId(), nil
}

// UpdateRecord updated a record from the parameters specified and
// returns an error if it fails.
func (c *Client) UpdateRecord(domain string, id string, opts *ChangeRecord) (string, error) {
	// Make the request parameters
	params := make(map[string]interface{})

	if opts.Name != "" {
		params["name"] = opts.Name
	}

	if opts.Type != "" {
		params["record_type"] = opts.Type
	}

	if opts.Value != "" {
		params["content"] = opts.Value
	}

	if opts.Ttl != "" {
		ttl, err := strconv.ParseInt(opts.Ttl, 0, 0)
		if err != nil {
			return "", fmt.Errorf("Error parsing ttl: %s", err.Error())
		}
		params["ttl"] = ttl
	}

	endpoint := fmt.Sprintf("/domains/%s/records/%s", domain, id)

	req, err := c.NewRequest(params, "PUT", endpoint)
	if err != nil {
		return "", err
	}

	resp, err := checkResp(c.Http.Do(req))
	if err != nil {
		return "", fmt.Errorf("Error updating record: %s", err)
	}

	record := new(RecordResponse)

	err = decodeBody(resp, &record)

	if err != nil {
		return "", fmt.Errorf("Error parsing record response: %s", err)
	}

	// The request was successful
	return record.Record.StringId(), nil
}

// DestroyRecord destroys a record by the ID specified and
// returns an error if it fails. If no error is returned,
// the Record was succesfully destroyed.
func (c *Client) DestroyRecord(domain string, id string) error {
	var body map[string]interface{}
	req, err := c.NewRequest(body, "DELETE", fmt.Sprintf("/domains/%s/records/%s", domain, id))

	if err != nil {
		return err
	}

	_, err = checkResp(c.Http.Do(req))

	if err != nil {
		return fmt.Errorf("Error destroying record: %s", err)
	}

	// The request was successful
	return nil
}

// RetrieveRecord gets  a record by the ID specified and
// returns a Record and an error. An error will be returned for failed
// requests with a nil Record.
func (c *Client) RetrieveRecord(domain string, id string) (*Record, error) {
	var body map[string]interface{}
	req, err := c.NewRequest(body, "GET", fmt.Sprintf("/domains/%s/records/%s", domain, id))
	if err != nil {
		return nil, err
	}

	resp, err := checkResp(c.Http.Do(req))
	if err != nil {
		return nil, fmt.Errorf("Error retrieving record: %s", err)
	}

	recordResp := RecordResponse{}

	err = decodeBody(resp, &recordResp)

	if err != nil {
		return nil, fmt.Errorf("Error decoding record response: %s", err)
	}

	// The request was successful
	return &recordResp.Record, nil
}

// GetRecords retrieves all the records for the given domain.
func (c *Client) GetRecords(domain string) ([]Record, error) {
	req, err := c.NewRequest(nil, "GET", "/domains/"+domain+"/records")
	if err != nil {
		return nil, err
	}
	resp, err := c.Http.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	recordResponses := make([]RecordResponse, 10)
	err = decode(resp.Body, &recordResponses)
	if err != nil {
		return nil, err
	}
	records := make([]Record, len(recordResponses))
	for i, rr := range recordResponses {
		records[i] = rr.Record
	}
	return records, nil
}
<|endoftext|>