<commit_before>package doit

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"text/tabwriter"

	"github.com/digitalocean/godo"
)

const (
	// NSRoot is a configuration key that signifies this value is at the root.
	NSRoot = "doit"
)

// DisplayOutput displays an object or group of objects to a user. It
// checks to see what the output type should be.
func DisplayOutput(item interface{}, out io.Writer) error {
	output := DoitConfig.GetString(NSRoot, "output")
	if output == "" {
		output = "text"
	}

	switch output {
	case "json":
		return writeJSON(item, out)
	case "text":
		return writeText(item, out)
	default:
		return fmt.Errorf("unknown output type")
	}
}

func writeJSON(item interface{}, w io.Writer) error {
	b, err := json.Marshal(item)
	if err != nil {
		return err
	}

	var out bytes.Buffer
	json.Indent(&out, b, "", "  ")
	_, err = out.WriteTo(w)
	return err
}

func writeText(item interface{}, w io.Writer) error {
	switch i := item.(type) {
	case *godo.Account:
		a := item.(*godo.Account)
		outputAccount(a, w)
	case *godo.Action:
		outputActions([]godo.Action{*i}, w)
	case []godo.Action:
		outputActions(i, w)
	case *godo.Domain:
		outputZone(item.(*godo.Domain), w)
	case []godo.Domain:
		outputDomains(i, w)
	case *godo.DomainRecord:
		outputRecords([]godo.DomainRecord{*i}, w)
	case []godo.DomainRecord:
		outputRecords(i, w)
	case *godo.Droplet:
		outputDroplets([]godo.Droplet{*i}, w)
	case []godo.Droplet:
		outputDroplets(i, w)
	case *godo.Image:
		outputImages([]godo.Image{*i}, w)
	case []godo.Image:
		outputImages(i, w)
	case *godo.Kernel:
		outputKernels([]godo.Kernel{*i}, w)
	case []godo.Kernel:
		outputKernels(i, w)
	case *godo.Key:
		outputSSHKeys([]godo.Key{*i}, w)
	case []godo.Key:
		outputSSHKeys(i, w)

	case []godo.Region:
		outputRegions(i, w)
	case []godo.Size:
		outputSizes(i, w)

	case *godo.FloatingIP:
		outputFloatingIPs([]godo.FloatingIP{*i}, w)
	case []godo.FloatingIP:
		outputFloatingIPs(i, w)

	default:
		panic(fmt.Sprintf("no mapping for %T", item))
	}

	return nil
}

func outputAccount(account *godo.Account, out io.Writer) {
	w := new(tabwriter.Writer)
	w.Init(out, 0, 8, 1, '\t', 0)

	fmt.Fprintln(w, "Email\tDroplet Limit\tEmail Verified\tUUID\t")
	fmt.Fprintf(w, "")
	fmt.Fprintf(w, "%s\t%d\t%t\t%s\t\n", account.Email, account.DropletLimit, account.EmailVerified, account.UUID)
	fmt.Fprintln(w)
	w.Flush()
}

func outputActions(list []godo.Action, out io.Writer) {
	w := new(tabwriter.Writer)
	w.Init(out, 0, 8, 1, '\t', 0)

	fmt.Fprintln(w, "ID\tStatus\tType\tStarted At\tCompleted At\tResource ID\tResource Type\tRegion")

	for _, a := range list {
		fmt.Fprintf(w, "")
		fmt.Fprintf(w, "%d\t%s\t%s\t%s\t%s\t%d\t%s\t%s\n",
			a.ID, a.Status, a.Type, a.StartedAt, a.CompletedAt, a.ResourceID, a.ResourceType, a.RegionSlug)
	}
	fmt.Fprintln(w)
	w.Flush()
}

func outputDroplets(list []godo.Droplet, out io.Writer) {
	w := new(tabwriter.Writer)
	w.Init(out, 0, 8, 1, '\t', 0)

	fmt.Fprintln(w, "ID\tName\tPublic IPv4\tMemory\tVCPUs\tDisk\tRegion\tImage\tStatus")

	for _, d := range list {
		ip := extractDropletPublicIP(&d)
		image := fmt.Sprintf("%s %s", d.Image.Distribution, d.Image.Name)
		fmt.Fprintf(w, "%d\t%s\t%s\t%d\t%d\t%d\t%s\t%s\t%s\n",
			d.ID, d.Name, ip, d.Memory, d.Vcpus, d.Disk, d.Region.Slug, image, d.Status)
	}
	fmt.Fprintln(w)
	w.Flush()
}

func outputFloatingIPs(list []godo.FloatingIP, out io.Writer) {
	w := new(tabwriter.Writer)
	w.Init(out, 0, 4*4, 1, '\t', 0)

	fmt.Fprintln(w, "IP\tRegion\tDroplet")
	for _, ip := range list {
		var droplet string
		if ip.Droplet != nil {
			droplet = fmt.Sprintf("%d", ip.Droplet.ID)
		}
		fmt.Fprintf(w, "%s\t%s\t%s\n", ip.IP, ip.Region.Slug, droplet)
	}

	fmt.Fprintln(w)
	w.Flush()
}

func outputImages(list []godo.Image, out io.Writer) {
	w := new(tabwriter.Writer)
	w.Init(out, 0, 8, 1, '\t', 0)

	fmt.Fprintln(w, "ID\tName\tType\tDistribution\tSlug\tPublic\tMin Disk")

	for _, i := range list {
		publicStatus := false
		if i.Public {
			publicStatus = true
		}

		fmt.Fprintf(w, "%d\t%s\t%s\t%s\t%s\t%t\t%d\n",
			i.ID, i.Name, i.Type, i.Distribution, i.Slug, publicStatus, i.MinDiskSize)

	}
	fmt.Fprintln(w)
	w.Flush()
}

func outputKernels(list []godo.Kernel, out io.Writer) {
	w := new(tabwriter.Writer)
	w.Init(out, 0, 8, 1, '\t', 0)

	fmt.Fprintln(w, "ID\tName\tVersion")

	for _, k := range list {
		fmt.Fprintf(w, "%d\t%s\t%s\n", k.ID, k.Name, k.Version)
	}
	fmt.Fprintln(w)
	w.Flush()
}

func outputRegions(list []godo.Region, out io.Writer) {
	w := new(tabwriter.Writer)
	w.Init(out, 0, 8, 1, '\t', 0)

	fmt.Fprintln(w, "Slug\tName\tAvailable")

	for _, r := range list {
		fmt.Fprintf(w, "%s\t%s\t%t\n", r.Slug, r.Name, r.Available)
	}
	fmt.Fprintln(w)
	w.Flush()
}

func outputSizes(list []godo.Size, out io.Writer) {
	w := new(tabwriter.Writer)
	w.Init(out, 0, 8, 1, '\t', 0)

	fmt.Fprintln(w, "Slug\tMemory\tVcpus\tDisk\tPrice Monthly\tPrice Hourly")

	for _, s := range list {
		fmt.Fprintf(w, "%s\t%d\t%d\t%d\t%0.2f\t%f\n",
			s.Slug, s.Memory, s.Vcpus, s.Disk, s.PriceMonthly, s.PriceHourly)
	}
	fmt.Fprintln(w)
	w.Flush()
}

func outputSSHKeys(list []godo.Key, out io.Writer) {
	w := new(tabwriter.Writer)
	w.Init(out, 0, 8, 1, '\t', 0)

	fmt.Fprintln(w, "ID\tName\tFingerprint")

	for _, s := range list {
		fmt.Fprintf(w, "%d\t%s\t%s\n",
			s.ID, s.Name, s.Fingerprint)
	}
	fmt.Fprintln(w)
	w.Flush()
}

func outputZone(domain *godo.Domain, out io.Writer) {
	fmt.Fprintln(out, domain.ZoneFile)
}

func outputDomains(list []godo.Domain, out io.Writer) {
	w := new(tabwriter.Writer)
	w.Init(out, 0, 8, 1, '\t', 0)

	fmt.Fprintln(w, "Name")

	for _, d := range list {
		fmt.Fprintf(w, "%s\n", d.Name)
	}

	fmt.Fprintln(w)
	w.Flush()
}

func outputRecords(list []godo.DomainRecord, out io.Writer) {
	w := new(tabwriter.Writer)
	w.Init(out, 0, 8, 1, '\t', 0)

	fmt.Fprintln(w, "ID\tType\tName\tData\tPriority\tPort\tWeight")

	for _, d := range list {
		fmt.Fprintf(w, "%d\t%s\t%s\t%s\t%d\t%d\t%d\n", d.ID, d.Type, d.Name, d.Data,
			d.Priority, d.Port, d.Weight)
	}

	fmt.Fprintln(w)
	w.Flush()
}
<commit_msg>Add status to account list<commit_after>package doit

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"text/tabwriter"

	"github.com/digitalocean/godo"
)

const (
	// NSRoot is a configuration key that signifies this value is at the root.
	NSRoot = "doit"
)

// DisplayOutput displays an object or group of objects to a user. It
// checks to see what the output type should be.
func DisplayOutput(item interface{}, out io.Writer) error {
	output := DoitConfig.GetString(NSRoot, "output")
	if output == "" {
		output = "text"
	}

	switch output {
	case "json":
		return writeJSON(item, out)
	case "text":
		return writeText(item, out)
	default:
		return fmt.Errorf("unknown output type")
	}
}

func writeJSON(item interface{}, w io.Writer) error {
	b, err := json.Marshal(item)
	if err != nil {
		return err
	}

	var out bytes.Buffer
	json.Indent(&out, b, "", "  ")
	_, err = out.WriteTo(w)
	return err
}

func writeText(item interface{}, w io.Writer) error {
	switch i := item.(type) {
	case *godo.Account:
		a := item.(*godo.Account)
		outputAccount(a, w)
	case *godo.Action:
		outputActions([]godo.Action{*i}, w)
	case []godo.Action:
		outputActions(i, w)
	case *godo.Domain:
		outputZone(item.(*godo.Domain), w)
	case []godo.Domain:
		outputDomains(i, w)
	case *godo.DomainRecord:
		outputRecords([]godo.DomainRecord{*i}, w)
	case []godo.DomainRecord:
		outputRecords(i, w)
	case *godo.Droplet:
		outputDroplets([]godo.Droplet{*i}, w)
	case []godo.Droplet:
		outputDroplets(i, w)
	case *godo.Image:
		outputImages([]godo.Image{*i}, w)
	case []godo.Image:
		outputImages(i, w)
	case *godo.Kernel:
		outputKernels([]godo.Kernel{*i}, w)
	case []godo.Kernel:
		outputKernels(i, w)
	case *godo.Key:
		outputSSHKeys([]godo.Key{*i}, w)
	case []godo.Key:
		outputSSHKeys(i, w)

	case []godo.Region:
		outputRegions(i, w)
	case []godo.Size:
		outputSizes(i, w)

	case *godo.FloatingIP:
		outputFloatingIPs([]godo.FloatingIP{*i}, w)
	case []godo.FloatingIP:
		outputFloatingIPs(i, w)

	default:
		panic(fmt.Sprintf("no mapping for %T", item))
	}

	return nil
}

func outputAccount(account *godo.Account, out io.Writer) {
	w := new(tabwriter.Writer)
	w.Init(out, 0, 8, 1, '\t', 0)

	fmt.Fprintln(w, "Email\tDroplet Limit\tEmail Verified\tUUID\tStatus")
	fmt.Fprintf(w, "")
	fmt.Fprintf(w, "%s\t%d\t%t\t%s\t%s\n", account.Email, account.DropletLimit, account.EmailVerified, account.UUID, account.Status)
	fmt.Fprintln(w)
	w.Flush()
}

func outputActions(list []godo.Action, out io.Writer) {
	w := new(tabwriter.Writer)
	w.Init(out, 0, 8, 1, '\t', 0)

	fmt.Fprintln(w, "ID\tStatus\tType\tStarted At\tCompleted At\tResource ID\tResource Type\tRegion")

	for _, a := range list {
		fmt.Fprintf(w, "")
		fmt.Fprintf(w, "%d\t%s\t%s\t%s\t%s\t%d\t%s\t%s\n",
			a.ID, a.Status, a.Type, a.StartedAt, a.CompletedAt, a.ResourceID, a.ResourceType, a.RegionSlug)
	}
	fmt.Fprintln(w)
	w.Flush()
}

func outputDroplets(list []godo.Droplet, out io.Writer) {
	w := new(tabwriter.Writer)
	w.Init(out, 0, 8, 1, '\t', 0)

	fmt.Fprintln(w, "ID\tName\tPublic IPv4\tMemory\tVCPUs\tDisk\tRegion\tImage\tStatus")

	for _, d := range list {
		ip := extractDropletPublicIP(&d)
		image := fmt.Sprintf("%s %s", d.Image.Distribution, d.Image.Name)
		fmt.Fprintf(w, "%d\t%s\t%s\t%d\t%d\t%d\t%s\t%s\t%s\n",
			d.ID, d.Name, ip, d.Memory, d.Vcpus, d.Disk, d.Region.Slug, image, d.Status)
	}
	fmt.Fprintln(w)
	w.Flush()
}

func outputFloatingIPs(list []godo.FloatingIP, out io.Writer) {
	w := new(tabwriter.Writer)
	w.Init(out, 0, 4*4, 1, '\t', 0)

	fmt.Fprintln(w, "IP\tRegion\tDroplet")
	for _, ip := range list {
		var droplet string
		if ip.Droplet != nil {
			droplet = fmt.Sprintf("%d", ip.Droplet.ID)
		}
		fmt.Fprintf(w, "%s\t%s\t%s\n", ip.IP, ip.Region.Slug, droplet)
	}

	fmt.Fprintln(w)
	w.Flush()
}

func outputImages(list []godo.Image, out io.Writer) {
	w := new(tabwriter.Writer)
	w.Init(out, 0, 8, 1, '\t', 0)

	fmt.Fprintln(w, "ID\tName\tType\tDistribution\tSlug\tPublic\tMin Disk")

	for _, i := range list {
		publicStatus := false
		if i.Public {
			publicStatus = true
		}

		fmt.Fprintf(w, "%d\t%s\t%s\t%s\t%s\t%t\t%d\n",
			i.ID, i.Name, i.Type, i.Distribution, i.Slug, publicStatus, i.MinDiskSize)

	}
	fmt.Fprintln(w)
	w.Flush()
}

func outputKernels(list []godo.Kernel, out io.Writer) {
	w := new(tabwriter.Writer)
	w.Init(out, 0, 8, 1, '\t', 0)

	fmt.Fprintln(w, "ID\tName\tVersion")

	for _, k := range list {
		fmt.Fprintf(w, "%d\t%s\t%s\n", k.ID, k.Name, k.Version)
	}
	fmt.Fprintln(w)
	w.Flush()
}

func outputRegions(list []godo.Region, out io.Writer) {
	w := new(tabwriter.Writer)
	w.Init(out, 0, 8, 1, '\t', 0)

	fmt.Fprintln(w, "Slug\tName\tAvailable")

	for _, r := range list {
		fmt.Fprintf(w, "%s\t%s\t%t\n", r.Slug, r.Name, r.Available)
	}
	fmt.Fprintln(w)
	w.Flush()
}

func outputSizes(list []godo.Size, out io.Writer) {
	w := new(tabwriter.Writer)
	w.Init(out, 0, 8, 1, '\t', 0)

	fmt.Fprintln(w, "Slug\tMemory\tVcpus\tDisk\tPrice Monthly\tPrice Hourly")

	for _, s := range list {
		fmt.Fprintf(w, "%s\t%d\t%d\t%d\t%0.2f\t%f\n",
			s.Slug, s.Memory, s.Vcpus, s.Disk, s.PriceMonthly, s.PriceHourly)
	}
	fmt.Fprintln(w)
	w.Flush()
}

func outputSSHKeys(list []godo.Key, out io.Writer) {
	w := new(tabwriter.Writer)
	w.Init(out, 0, 8, 1, '\t', 0)

	fmt.Fprintln(w, "ID\tName\tFingerprint")

	for _, s := range list {
		fmt.Fprintf(w, "%d\t%s\t%s\n",
			s.ID, s.Name, s.Fingerprint)
	}
	fmt.Fprintln(w)
	w.Flush()
}

func outputZone(domain *godo.Domain, out io.Writer) {
	fmt.Fprintln(out, domain.ZoneFile)
}

func outputDomains(list []godo.Domain, out io.Writer) {
	w := new(tabwriter.Writer)
	w.Init(out, 0, 8, 1, '\t', 0)

	fmt.Fprintln(w, "Name")

	for _, d := range list {
		fmt.Fprintf(w, "%s\n", d.Name)
	}

	fmt.Fprintln(w)
	w.Flush()
}

func outputRecords(list []godo.DomainRecord, out io.Writer) {
	w := new(tabwriter.Writer)
	w.Init(out, 0, 8, 1, '\t', 0)

	fmt.Fprintln(w, "ID\tType\tName\tData\tPriority\tPort\tWeight")

	for _, d := range list {
		fmt.Fprintf(w, "%d\t%s\t%s\t%s\t%d\t%d\t%d\n", d.ID, d.Type, d.Name, d.Data,
			d.Priority, d.Port, d.Weight)
	}

	fmt.Fprintln(w)
	w.Flush()
}
<|endoftext|>


<commit_before>// Copyright 2016 Google Inc. All Rights Reserved.
//
// 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.

// Binary dnsmasq_exporter is a Prometheus exporter for dnsmasq statistics.
package main

import (
	"bufio"
	"flag"
	"fmt"
	"log"
	"net/http"
	"os"
	"strconv"

	"golang.org/x/sync/errgroup"

	"github.com/miekg/dns"
	"github.com/prometheus/client_golang/prometheus"
	"github.com/prometheus/client_golang/prometheus/promhttp"
)

var (
	listen = flag.String("listen",
		"localhost:9153",
		"listen address")

	leasesPath = flag.String("leases_path",
		"/var/lib/misc/dnsmasq.leases",
		"path to the dnsmasq leases file")

	dnsmasqAddr = flag.String("dnsmasq",
		"localhost:53",
		"dnsmasq host:port address")
	metricsPath = flag.String("metrics_path",
		"/metrics",
		"path under which metrics are served")
)

var (
	// floatMetrics contains prometheus Gauges, keyed by the stats DNS record
	// they correspond to.
	floatMetrics = map[string]prometheus.Gauge{
		"cachesize.bind.": prometheus.NewGauge(prometheus.GaugeOpts{
			Name: "dnsmasq_cachesize",
			Help: "configured size of the DNS cache",
		}),

		"insertions.bind.": prometheus.NewGauge(prometheus.GaugeOpts{
			Name: "dnsmasq_insertions",
			Help: "DNS cache insertions",
		}),

		"evictions.bind.": prometheus.NewGauge(prometheus.GaugeOpts{
			Name: "dnsmasq_evictions",
			Help: "DNS cache exictions: numbers of entries which replaced an unexpired cache entry",
		}),

		"misses.bind.": prometheus.NewGauge(prometheus.GaugeOpts{
			Name: "dnsmasq_misses",
			Help: "DNS cache misses: queries which had to be forwarded",
		}),

		"hits.bind.": prometheus.NewGauge(prometheus.GaugeOpts{
			Name: "dnsmasq_hits",
			Help: "DNS queries answered locally (cache hits)",
		}),

		"auth.bind.": prometheus.NewGauge(prometheus.GaugeOpts{
			Name: "dnsmasq_auth",
			Help: "DNS queries for authoritative zones",
		}),
	}

	leases = prometheus.NewGauge(prometheus.GaugeOpts{
		Name: "dnsmasq_leases",
		Help: "Number of DHCP leases handed out",
	})
)

func init() {
	for _, g := range floatMetrics {
		prometheus.MustRegister(g)
	}
	prometheus.MustRegister(leases)
}

// From https://manpages.debian.org/stretch/dnsmasq-base/dnsmasq.8.en.html:
// The cache statistics are also available in the DNS as answers to queries of
// class CHAOS and type TXT in domain bind. The domain names are cachesize.bind,
// insertions.bind, evictions.bind, misses.bind, hits.bind, auth.bind and
// servers.bind. An example command to query this, using the dig utility would
// be:
//     dig +short chaos txt cachesize.bind

type server struct {
	promHandler http.Handler
	dnsClient   *dns.Client
	dnsmasqAddr string
	leasesPath  string
}

func (s *server) metrics(w http.ResponseWriter, r *http.Request) {
	var eg errgroup.Group

	eg.Go(func() error {
		msg := &dns.Msg{
			MsgHdr: dns.MsgHdr{
				Id:               dns.Id(),
				RecursionDesired: true,
			},
			Question: []dns.Question{
				dns.Question{"cachesize.bind.", dns.TypeTXT, dns.ClassCHAOS},
				dns.Question{"insertions.bind.", dns.TypeTXT, dns.ClassCHAOS},
				dns.Question{"evictions.bind.", dns.TypeTXT, dns.ClassCHAOS},
				dns.Question{"misses.bind.", dns.TypeTXT, dns.ClassCHAOS},
				dns.Question{"hits.bind.", dns.TypeTXT, dns.ClassCHAOS},
				dns.Question{"auth.bind.", dns.TypeTXT, dns.ClassCHAOS},
				dns.Question{"servers.bind.", dns.TypeTXT, dns.ClassCHAOS},
			},
		}
		in, _, err := s.dnsClient.Exchange(msg, s.dnsmasqAddr)
		if err != nil {
			return err
		}
		for _, a := range in.Answer {
			txt, ok := a.(*dns.TXT)
			if !ok {
				continue
			}
			switch txt.Hdr.Name {
			case "servers.bind.":
				// TODO: parse <server> <successes> <errors>, also with multiple upstreams
			default:
				g, ok := floatMetrics[txt.Hdr.Name]
				if !ok {
					continue // ignore unexpected answer from dnsmasq
				}
				if got, want := len(txt.Txt), 1; got != want {
					return fmt.Errorf("stats DNS record %q: unexpected number of replies: got %d, want %d", txt.Hdr.Name, got, want)
				}
				f, err := strconv.ParseFloat(txt.Txt[0], 64)
				if err != nil {
					return err
				}
				g.Set(f)
			}
		}
		return nil
	})

	eg.Go(func() error {
		f, err := os.Open(s.leasesPath)
		if err != nil {
			return err
		}
		defer f.Close()
		scanner := bufio.NewScanner(f)
		var lines float64
		for scanner.Scan() {
			lines++
		}
		if err := scanner.Err(); err != nil {
			return err
		}
		leases.Set(lines)
		return nil
	})

	if err := eg.Wait(); err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	s.promHandler.ServeHTTP(w, r)
}

func main() {
	flag.Parse()
	s := &server{
		promHandler: promhttp.Handler(),
		dnsClient: &dns.Client{
			SingleInflight: true,
		},
		dnsmasqAddr: *dnsmasqAddr,
		leasesPath:  *leasesPath,
	}
	http.HandleFunc(*metricsPath, s.metrics)
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		w.Write([]byte(`
			<title>Dnsmasq Exporter</title>

Dnsmasq Exporter

<a href="` + *metricsPath + `">Metrics

			`))
	})
	log.Fatal(http.ListenAndServe(*listen, nil))
}
<commit_msg>add log output and use the prometheus log library<commit_after>// Copyright 2016 Google Inc. All Rights Reserved.
//
// 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.

// Binary dnsmasq_exporter is a Prometheus exporter for dnsmasq statistics.
package main

import (
	"bufio"
	"flag"
	"fmt"
	"net/http"
	"os"
	"strconv"

	"golang.org/x/sync/errgroup"

	"github.com/miekg/dns"
	"github.com/prometheus/client_golang/prometheus"
	"github.com/prometheus/client_golang/prometheus/promhttp"
	"github.com/prometheus/common/log"
)

var (
	listen = flag.String("listen",
		"localhost:9153",
		"listen address")

	leasesPath = flag.String("leases_path",
		"/var/lib/misc/dnsmasq.leases",
		"path to the dnsmasq leases file")

	dnsmasqAddr = flag.String("dnsmasq",
		"localhost:53",
		"dnsmasq host:port address")
	metricsPath = flag.String("metrics_path",
		"/metrics",
		"path under which metrics are served")
)

var (
	// floatMetrics contains prometheus Gauges, keyed by the stats DNS record
	// they correspond to.
	floatMetrics = map[string]prometheus.Gauge{
		"cachesize.bind.": prometheus.NewGauge(prometheus.GaugeOpts{
			Name: "dnsmasq_cachesize",
			Help: "configured size of the DNS cache",
		}),

		"insertions.bind.": prometheus.NewGauge(prometheus.GaugeOpts{
			Name: "dnsmasq_insertions",
			Help: "DNS cache insertions",
		}),

		"evictions.bind.": prometheus.NewGauge(prometheus.GaugeOpts{
			Name: "dnsmasq_evictions",
			Help: "DNS cache exictions: numbers of entries which replaced an unexpired cache entry",
		}),

		"misses.bind.": prometheus.NewGauge(prometheus.GaugeOpts{
			Name: "dnsmasq_misses",
			Help: "DNS cache misses: queries which had to be forwarded",
		}),

		"hits.bind.": prometheus.NewGauge(prometheus.GaugeOpts{
			Name: "dnsmasq_hits",
			Help: "DNS queries answered locally (cache hits)",
		}),

		"auth.bind.": prometheus.NewGauge(prometheus.GaugeOpts{
			Name: "dnsmasq_auth",
			Help: "DNS queries for authoritative zones",
		}),
	}

	leases = prometheus.NewGauge(prometheus.GaugeOpts{
		Name: "dnsmasq_leases",
		Help: "Number of DHCP leases handed out",
	})
)

func init() {
	for _, g := range floatMetrics {
		prometheus.MustRegister(g)
	}
	prometheus.MustRegister(leases)
}

// From https://manpages.debian.org/stretch/dnsmasq-base/dnsmasq.8.en.html:
// The cache statistics are also available in the DNS as answers to queries of
// class CHAOS and type TXT in domain bind. The domain names are cachesize.bind,
// insertions.bind, evictions.bind, misses.bind, hits.bind, auth.bind and
// servers.bind. An example command to query this, using the dig utility would
// be:
//     dig +short chaos txt cachesize.bind

type server struct {
	promHandler http.Handler
	dnsClient   *dns.Client
	dnsmasqAddr string
	leasesPath  string
}

func (s *server) metrics(w http.ResponseWriter, r *http.Request) {
	var eg errgroup.Group

	eg.Go(func() error {
		msg := &dns.Msg{
			MsgHdr: dns.MsgHdr{
				Id:               dns.Id(),
				RecursionDesired: true,
			},
			Question: []dns.Question{
				dns.Question{"cachesize.bind.", dns.TypeTXT, dns.ClassCHAOS},
				dns.Question{"insertions.bind.", dns.TypeTXT, dns.ClassCHAOS},
				dns.Question{"evictions.bind.", dns.TypeTXT, dns.ClassCHAOS},
				dns.Question{"misses.bind.", dns.TypeTXT, dns.ClassCHAOS},
				dns.Question{"hits.bind.", dns.TypeTXT, dns.ClassCHAOS},
				dns.Question{"auth.bind.", dns.TypeTXT, dns.ClassCHAOS},
				dns.Question{"servers.bind.", dns.TypeTXT, dns.ClassCHAOS},
			},
		}
		in, _, err := s.dnsClient.Exchange(msg, s.dnsmasqAddr)
		if err != nil {
			return err
		}
		for _, a := range in.Answer {
			txt, ok := a.(*dns.TXT)
			if !ok {
				continue
			}
			switch txt.Hdr.Name {
			case "servers.bind.":
				// TODO: parse <server> <successes> <errors>, also with multiple upstreams
			default:
				g, ok := floatMetrics[txt.Hdr.Name]
				if !ok {
					continue // ignore unexpected answer from dnsmasq
				}
				if got, want := len(txt.Txt), 1; got != want {
					return fmt.Errorf("stats DNS record %q: unexpected number of replies: got %d, want %d", txt.Hdr.Name, got, want)
				}
				f, err := strconv.ParseFloat(txt.Txt[0], 64)
				if err != nil {
					return err
				}
				g.Set(f)
			}
		}
		return nil
	})

	eg.Go(func() error {
		f, err := os.Open(s.leasesPath)
		if err != nil {
			log.Warnln("could not open leases file:", err)
			return err
		}
		defer f.Close()
		scanner := bufio.NewScanner(f)
		var lines float64
		for scanner.Scan() {
			lines++
		}
		if err := scanner.Err(); err != nil {
			return err
		}
		leases.Set(lines)
		return nil
	})

	if err := eg.Wait(); err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	s.promHandler.ServeHTTP(w, r)
}

func main() {
	flag.Parse()
	s := &server{
		promHandler: promhttp.Handler(),
		dnsClient: &dns.Client{
			SingleInflight: true,
		},
		dnsmasqAddr: *dnsmasqAddr,
		leasesPath:  *leasesPath,
	}
	http.HandleFunc(*metricsPath, s.metrics)
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		w.Write([]byte(`
			<title>Dnsmasq Exporter</title>

Dnsmasq Exporter

<a href="` + *metricsPath + `">Metrics

			`))
	})
	log.Infoln("Listening on", *listen)
	log.Infoln("Serving metrics under", *metricsPath)
	log.Fatal(http.ListenAndServe(*listen, nil))
}
<|endoftext|>


<commit_before>package digitalocean

import (
	"fmt"
	"strconv"
)

type DropletResponse struct {
	Droplet Droplet `json:"droplet"`
}

// Droplet is used to represent a retrieved Droplet. All properties
// are set as strings.
type Droplet struct {
	Id       int64                               `json:"id"`
	Name     string                              `json:"name"`
	Region   map[string]interface{}              `json:"region"`
	Image    map[string]interface{}              `json:"image"`
	SizeSlug string                              `json:"size_slug"`
	Locked   bool                                `json:"locked"`
	Status   string                              `json:"status"`
	Networks map[string][]map[string]interface{} `json:"networks"`
}

// Returns the slug for the region
func (d *Droplet) RegionSlug() string {
	if _, ok := d.Region["slug"]; ok {
		return d.Region["slug"].(string)
	}

	return ""
}

// Returns the slug for the region
func (d *Droplet) StringId() string {
	return strconv.FormatInt(d.Id, 10)
}

// Returns the string for Locked
func (d *Droplet) IsLocked() string {
	return strconv.FormatBool(d.Locked)
}

// Returns the slug for the image
func (d *Droplet) ImageSlug() string {
	if _, ok := d.Image["slug"]; ok {
		if attr, ok := d.Image["slug"].(string); ok {
			return attr
		}
	}

	return ""
}

func (d *Droplet) ImageId() string {
	if _, ok := d.Image["id"]; ok {
		if attr, ok := d.Image["id"].(float64); ok {
			return strconv.FormatFloat(attr, 'f', 0, 64)
		}
	}

	return ""
}

// Returns the ipv4 address
func (d *Droplet) IPV4Address(addressType string) string {
	if _, ok := d.Networks["v4"]; ok {
		for _, v := range d.Networks["v4"] {
			if v["type"].(string) == addressType {
				return v["ip_address"].(string)
			}
		}
	}
	return ""
}

// Returns the ipv6 adddress
func (d *Droplet) IPV6Address(addressType string) string {
	if _, ok := d.Networks["v6"]; ok {
		for _, v := range d.Networks["v6"] {
			if v["type"].(string) == addressType {
				return v["ip_address"].(string)
			}
		}
	}
	return ""
}

// Currently DO only has a network type per droplet,
// so we just takes ipv4s
func (d *Droplet) NetworkingType() string {
	if d.IPV4Address("private") != "" {
		return "private"
	} else {
		return "public"
	}
}

// CreateDroplet contains the request parameters to create a new
// droplet.
type CreateDroplet struct {
	Name              string   `json:"name"`               // Name of the droplet
	Region            string   `json:"region"`             // Slug of the region to create the droplet in
	Size              string   `json:"size"`               // Slug of the size to use for the droplet
	Image             string   `json:"image"`              // Slug of the image, if using a public image
	SSHKeys           []string `json:"ssh_keys"`           // Array of SSH Key IDs that should be added
	Backups           string   `json:"backups"`            // 'true' or 'false' if backups are enabled
	IPV6              string   `json:"ipv6"`               // 'true' or 'false' if IPV6 is enabled
	PrivateNetworking string   `json:"private_networking"` // 'true' or 'false' if Private Networking is enabled
	UserData          string   `json:"user_data"`          // metadata for the droplet
}

// CreateDroplet creates a droplet from the parameters specified and
// returns an error if it fails. If no error and an ID is returned,
// the Droplet was succesfully created.
func (c *Client) CreateDroplet(opts *CreateDroplet) (string, error) {
	req, err := c.NewRequest(opts, "POST", "/droplets")

	if err != nil {
		return "", err
	}

	resp, err := checkResp(c.Http.Do(req))

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

	droplet := new(DropletResponse)

	err = decodeBody(resp, &droplet)

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

	// The request was successful
	return droplet.Droplet.StringId(), nil
}

// DestroyDroplet destroys a droplet by the ID specified and
// returns an error if it fails. If no error is returned,
// the Droplet was succesfully destroyed.
func (c *Client) DestroyDroplet(id string) error {
	req, err := c.NewRequest(nil, "DELETE", fmt.Sprintf("/droplets/%s", id))

	if err != nil {
		return err
	}

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

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

	// The request was successful
	return nil
}

// RetrieveDroplet gets  a droplet by the ID specified and
// returns a Droplet and an error. An error will be returned for failed
// requests with a nil Droplet.
func (c *Client) RetrieveDroplet(id string) (Droplet, error) {
	req, err := c.NewRequest(nil, "GET", fmt.Sprintf("/droplets/%s", id))

	if err != nil {
		return Droplet{}, err
	}

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

	droplet := new(DropletResponse)

	err = decodeBody(resp, droplet)

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

	// The request was successful
	return droplet.Droplet, nil
}

// Action sends the specified action to the droplet. An error
// is retunred, and is nil if successful
func (c *Client) Action(id string, action map[string]interface{}) error {
	req, err := c.NewRequest(action, "POST", fmt.Sprintf("/droplets/%s/actions", id))

	if err != nil {
		return err
	}

	_, err = checkResp(c.Http.Do(req))
	if err != nil {
		return fmt.Errorf("Error processing droplet action: %s", err)
	}

	// The request was successful
	return nil
}

// Resizes a droplet to the size slug specified
func (c *Client) Resize(id string, size string) error {
	return c.Action(id, map[string]interface{}{
		"type": "resize",
		"size": size,
	})
}

// Renames a droplet to the name specified
func (c *Client) Rename(id string, name string) error {
	return c.Action(id, map[string]interface{}{
		"type": "rename",
		"name": name,
	})
}

// Enables IPV6 on the droplet
func (c *Client) EnableIPV6s(id string) error {
	return c.Action(id, map[string]interface{}{
		"type": "enable_ipv6",
	})
}

// Enables private networking on the droplet
func (c *Client) EnablePrivateNetworking(id string) error {
	return c.Action(id, map[string]interface{}{
		"type": "enable_private_networking",
	})
}

// Resizes a droplet to the size slug specified
func (c *Client) PowerOff(id string) error {
	return c.Action(id, map[string]interface{}{
		"type": "power_off",
	})
}

// Resizes a droplet to the size slug specified
func (c *Client) PowerOn(id string) error {
	return c.Action(id, map[string]interface{}{
		"type": "power_on",
	})
}
<commit_msg>droplet: omitempty droplet JSON<commit_after>package digitalocean

import (
	"fmt"
	"strconv"
)

type DropletResponse struct {
	Droplet Droplet `json:"droplet"`
}

// Droplet is used to represent a retrieved Droplet. All properties
// are set as strings.
type Droplet struct {
	Id       int64                               `json:"id"`
	Name     string                              `json:"name"`
	Region   map[string]interface{}              `json:"region"`
	Image    map[string]interface{}              `json:"image"`
	SizeSlug string                              `json:"size_slug"`
	Locked   bool                                `json:"locked"`
	Status   string                              `json:"status"`
	Networks map[string][]map[string]interface{} `json:"networks"`
}

// Returns the slug for the region
func (d *Droplet) RegionSlug() string {
	if _, ok := d.Region["slug"]; ok {
		return d.Region["slug"].(string)
	}

	return ""
}

// Returns the slug for the region
func (d *Droplet) StringId() string {
	return strconv.FormatInt(d.Id, 10)
}

// Returns the string for Locked
func (d *Droplet) IsLocked() string {
	return strconv.FormatBool(d.Locked)
}

// Returns the slug for the image
func (d *Droplet) ImageSlug() string {
	if _, ok := d.Image["slug"]; ok {
		if attr, ok := d.Image["slug"].(string); ok {
			return attr
		}
	}

	return ""
}

func (d *Droplet) ImageId() string {
	if _, ok := d.Image["id"]; ok {
		if attr, ok := d.Image["id"].(float64); ok {
			return strconv.FormatFloat(attr, 'f', 0, 64)
		}
	}

	return ""
}

// Returns the ipv4 address
func (d *Droplet) IPV4Address(addressType string) string {
	if _, ok := d.Networks["v4"]; ok {
		for _, v := range d.Networks["v4"] {
			if v["type"].(string) == addressType {
				return v["ip_address"].(string)
			}
		}
	}
	return ""
}

// Returns the ipv6 adddress
func (d *Droplet) IPV6Address(addressType string) string {
	if _, ok := d.Networks["v6"]; ok {
		for _, v := range d.Networks["v6"] {
			if v["type"].(string) == addressType {
				return v["ip_address"].(string)
			}
		}
	}
	return ""
}

// Currently DO only has a network type per droplet,
// so we just takes ipv4s
func (d *Droplet) NetworkingType() string {
	if d.IPV4Address("private") != "" {
		return "private"
	} else {
		return "public"
	}
}

// CreateDroplet contains the request parameters to create a new
// droplet.
type CreateDroplet struct {
	Name              string   `json:"name,omitempty"`               // Name of the droplet
	Region            string   `json:"region,omitempty"`             // Slug of the region to create the droplet in
	Size              string   `json:"size,omitempty"`               // Slug of the size to use for the droplet
	Image             string   `json:"image,omitempty"`              // Slug of the image, if using a public image
	SSHKeys           []string `json:"ssh_keys,omitempty"`           // Array of SSH Key IDs that should be added
	Backups           string   `json:"backups,omitempty"`            // 'true' or 'false' if backups are enabled
	IPV6              string   `json:"ipv6,omitempty"`               // 'true' or 'false' if IPV6 is enabled
	PrivateNetworking string   `json:"private_networking,omitempty"` // 'true' or 'false' if Private Networking is enabled
	UserData          string   `json:"user_data,omitempty"`          // metadata for the droplet
}

// CreateDroplet creates a droplet from the parameters specified and
// returns an error if it fails. If no error and an ID is returned,
// the Droplet was succesfully created.
func (c *Client) CreateDroplet(opts *CreateDroplet) (string, error) {
	req, err := c.NewRequest(opts, "POST", "/droplets")

	if err != nil {
		return "", err
	}

	resp, err := checkResp(c.Http.Do(req))

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

	droplet := new(DropletResponse)

	err = decodeBody(resp, &droplet)

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

	// The request was successful
	return droplet.Droplet.StringId(), nil
}

// DestroyDroplet destroys a droplet by the ID specified and
// returns an error if it fails. If no error is returned,
// the Droplet was succesfully destroyed.
func (c *Client) DestroyDroplet(id string) error {
	req, err := c.NewRequest(nil, "DELETE", fmt.Sprintf("/droplets/%s", id))

	if err != nil {
		return err
	}

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

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

	// The request was successful
	return nil
}

// RetrieveDroplet gets  a droplet by the ID specified and
// returns a Droplet and an error. An error will be returned for failed
// requests with a nil Droplet.
func (c *Client) RetrieveDroplet(id string) (Droplet, error) {
	req, err := c.NewRequest(nil, "GET", fmt.Sprintf("/droplets/%s", id))

	if err != nil {
		return Droplet{}, err
	}

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

	droplet := new(DropletResponse)

	err = decodeBody(resp, droplet)

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

	// The request was successful
	return droplet.Droplet, nil
}

// Action sends the specified action to the droplet. An error
// is retunred, and is nil if successful
func (c *Client) Action(id string, action map[string]interface{}) error {
	req, err := c.NewRequest(action, "POST", fmt.Sprintf("/droplets/%s/actions", id))

	if err != nil {
		return err
	}

	_, err = checkResp(c.Http.Do(req))
	if err != nil {
		return fmt.Errorf("Error processing droplet action: %s", err)
	}

	// The request was successful
	return nil
}

// Resizes a droplet to the size slug specified
func (c *Client) Resize(id string, size string) error {
	return c.Action(id, map[string]interface{}{
		"type": "resize",
		"size": size,
	})
}

// Renames a droplet to the name specified
func (c *Client) Rename(id string, name string) error {
	return c.Action(id, map[string]interface{}{
		"type": "rename",
		"name": name,
	})
}

// Enables IPV6 on the droplet
func (c *Client) EnableIPV6s(id string) error {
	return c.Action(id, map[string]interface{}{
		"type": "enable_ipv6",
	})
}

// Enables private networking on the droplet
func (c *Client) EnablePrivateNetworking(id string) error {
	return c.Action(id, map[string]interface{}{
		"type": "enable_private_networking",
	})
}

// Resizes a droplet to the size slug specified
func (c *Client) PowerOff(id string) error {
	return c.Action(id, map[string]interface{}{
		"type": "power_off",
	})
}

// Resizes a droplet to the size slug specified
func (c *Client) PowerOn(id string) error {
	return c.Action(id, map[string]interface{}{
		"type": "power_on",
	})
}
<|endoftext|>


<commit_before>package handlers

import (
	"github.com/go-openapi/runtime/middleware"
	coreclusters "github.com/radanalyticsio/oshinko-core/clusters"
	osa "github.com/radanalyticsio/oshinko-rest/helpers/authentication"
	oe "github.com/radanalyticsio/oshinko-rest/helpers/errors"
	"github.com/radanalyticsio/oshinko-rest/helpers/info"
	"github.com/radanalyticsio/oshinko-core/clusterconfigs"
	"github.com/radanalyticsio/oshinko-rest/models"
	apiclusters "github.com/radanalyticsio/oshinko-rest/restapi/operations/clusters"
)

const nameSpaceMsg = "cannot determine target openshift namespace"
const clientMsg = "unable to create an openshift client"

func generalErr(err error, title, msg string, code int32) *models.ErrorResponse {
	if err != nil {
		msg = msg + ", err: " + err.Error()
	}
	return oe.NewSingleErrorResponse(code, title, msg)
}

func tostrptr(val string) *string {
	v := val
	return &v
}

func singleClusterResponse(sc coreclusters.SparkCluster) *models.SingleCluster {

	addpod := func(p coreclusters.SparkPod) *models.ClusterModelPodsItems0 {
		pod := new(models.ClusterModelPodsItems0)
		pod.IP = tostrptr(p.IP)
		pod.Status = tostrptr(p.Status)
		pod.Type = tostrptr(p.Type)
		return pod
	}

	// Build the response
	cluster := &models.SingleCluster{&models.ClusterModel{}}
	cluster.Cluster.Name = tostrptr(sc.Name)
	cluster.Cluster.MasterURL = tostrptr(sc.MasterURL)
	cluster.Cluster.MasterWebURL = tostrptr(sc.MasterWebURL)

	cluster.Cluster.Status = tostrptr(sc.Status)

	cluster.Cluster.Pods = []*models.ClusterModelPodsItems0{}
	for i := range sc.Pods {
		cluster.Cluster.Pods = append(cluster.Cluster.Pods, addpod(sc.Pods[i]))
	}

	cluster.Cluster.Config = &models.NewClusterConfig{
		SparkMasterConfig: sc.Config.SparkMasterConfig,
		SparkWorkerConfig: sc.Config.SparkWorkerConfig,
		MasterCount: int64(sc.Config.MasterCount),
		WorkerCount: int64(sc.Config.WorkerCount),
		Name: sc.Config.Name,
	}
	return cluster
}

func assignConfig(config *models.NewClusterConfig) *clusterconfigs.ClusterConfig {
	if config == nil {
		return nil
	}
	result := &clusterconfigs.ClusterConfig{
		Name: config.Name,
		MasterCount: int(config.MasterCount),
		WorkerCount: int(config.WorkerCount),
		SparkMasterConfig: config.SparkMasterConfig,
		SparkWorkerConfig: config.SparkWorkerConfig,
	}
	return result
}

// CreateClusterResponse create a cluster and return the representation
func CreateClusterResponse(params apiclusters.CreateClusterParams) middleware.Responder {

	// Do this so that we only have to specify the error code when we build ErrorResponse
	reterr := func(err *models.ErrorResponse) *apiclusters.CreateClusterDefault {
		return apiclusters.NewCreateClusterDefault(int(*err.Errors[0].Status)).WithPayload(err)
	}

	// Convenience wrapper for create failure
	fail := func(err error, msg string, code int32) *models.ErrorResponse {
		return generalErr(err, "cannot create cluster", msg, code)
	}

	//code := func(err error) int32 {
	//	if strings.Index(err.Error(), "already exists") != -1 {
	//		return 409
	//	}
	//	return 500
	//}
	const imageMsg = "cannot determine name of spark image"

	clustername := *params.Cluster.Name

	namespace, err := info.GetNamespace()
	if namespace == "" || err != nil {
		return reterr(fail(err, nameSpaceMsg, 500))
	}

	image, err := info.GetSparkImage()
	if image == "" || err != nil {
		return reterr(fail(err, imageMsg, 500))
	}

	client, err := osa.GetKubeClient()
	if err != nil {
		return reterr(fail(err, clientMsg, 500))
	}

	osclient, err := osa.GetOpenShiftClient()
	if err != nil {
		return reterr(fail(err, clientMsg, 500))
	}

	config := assignConfig(params.Cluster.Config)
	sc, err := coreclusters.CreateCluster(clustername, namespace, image, config, osclient, client)
	if err != nil {
		return reterr(fail(err, "", 409))
	}
	return apiclusters.NewCreateClusterCreated().WithLocation(sc.Href).WithPayload(singleClusterResponse(sc))
}

// DeleteClusterResponse delete a cluster
func DeleteClusterResponse(params apiclusters.DeleteSingleClusterParams) middleware.Responder {

	// Do this so that we only have to specify the error code when we build ErrorResponse
	reterr := func(err *models.ErrorResponse) *apiclusters.DeleteSingleClusterDefault {
		return apiclusters.NewDeleteSingleClusterDefault(int(*err.Errors[0].Status)).WithPayload(err)
	}

	// Convenience wrapper for delete failure
	fail := func(err error, msg string, code int32) *models.ErrorResponse {
		return generalErr(err, "cluster deletion failed", msg, code)
	}

	namespace, err := info.GetNamespace()
	if namespace == "" || err != nil {
		return reterr(fail(err, nameSpaceMsg, 500))
	}

	osclient, err := osa.GetOpenShiftClient()
	if err != nil {
		return reterr(fail(err, clientMsg, 500))
	}

	client, err := osa.GetKubeClient()
	if err != nil {
		return reterr(fail(err, clientMsg, 500))
	}

	info, err := coreclusters.DeleteCluster(params.Name, namespace, osclient, client)
	if err != nil {
		return reterr(fail(err, "", 409))
	}
	if info != "" {
		return reterr(fail(nil, "deletion may be incomplete: " + info, 500))
	}
	return apiclusters.NewDeleteSingleClusterNoContent()
}

// FindClustersResponse find a cluster and return its representation
func FindClustersResponse(params apiclusters.FindClustersParams) middleware.Responder {

	// Do this so that we only have to specify the error code when we build ErrorResponse
	reterr := func(err *models.ErrorResponse) *apiclusters.FindClustersDefault {
		return apiclusters.NewFindClustersDefault(int(*err.Errors[0].Status)).WithPayload(err)
	}

	// Convenience wrapper for list failure
	fail := func(err error, msg string, code int32) *models.ErrorResponse {
		return generalErr(err, "cannot list clusters", msg, code)
	}

	namespace, err := info.GetNamespace()
	if namespace == "" || err != nil {
		return reterr(fail(err, nameSpaceMsg, 500))
	}

	client, err := osa.GetKubeClient()
	if err != nil {
		return reterr(fail(err, clientMsg, 500))
	}
	scs, err := coreclusters.FindClusters(namespace, client)
	if err != nil {
		return reterr(fail(err, "", 409))
	}

	// Create the payload that we're going to write into for the response
	payload := apiclusters.FindClustersOKBodyBody{}
	payload.Clusters = []*apiclusters.ClustersItems0{}
	for idx := range(scs) {
		clt := new(apiclusters.ClustersItems0)
		clt.Href = &scs[idx].Href
		clt.MasterURL = &scs[idx].MasterURL
		clt.MasterWebURL = &scs[idx].MasterWebURL
		clt.Name = &scs[idx].Name
		clt.Status = &scs[idx].Status
		wc := int64(scs[idx].WorkerCount)
		clt.WorkerCount = &wc
		payload.Clusters = append(payload.Clusters, clt)
	}

	return apiclusters.NewFindClustersOK().WithPayload(payload)
}

// FindSingleClusterResponse find a cluster and return its representation
func FindSingleClusterResponse(params apiclusters.FindSingleClusterParams) middleware.Responder {

	clustername := params.Name

	// Do this so that we only have to specify the error code when we build ErrorResponse
	reterr := func(err *models.ErrorResponse) *apiclusters.FindSingleClusterDefault {
		return apiclusters.NewFindSingleClusterDefault(int(*err.Errors[0].Status)).WithPayload(err)
	}

	// Convenience wrapper for get failure
	fail := func(err error, msg string, code int32) *models.ErrorResponse {
		return generalErr(err, "Cannot get cluster", msg, code)
	}

	namespace, err := info.GetNamespace()
	if namespace == "" || err != nil {
		return reterr(fail(err, nameSpaceMsg, 500))
	}

	osclient, err := osa.GetOpenShiftClient()
	if err != nil {
		return reterr(fail(err, clientMsg, 500))
	}

	client, err := osa.GetKubeClient()
	if err != nil {
		return reterr(fail(err, clientMsg, 500))
	}

	sc, err := coreclusters.FindSingleCluster(clustername, namespace, osclient, client)
	if err != nil {
		return reterr(fail(err, "", 409))
	}

	return apiclusters.NewFindSingleClusterOK().WithPayload(singleClusterResponse(sc))
}

// UpdateSingleClusterResponse update a cluster and return the new representation
func UpdateSingleClusterResponse(params apiclusters.UpdateSingleClusterParams) middleware.Responder {

	// Do this so that we only have to specify the error code when we build ErrorResponse
	reterr := func(err *models.ErrorResponse) *apiclusters.UpdateSingleClusterDefault {
		return apiclusters.NewUpdateSingleClusterDefault(int(*err.Errors[0].Status)).WithPayload(err)
	}

	// Convenience wrapper for update failure
	fail := func(err error, msg string, code int32) *models.ErrorResponse {
		return generalErr(err, "cannot update cluster", msg, code)
	}

	const clusterNameMsg = "changing the cluster name is not supported"

	clustername := params.Name

	// Before we do further checks, make sure that we have deploymentconfigs
	// If either the master or the worker deploymentconfig are missing, we
	// assume that the cluster is missing. These are the base objects that
	// we use to create a cluster
	namespace, err := info.GetNamespace()
	if namespace == "" || err != nil {
		return reterr(fail(err, nameSpaceMsg, 500))
	}

	osclient, err := osa.GetOpenShiftClient()
	if err != nil {
		return reterr(fail(err, clientMsg, 500))
	}

	client, err := osa.GetKubeClient()
	if err != nil {
		return reterr(fail(err, clientMsg, 500))
	}

	// Simple things first. At this time we do not support cluster name change and
	// we do not suppport scaling the master count (likely need HA setup for that to make sense)
	if clustername != *params.Cluster.Name {
		return reterr(fail(nil, clusterNameMsg, 409))
	}

	config := assignConfig(params.Cluster.Config)
	sc, err := coreclusters.UpdateCluster(clustername, namespace, config, osclient, client)
	if err != nil {
		return reterr(fail(err, "", 409))
	}
	return apiclusters.NewUpdateSingleClusterAccepted().WithPayload(singleClusterResponse(sc))
}
<commit_msg>restoring 404 error on cluster not found<commit_after>package handlers

import (
	"github.com/go-openapi/runtime/middleware"
	coreclusters "github.com/radanalyticsio/oshinko-core/clusters"
	osa "github.com/radanalyticsio/oshinko-rest/helpers/authentication"
	oe "github.com/radanalyticsio/oshinko-rest/helpers/errors"
	"github.com/radanalyticsio/oshinko-rest/helpers/info"
	"github.com/radanalyticsio/oshinko-core/clusterconfigs"
	"github.com/radanalyticsio/oshinko-rest/models"
	apiclusters "github.com/radanalyticsio/oshinko-rest/restapi/operations/clusters"
	"strings"
)

const nameSpaceMsg = "cannot determine target openshift namespace"
const clientMsg = "unable to create an openshift client"

func generalErr(err error, title, msg string, code int32) *models.ErrorResponse {
	if err != nil {
		msg = msg + ", err: " + err.Error()
	}
	return oe.NewSingleErrorResponse(code, title, msg)
}

func tostrptr(val string) *string {
	v := val
	return &v
}

func singleClusterResponse(sc coreclusters.SparkCluster) *models.SingleCluster {

	addpod := func(p coreclusters.SparkPod) *models.ClusterModelPodsItems0 {
		pod := new(models.ClusterModelPodsItems0)
		pod.IP = tostrptr(p.IP)
		pod.Status = tostrptr(p.Status)
		pod.Type = tostrptr(p.Type)
		return pod
	}

	// Build the response
	cluster := &models.SingleCluster{&models.ClusterModel{}}
	cluster.Cluster.Name = tostrptr(sc.Name)
	cluster.Cluster.MasterURL = tostrptr(sc.MasterURL)
	cluster.Cluster.MasterWebURL = tostrptr(sc.MasterWebURL)

	cluster.Cluster.Status = tostrptr(sc.Status)

	cluster.Cluster.Pods = []*models.ClusterModelPodsItems0{}
	for i := range sc.Pods {
		cluster.Cluster.Pods = append(cluster.Cluster.Pods, addpod(sc.Pods[i]))
	}

	cluster.Cluster.Config = &models.NewClusterConfig{
		SparkMasterConfig: sc.Config.SparkMasterConfig,
		SparkWorkerConfig: sc.Config.SparkWorkerConfig,
		MasterCount: int64(sc.Config.MasterCount),
		WorkerCount: int64(sc.Config.WorkerCount),
		Name: sc.Config.Name,
	}
	return cluster
}

func assignConfig(config *models.NewClusterConfig) *clusterconfigs.ClusterConfig {
	if config == nil {
		return nil
	}
	result := &clusterconfigs.ClusterConfig{
		Name: config.Name,
		MasterCount: int(config.MasterCount),
		WorkerCount: int(config.WorkerCount),
		SparkMasterConfig: config.SparkMasterConfig,
		SparkWorkerConfig: config.SparkWorkerConfig,
	}
	return result
}

// CreateClusterResponse create a cluster and return the representation
func CreateClusterResponse(params apiclusters.CreateClusterParams) middleware.Responder {

	// Do this so that we only have to specify the error code when we build ErrorResponse
	reterr := func(err *models.ErrorResponse) *apiclusters.CreateClusterDefault {
		return apiclusters.NewCreateClusterDefault(int(*err.Errors[0].Status)).WithPayload(err)
	}

	// Convenience wrapper for create failure
	fail := func(err error, msg string, code int32) *models.ErrorResponse {
		return generalErr(err, "cannot create cluster", msg, code)
	}

	//code := func(err error) int32 {
	//	if strings.Index(err.Error(), "already exists") != -1 {
	//		return 409
	//	}
	//	return 500
	//}
	const imageMsg = "cannot determine name of spark image"

	clustername := *params.Cluster.Name

	namespace, err := info.GetNamespace()
	if namespace == "" || err != nil {
		return reterr(fail(err, nameSpaceMsg, 500))
	}

	image, err := info.GetSparkImage()
	if image == "" || err != nil {
		return reterr(fail(err, imageMsg, 500))
	}

	client, err := osa.GetKubeClient()
	if err != nil {
		return reterr(fail(err, clientMsg, 500))
	}

	osclient, err := osa.GetOpenShiftClient()
	if err != nil {
		return reterr(fail(err, clientMsg, 500))
	}

	config := assignConfig(params.Cluster.Config)
	sc, err := coreclusters.CreateCluster(clustername, namespace, image, config, osclient, client)
	if err != nil {
		return reterr(fail(err, "", 409))
	}
	return apiclusters.NewCreateClusterCreated().WithLocation(sc.Href).WithPayload(singleClusterResponse(sc))
}

// DeleteClusterResponse delete a cluster
func DeleteClusterResponse(params apiclusters.DeleteSingleClusterParams) middleware.Responder {

	// Do this so that we only have to specify the error code when we build ErrorResponse
	reterr := func(err *models.ErrorResponse) *apiclusters.DeleteSingleClusterDefault {
		return apiclusters.NewDeleteSingleClusterDefault(int(*err.Errors[0].Status)).WithPayload(err)
	}

	// Convenience wrapper for delete failure
	fail := func(err error, msg string, code int32) *models.ErrorResponse {
		return generalErr(err, "cluster deletion failed", msg, code)
	}

	namespace, err := info.GetNamespace()
	if namespace == "" || err != nil {
		return reterr(fail(err, nameSpaceMsg, 500))
	}

	osclient, err := osa.GetOpenShiftClient()
	if err != nil {
		return reterr(fail(err, clientMsg, 500))
	}

	client, err := osa.GetKubeClient()
	if err != nil {
		return reterr(fail(err, clientMsg, 500))
	}

	info, err := coreclusters.DeleteCluster(params.Name, namespace, osclient, client)
	if err != nil && strings.Index(err.Error(), "no such cluster") != -1 {
		return reterr(fail(err, "No such cluster", 404))
	} else if err != nil {
		return reterr(fail(err, "", 409))
	}
	if info != "" {
		return reterr(fail(nil, "deletion may be incomplete: " + info, 500))
	}
	return apiclusters.NewDeleteSingleClusterNoContent()
}

// FindClustersResponse find a cluster and return its representation
func FindClustersResponse(params apiclusters.FindClustersParams) middleware.Responder {

	// Do this so that we only have to specify the error code when we build ErrorResponse
	reterr := func(err *models.ErrorResponse) *apiclusters.FindClustersDefault {
		return apiclusters.NewFindClustersDefault(int(*err.Errors[0].Status)).WithPayload(err)
	}

	// Convenience wrapper for list failure
	fail := func(err error, msg string, code int32) *models.ErrorResponse {
		return generalErr(err, "cannot list clusters", msg, code)
	}

	namespace, err := info.GetNamespace()
	if namespace == "" || err != nil {
		return reterr(fail(err, nameSpaceMsg, 500))
	}

	client, err := osa.GetKubeClient()
	if err != nil {
		return reterr(fail(err, clientMsg, 500))
	}
	scs, err := coreclusters.FindClusters(namespace, client)
	if err != nil {
		return reterr(fail(err, "", 409))
	}

	// Create the payload that we're going to write into for the response
	payload := apiclusters.FindClustersOKBodyBody{}
	payload.Clusters = []*apiclusters.ClustersItems0{}
	for idx := range(scs) {
		clt := new(apiclusters.ClustersItems0)
		clt.Href = &scs[idx].Href
		clt.MasterURL = &scs[idx].MasterURL
		clt.MasterWebURL = &scs[idx].MasterWebURL
		clt.Name = &scs[idx].Name
		clt.Status = &scs[idx].Status
		wc := int64(scs[idx].WorkerCount)
		clt.WorkerCount = &wc
		payload.Clusters = append(payload.Clusters, clt)
	}

	return apiclusters.NewFindClustersOK().WithPayload(payload)
}

// FindSingleClusterResponse find a cluster and return its representation
func FindSingleClusterResponse(params apiclusters.FindSingleClusterParams) middleware.Responder {

	clustername := params.Name

	// Do this so that we only have to specify the error code when we build ErrorResponse
	reterr := func(err *models.ErrorResponse) *apiclusters.FindSingleClusterDefault {
		return apiclusters.NewFindSingleClusterDefault(int(*err.Errors[0].Status)).WithPayload(err)
	}

	// Convenience wrapper for get failure
	fail := func(err error, msg string, code int32) *models.ErrorResponse {
		return generalErr(err, "Cannot get cluster", msg, code)
	}

	namespace, err := info.GetNamespace()
	if namespace == "" || err != nil {
		return reterr(fail(err, nameSpaceMsg, 500))
	}

	osclient, err := osa.GetOpenShiftClient()
	if err != nil {
		return reterr(fail(err, clientMsg, 500))
	}

	client, err := osa.GetKubeClient()
	if err != nil {
		return reterr(fail(err, clientMsg, 500))
	}

	sc, err := coreclusters.FindSingleCluster(clustername, namespace, osclient, client)
	if err != nil && strings.Index(err.Error(), "no such cluster") != -1 {
		return reterr(fail(err, "No such cluster", 404))
	} else if err != nil {
		return reterr(fail(err, "", 409))
	}

	return apiclusters.NewFindSingleClusterOK().WithPayload(singleClusterResponse(sc))
}

// UpdateSingleClusterResponse update a cluster and return the new representation
func UpdateSingleClusterResponse(params apiclusters.UpdateSingleClusterParams) middleware.Responder {

	// Do this so that we only have to specify the error code when we build ErrorResponse
	reterr := func(err *models.ErrorResponse) *apiclusters.UpdateSingleClusterDefault {
		return apiclusters.NewUpdateSingleClusterDefault(int(*err.Errors[0].Status)).WithPayload(err)
	}

	// Convenience wrapper for update failure
	fail := func(err error, msg string, code int32) *models.ErrorResponse {
		return generalErr(err, "cannot update cluster", msg, code)
	}

	const clusterNameMsg = "changing the cluster name is not supported"

	clustername := params.Name

	// Before we do further checks, make sure that we have deploymentconfigs
	// If either the master or the worker deploymentconfig are missing, we
	// assume that the cluster is missing. These are the base objects that
	// we use to create a cluster
	namespace, err := info.GetNamespace()
	if namespace == "" || err != nil {
		return reterr(fail(err, nameSpaceMsg, 500))
	}

	osclient, err := osa.GetOpenShiftClient()
	if err != nil {
		return reterr(fail(err, clientMsg, 500))
	}

	client, err := osa.GetKubeClient()
	if err != nil {
		return reterr(fail(err, clientMsg, 500))
	}

	// Simple things first. At this time we do not support cluster name change and
	// we do not suppport scaling the master count (likely need HA setup for that to make sense)
	if clustername != *params.Cluster.Name {
		return reterr(fail(nil, clusterNameMsg, 409))
	}

	config := assignConfig(params.Cluster.Config)
	sc, err := coreclusters.UpdateCluster(clustername, namespace, config, osclient, client)
	if err != nil && strings.Index(err.Error(), "no such cluster") != -1 {
		return reterr(fail(err, "No such cluster", 404))
	} else if err != nil {
		return reterr(fail(err, "", 409))
	}
	return apiclusters.NewUpdateSingleClusterAccepted().WithPayload(singleClusterResponse(sc))
}
<|endoftext|>


<commit_before>package handlers

import (
	"errors"
	"fmt"
	"net/url"
	"path/filepath"
	"strconv"
	"strings"

	"golang.org/x/net/context"

	libsass "github.com/wellington/go-libsass"
	"github.com/wellington/go-libsass/libs"
	sw "github.com/wellington/spritewell"
	"github.com/wellington/wellington/payload"
)

func init() {
	libsass.RegisterSassFunc("sprite($map, $name, $offsetX: 0px, $offsetY: 0px)", Sprite)
	libsass.RegisterSassFunc("sprite-map($glob, $spacing: 0px)", SpriteMap)
	libsass.RegisterHandler("sprite-file($map, $name)", SpriteFile)
	libsass.RegisterHandler("sprite-position($map, $file)", SpritePosition)
}

// SpritePosition returns the position of the image in the sprite-map.
// This is useful for passing directly to background-position
func SpritePosition(v interface{}, usv libsass.SassValue, rsv *libsass.SassValue) error {
	ctx := v.(*libsass.Context)
	var glob, name string
	err := libsass.Unmarshal(usv, &glob, &name)
	if err != nil {
		return setErrorAndReturn(err, rsv)
	}
	payload, ok := ctx.Payload.(sw.Spriter)
	if !ok {
		return setErrorAndReturn(errors.New("Context payload not found"), rsv)
	}
	sprites := payload.Sprite()
	sprites.RLock()
	defer sprites.RUnlock()
	imgs, ok := sprites.M[glob]
	if !ok {
		err := fmt.Errorf(
			"Variable not found matching glob: %s sprite:%s", glob, name)
		return setErrorAndReturn(err, rsv)
	}

	if imgs.Lookup(name) == -1 {
		return setErrorAndReturn(fmt.Errorf("image %s not found\n"+
			"   try one of these: %v", name, imgs.Paths), rsv)
	}

	// This is an odd name for what it does
	pos := imgs.GetPack(imgs.Lookup(name))

	if err != nil {
		return setErrorAndReturn(err, rsv)
	}

	x := libs.SassNumber{Unit: "px", Value: float64(-pos.X)}
	y := libs.SassNumber{Unit: "px", Value: float64(-pos.Y)}

	str, err := libsass.Marshal(
		[]libs.SassNumber{x, y},
	)

	if err != nil {
		return setErrorAndReturn(err, rsv)
	}
	if rsv != nil {
		*rsv = str
	}

	return nil
}

// SpriteFile proxies the sprite glob and image name through.
func SpriteFile(v interface{}, usv libsass.SassValue, rsv *libsass.SassValue) error {
	var glob, name string
	err := libsass.Unmarshal(usv, &glob, &name)
	if err != nil {
		return setErrorAndReturn(err, rsv)
	}
	infs := []interface{}{glob, name}
	res, err := libsass.Marshal(infs)
	if rsv != nil {
		*rsv = res
	}
	return nil
}

// Sprite returns the source and background position for an image in the
// spritesheet.
func Sprite(ctx context.Context, usv libsass.SassValue) (rsv *libsass.SassValue, err error) {

	comp, err := libsass.CompFromCtx(ctx)
	if err != nil {
		return nil, err
	}

	pather := comp.(libsass.Pather)

	var glob, name string
	var offsetX, offsetY libs.SassNumber
	err = libsass.Unmarshal(usv, &glob, &name, &offsetX, &offsetY)
	if err != nil {
		if err == libsass.ErrSassNumberNoUnit {
			err := fmt.Errorf(
				"Please specify unit for offset ie. (2px)")
			return nil, err
		}
		return nil, err
	}

	loadctx := comp.Payload()

	sprites := payload.Sprite(loadctx)

	imgs := sprites.Get(glob)
	if imgs == nil {
		err := fmt.Errorf(
			"Variable not found matching glob: %s sprite:%s", glob, name)
		return nil, err
	}

	path, err := imgs.OutputPath()
	if err != nil {
		return nil, err
	}

	buildDir := pather.BuildDir()
	genImgDir := pather.GenImgDir()
	httpPath := pather.HTTPPath()

	// FIXME: path directory can not be trusted, rebuild this from the context
	if len(httpPath) == 0 {
		ctxPath, err := filepath.Rel(buildDir, genImgDir)
		if err != nil {
			return nil, err
		}
		path = strings.Join([]string{ctxPath, filepath.Base(path)}, "/")
	} else {
		u, err := url.Parse(httpPath)
		if err != nil {
			return nil, err
		}
		u.Path = strings.Join([]string{u.Path, "build", filepath.Base(path)}, "/")
		path = u.String()
	}
	if err != nil {
		return nil, err
	}

	if imgs.Lookup(name) == -1 {
		return nil, fmt.Errorf("image %s not found\n"+
			"   try one of these: %v", name, imgs.Paths)
	}
	// This is an odd name for what it does
	pos := imgs.GetPack(imgs.Lookup(name))

	if err != nil {
		return nil, err
	}

	x := libs.SassNumber{Unit: "px", Value: float64(-pos.X)}
	x = x.Add(offsetX)

	y := libs.SassNumber{Unit: "px", Value: float64(-pos.Y)}
	y = y.Add(offsetY)

	str, err := libsass.Marshal(
		fmt.Sprintf(`url("%s") %s %s`,
			path, x, y,
		))
	if err != nil {
		return nil, err
	}

	return &str, nil
}

// SpriteMap returns a sprite from the passed glob and sprite
// parameters.
func SpriteMap(mainctx context.Context, usv libsass.SassValue) (*libsass.SassValue, error) {
	var glob string
	var spacing libs.SassNumber
	err := libsass.Unmarshal(usv, &glob, &spacing)
	if err != nil {
		return nil, err
	}
	comp, err := libsass.CompFromCtx(mainctx)
	if err != nil {
		return nil, err
	}
	ctx := comp.Context()
	imgs := sw.New(&sw.Options{
		ImageDir:  ctx.ImageDir,
		BuildDir:  ctx.BuildDir,
		GenImgDir: ctx.GenImgDir,
		Padding:   int(spacing.Value),
	})
	if cglob, err := strconv.Unquote(glob); err == nil {
		glob = cglob
	}

	key := glob + strconv.FormatInt(int64(spacing.Value), 10)

	loadctx := comp.Payload()
	sprites := payload.Sprite(loadctx)

	// FIXME: wtf is this?
	// sprites.RLock()
	// if _, ok := sprites.M[key]; ok {
	// 	defer sprites.RUnlock()
	// 	res, err := libsass.Marshal(key)
	// 	if err != nil {
	// 		return setErrorAndReturn(err, rsv)
	// 	}
	// 	if rsv != nil {
	// 		*rsv = res
	// 	}
	// 	return nil
	// }
	// sprites.RUnlock()

	err = imgs.Decode(glob)
	if err != nil {
		return nil, err
	}

	_, err = imgs.Export()
	if err != nil {
		return nil, err
	}

	res, err := libsass.Marshal(key)
	if err != nil {
		return nil, err
	}

	sprites.Set(key, imgs)

	return &res, nil
}
<commit_msg>refactor position<commit_after>package handlers

import (
	"errors"
	"fmt"
	"net/url"
	"path/filepath"
	"strconv"
	"strings"

	"golang.org/x/net/context"

	libsass "github.com/wellington/go-libsass"
	"github.com/wellington/go-libsass/libs"
	sw "github.com/wellington/spritewell"
	"github.com/wellington/wellington/payload"
)

func init() {
	libsass.RegisterSassFunc("sprite($map, $name, $offsetX: 0px, $offsetY: 0px)", Sprite)
	libsass.RegisterSassFunc("sprite-map($glob, $spacing: 0px)", SpriteMap)
	libsass.RegisterHandler("sprite-file($map, $name)", SpriteFile)
	libsass.RegisterSassFunc("sprite-position($map, $file)", SpritePosition)
}

// SpritePosition returns the position of the image in the sprite-map.
// This is useful for passing directly to background-position
func SpritePosition(mainctx context.Context, usv libsass.SassValue) (rsv *libsass.SassValue, err error) {
	comp, err := libsass.CompFromCtx(mainctx)
	if err != nil {
		return
	}

	var glob, name string
	err = libsass.Unmarshal(usv, &glob, &name)
	if err != nil {
		return
	}

	loadctx := comp.Payload()
	if loadctx == nil {
		err = ErrPayloadNil
		return
	}

	sprites := payload.Sprite(loadctx)
	if sprites == nil {
		err = errors.New("Sprites missing")
		return
	}

	imgs := sprites.Get(glob)
	if imgs == nil {
		err = fmt.Errorf(
			"Variable not found matching glob: %s sprite:%s", glob, name)
		return
	}

	if imgs.Lookup(name) == -1 {
		err = fmt.Errorf("image %s not found\n"+
			"   try one of these: %v", name, imgs.Paths)
		return
	}

	// This is an odd name for what it does
	pos := imgs.GetPack(imgs.Lookup(name))
	if err != nil {
		return
	}

	x := libs.SassNumber{Unit: "px", Value: float64(-pos.X)}
	y := libs.SassNumber{Unit: "px", Value: float64(-pos.Y)}

	str, err := libsass.Marshal(
		[]libs.SassNumber{x, y},
	)
	return &str, err
}

// SpriteFile proxies the sprite glob and image name through.
func SpriteFile(v interface{}, usv libsass.SassValue, rsv *libsass.SassValue) error {
	var glob, name string
	err := libsass.Unmarshal(usv, &glob, &name)
	if err != nil {
		return setErrorAndReturn(err, rsv)
	}
	infs := []interface{}{glob, name}
	res, err := libsass.Marshal(infs)
	if rsv != nil {
		*rsv = res
	}
	return nil
}

// Sprite returns the source and background position for an image in the
// spritesheet.
func Sprite(ctx context.Context, usv libsass.SassValue) (rsv *libsass.SassValue, err error) {

	comp, err := libsass.CompFromCtx(ctx)
	if err != nil {
		return nil, err
	}

	pather := comp.(libsass.Pather)

	var glob, name string
	var offsetX, offsetY libs.SassNumber
	err = libsass.Unmarshal(usv, &glob, &name, &offsetX, &offsetY)
	if err != nil {
		if err == libsass.ErrSassNumberNoUnit {
			err := fmt.Errorf(
				"Please specify unit for offset ie. (2px)")
			return nil, err
		}
		return nil, err
	}

	loadctx := comp.Payload()

	sprites := payload.Sprite(loadctx)

	imgs := sprites.Get(glob)
	if imgs == nil {
		err := fmt.Errorf(
			"Variable not found matching glob: %s sprite:%s", glob, name)
		return nil, err
	}

	path, err := imgs.OutputPath()
	if err != nil {
		return nil, err
	}

	buildDir := pather.BuildDir()
	genImgDir := pather.GenImgDir()
	httpPath := pather.HTTPPath()

	// FIXME: path directory can not be trusted, rebuild this from the context
	if len(httpPath) == 0 {
		ctxPath, err := filepath.Rel(buildDir, genImgDir)
		if err != nil {
			return nil, err
		}
		path = strings.Join([]string{ctxPath, filepath.Base(path)}, "/")
	} else {
		u, err := url.Parse(httpPath)
		if err != nil {
			return nil, err
		}
		u.Path = strings.Join([]string{u.Path, "build", filepath.Base(path)}, "/")
		path = u.String()
	}
	if err != nil {
		return nil, err
	}

	if imgs.Lookup(name) == -1 {
		return nil, fmt.Errorf("image %s not found\n"+
			"   try one of these: %v", name, imgs.Paths)
	}
	// This is an odd name for what it does
	pos := imgs.GetPack(imgs.Lookup(name))

	if err != nil {
		return nil, err
	}

	x := libs.SassNumber{Unit: "px", Value: float64(-pos.X)}
	x = x.Add(offsetX)

	y := libs.SassNumber{Unit: "px", Value: float64(-pos.Y)}
	y = y.Add(offsetY)

	str, err := libsass.Marshal(
		fmt.Sprintf(`url("%s") %s %s`,
			path, x, y,
		))
	if err != nil {
		return nil, err
	}

	return &str, nil
}

// SpriteMap returns a sprite from the passed glob and sprite
// parameters.
func SpriteMap(mainctx context.Context, usv libsass.SassValue) (*libsass.SassValue, error) {
	var glob string
	var spacing libs.SassNumber
	err := libsass.Unmarshal(usv, &glob, &spacing)
	if err != nil {
		return nil, err
	}
	comp, err := libsass.CompFromCtx(mainctx)
	if err != nil {
		return nil, err
	}
	ctx := comp.Context()
	imgs := sw.New(&sw.Options{
		ImageDir:  ctx.ImageDir,
		BuildDir:  ctx.BuildDir,
		GenImgDir: ctx.GenImgDir,
		Padding:   int(spacing.Value),
	})
	if cglob, err := strconv.Unquote(glob); err == nil {
		glob = cglob
	}

	key := glob + strconv.FormatInt(int64(spacing.Value), 10)

	loadctx := comp.Payload()
	sprites := payload.Sprite(loadctx)

	// FIXME: wtf is this?
	// sprites.RLock()
	// if _, ok := sprites.M[key]; ok {
	// 	defer sprites.RUnlock()
	// 	res, err := libsass.Marshal(key)
	// 	if err != nil {
	// 		return setErrorAndReturn(err, rsv)
	// 	}
	// 	if rsv != nil {
	// 		*rsv = res
	// 	}
	// 	return nil
	// }
	// sprites.RUnlock()

	err = imgs.Decode(glob)
	if err != nil {
		return nil, err
	}

	_, err = imgs.Export()
	if err != nil {
		return nil, err
	}

	res, err := libsass.Marshal(key)
	if err != nil {
		return nil, err
	}

	sprites.Set(key, imgs)

	return &res, nil
}
<|endoftext|>


<commit_before>package SimpleMailer

import (
	"fmt"
	"log"
	"net"
	"net/mail"
	"net/smtp"
	"crypto/tls"
	"io/ioutil"
	"strings"
	"time"
)

// function to send an email with TLS and PlainAuth
// this function is ran multiple times in some scripts
func SendEmail(outgoingEmail Outgoing) bool {
		email_html, err := ioutil.ReadFile(configs.EmailsDir + outgoingEmail.Template)
		from := mail.Address{configs.SMTPname, configs.SMTPfrom}
		to := mail.Address{"", outgoingEmail.Email}
		subj := outgoingEmail.Subject
		body := string(email_html)
		headers := make(map[string]string)
		headers["From"] = from.String()
		headers["To"] = to.String()
		headers["Subject"] = subj
		headers["Date"] = time.Now().Format(time.RFC1123Z)

		var message string
		for k, v := range headers {
			message += fmt.Sprintf("%s: %s\r\n", k, v)
		}
		newbody := ReplaceContentText(outgoingEmail.Variables.Inputs, body)
		message += "\n"+newbody
		servername := configs.SMTPhost+":"+configs.SMTPport
		host, _, _ := net.SplitHostPort(servername)
		auth := smtp.PlainAuth("", configs.SMTPuser, configs.SMTPpass, host)
		tlsconfig := &tls.Config{
			InsecureSkipVerify: true,
			ServerName: host,
		}
		conn, err := tls.Dial("tcp", servername, tlsconfig)
		if err != nil {
			return false
			log.Panic(err)
		}
		c, err := smtp.NewClient(conn, host)
		if err != nil {
			return false
			log.Panic(err)
		}
		if err = c.Auth(auth); err != nil {
			return false
			log.Panic(err)
		}
		if err = c.Mail(from.Address); err != nil {
			return false
			log.Panic(err)
		}
		if err = c.Rcpt(to.Address); err != nil {
			return false
			log.Panic(err)
		}
		w, err := c.Data()
		if err != nil {
			return false
			log.Panic(err)
		}
		_, err = w.Write([]byte(message))
		if err != nil {
			return false
			log.Panic(err)
		}
		err = w.Close()
		if err != nil {
			return false
			log.Panic(err)
		}
		c.Quit()
	return true
}

// function to replace variables as: {{USERNAME}} to USERNAME
// inside the html template. input as array of
// {"USERNAME": "gopher"}
func ReplaceContentText(array map[string]interface{}, content string) string {
	var newmeailstring string
	newmeailstring = content
	for k, v := range array {
		toreplace := "{{"+k+"}}"
		newmeailstring = strings.Replace(newmeailstring, toreplace, v.(string), -1)
	}
	return newmeailstring
}
<commit_msg>added from name<commit_after>package SimpleMailer

import (
	"fmt"
	"log"
	"net"
	"net/mail"
	"net/smtp"
	"crypto/tls"
	"io/ioutil"
	"strings"
	"time"
)

// function to send an email with TLS and PlainAuth
// this function is ran multiple times in some scripts
func SendEmail(outgoingEmail Outgoing) bool {
		email_html, err := ioutil.ReadFile(configs.EmailsDir + outgoingEmail.Template)
		from := mail.Address{configs.SMTPname, configs.SMTPfrom}
		to := mail.Address{"", outgoingEmail.Email}
		subj := outgoingEmail.Subject
		body := string(email_html)
		headers := make(map[string]string)
		headers["From"] = from.String()
		headers["To"] = to.String()
		headers["Subject"] = subj
		headers["Date"] = time.Now().Format(time.RFC1123Z)

		var message string
		for k, v := range headers {
			message += fmt.Sprintf("%s: %s\r\n", k, v)
		}
		newbody := ReplaceContentText(outgoingEmail.Variables.Inputs, body)
		message += "\n"+newbody
	fmt.Println(message)
		servername := configs.SMTPhost+":"+configs.SMTPport
		host, _, _ := net.SplitHostPort(servername)
		auth := smtp.PlainAuth("", configs.SMTPuser, configs.SMTPpass, host)
		tlsconfig := &tls.Config{
			InsecureSkipVerify: true,
			ServerName: host,
		}
		conn, err := tls.Dial("tcp", servername, tlsconfig)
		if err != nil {
			return false
			log.Panic(err)
		}
		c, err := smtp.NewClient(conn, host)
		if err != nil {
			return false
			log.Panic(err)
		}
		if err = c.Auth(auth); err != nil {
			return false
			log.Panic(err)
		}
		if err = c.Mail(from.Address); err != nil {
			return false
			log.Panic(err)
		}
		if err = c.Rcpt(to.Address); err != nil {
			return false
			log.Panic(err)
		}
		w, err := c.Data()
		if err != nil {
			return false
			log.Panic(err)
		}
		_, err = w.Write([]byte(message))
		if err != nil {
			return false
			log.Panic(err)
		}
		err = w.Close()
		if err != nil {
			return false
			log.Panic(err)
		}
		c.Quit()
	return true
}

// function to replace variables as: {{USERNAME}} to USERNAME
// inside the html template. input as array of
// {"USERNAME": "gopher"}
func ReplaceContentText(array map[string]interface{}, content string) string {
	var newmeailstring string
	newmeailstring = content
	for k, v := range array {
		toreplace := "{{"+k+"}}"
		newmeailstring = strings.Replace(newmeailstring, toreplace, v.(string), -1)
	}
	return newmeailstring
}
<|endoftext|>


<commit_before>package goka

import (
	"errors"
	"fmt"
	"sync"
)

var (
	// ErrEmitterAlreadyClosed is returned when Emit is called after the emitter has been finished.
	ErrEmitterAlreadyClosed error = errors.New("emitter already closed")
)

// Emitter emits messages into a specific Kafka topic, first encoding the message with the given codec.
type Emitter struct {
	codec    Codec
	producer Producer

	topic string

	wg   sync.WaitGroup
	mu   sync.Mutex
	done chan struct{}
}

// NewEmitter creates a new emitter using passed brokers, topic, codec and possibly options.
func NewEmitter(brokers []string, topic Stream, codec Codec, options ...EmitterOption) (*Emitter, error) {
	options = append(
		// default options comes first
		[]EmitterOption{
			WithEmitterClientID(fmt.Sprintf("goka-emitter-%s", topic)),
		},

		// user-defined options (may overwrite default ones)
		options...,
	)

	opts := new(eoptions)

	opts.applyOptions(topic, codec, options...)

	prod, err := opts.builders.producer(brokers, opts.clientID, opts.hasher)
	if err != nil {
		return nil, fmt.Errorf(errBuildProducer, err)
	}

	return &Emitter{
		codec:    codec,
		producer: prod,
		topic:    string(topic),
		done:     make(chan struct{}),
	}, nil
}

func (e *Emitter) emitDone(err error) { e.wg.Done() }

// EmitWithHeaders sends a message with the given headers for the passed key using the emitter's codec.
func (e *Emitter) EmitWithHeaders(key string, msg interface{}, headers map[string][]byte) (*Promise, error) {
	var (
		err  error
		data []byte
	)

	if msg != nil {
		data, err = e.codec.Encode(msg)
		if err != nil {
			return nil, fmt.Errorf("Error encoding value for key %s in topic %s: %v", key, e.topic, err)
		}
	}

	// protect e.done channel and e.wg WaitGroup together to reject all new emits after calling e.Finish
	e.mu.Lock()
	select {
	case <-e.done:
		e.mu.Unlock()
		return NewPromise().finish(nil, ErrEmitterAlreadyClosed), nil
	default:
		e.wg.Add(1)
		e.mu.Unlock()
	}

	if headers == nil {
		return e.producer.Emit(e.topic, key, data).Then(e.emitDone), nil
	}
	return e.producer.EmitWithHeaders(e.topic, key, data, headers).Then(e.emitDone), nil
}

// Emit sends a message for passed key using the emitter's codec.
func (e *Emitter) Emit(key string, msg interface{}) (*Promise, error) {
	return e.EmitWithHeaders(key, msg, nil)
}

// EmitSyncWithHeaders sends a message with the given headers to passed topic and key.
func (e *Emitter) EmitSyncWithHeaders(key string, msg interface{}, headers map[string][]byte) error {
	var (
		err     error
		promise *Promise
	)
	promise, err = e.EmitWithHeaders(key, msg, headers)

	if err != nil {
		return err
	}

	done := make(chan struct{})
	promise.Then(func(asyncErr error) {
		err = asyncErr
		close(done)
	})
	<-done
	return err
}

// EmitSync sends a message to passed topic and key.
func (e *Emitter) EmitSync(key string, msg interface{}) error {
	return e.EmitSyncWithHeaders(key, msg, nil)
}

// Finish waits until the emitter is finished producing all pending messages.
func (e *Emitter) Finish() error {
	e.mu.Lock()
	close(e.done)
	e.mu.Unlock()
	e.wg.Wait()
	return e.producer.Close()
}
<commit_msg>use an RWLock to allow concurrent emits<commit_after>package goka

import (
	"errors"
	"fmt"
	"sync"
)

var (
	// ErrEmitterAlreadyClosed is returned when Emit is called after the emitter has been finished.
	ErrEmitterAlreadyClosed error = errors.New("emitter already closed")
)

// Emitter emits messages into a specific Kafka topic, first encoding the message with the given codec.
type Emitter struct {
	codec    Codec
	producer Producer

	topic string

	wg   sync.WaitGroup
	mu   sync.RWMutex
	done chan struct{}
}

// NewEmitter creates a new emitter using passed brokers, topic, codec and possibly options.
func NewEmitter(brokers []string, topic Stream, codec Codec, options ...EmitterOption) (*Emitter, error) {
	options = append(
		// default options comes first
		[]EmitterOption{
			WithEmitterClientID(fmt.Sprintf("goka-emitter-%s", topic)),
		},

		// user-defined options (may overwrite default ones)
		options...,
	)

	opts := new(eoptions)

	opts.applyOptions(topic, codec, options...)

	prod, err := opts.builders.producer(brokers, opts.clientID, opts.hasher)
	if err != nil {
		return nil, fmt.Errorf(errBuildProducer, err)
	}

	return &Emitter{
		codec:    codec,
		producer: prod,
		topic:    string(topic),
		done:     make(chan struct{}),
	}, nil
}

func (e *Emitter) emitDone(err error) { e.wg.Done() }

// EmitWithHeaders sends a message with the given headers for the passed key using the emitter's codec.
func (e *Emitter) EmitWithHeaders(key string, msg interface{}, headers map[string][]byte) (*Promise, error) {
	var (
		err  error
		data []byte
	)

	if msg != nil {
		data, err = e.codec.Encode(msg)
		if err != nil {
			return nil, fmt.Errorf("Error encoding value for key %s in topic %s: %v", key, e.topic, err)
		}
	}

	// protect e.done channel and e.wg WaitGroup together to reject all new emits after calling e.Finish
	// wg.Add must not be called after wg.Wait finished
	e.mu.RLock()
	select {
	case <-e.done:
		e.mu.RUnlock()
		return NewPromise().finish(nil, ErrEmitterAlreadyClosed), nil
	default:
		e.wg.Add(1)
		e.mu.RUnlock()
	}

	if headers == nil {
		return e.producer.Emit(e.topic, key, data).Then(e.emitDone), nil
	}
	return e.producer.EmitWithHeaders(e.topic, key, data, headers).Then(e.emitDone), nil
}

// Emit sends a message for passed key using the emitter's codec.
func (e *Emitter) Emit(key string, msg interface{}) (*Promise, error) {
	return e.EmitWithHeaders(key, msg, nil)
}

// EmitSyncWithHeaders sends a message with the given headers to passed topic and key.
func (e *Emitter) EmitSyncWithHeaders(key string, msg interface{}, headers map[string][]byte) error {
	var (
		err     error
		promise *Promise
	)
	promise, err = e.EmitWithHeaders(key, msg, headers)

	if err != nil {
		return err
	}

	done := make(chan struct{})
	promise.Then(func(asyncErr error) {
		err = asyncErr
		close(done)
	})
	<-done
	return err
}

// EmitSync sends a message to passed topic and key.
func (e *Emitter) EmitSync(key string, msg interface{}) error {
	return e.EmitSyncWithHeaders(key, msg, nil)
}

// Finish waits until the emitter is finished producing all pending messages.
func (e *Emitter) Finish() error {
	e.mu.Lock()
	close(e.done)
	e.mu.Unlock()
	e.wg.Wait()
	return e.producer.Close()
}
<|endoftext|>


<commit_before>// Package parser implements a parser for HCL (HashiCorp Configuration
// Language)
package parser

import (
	"errors"
	"fmt"

	"github.com/hashicorp/hcl/hcl/ast"
	"github.com/hashicorp/hcl/hcl/scanner"
	"github.com/hashicorp/hcl/hcl/token"
)

type Parser struct {
	sc *scanner.Scanner

	// Last read token
	tok       token.Token
	commaPrev token.Token

	comments    []*ast.CommentGroup
	leadComment *ast.CommentGroup // last lead comment
	lineComment *ast.CommentGroup // last line comment

	enableTrace bool
	indent      int
	n           int // buffer size (max = 1)
}

func newParser(src []byte) *Parser {
	return &Parser{
		sc: scanner.New(src),
	}
}

// Parse returns the fully parsed source and returns the abstract syntax tree.
func Parse(src []byte) (*ast.File, error) {
	p := newParser(src)
	return p.Parse()
}

var errEofToken = errors.New("EOF token found")

// Parse returns the fully parsed source and returns the abstract syntax tree.
func (p *Parser) Parse() (*ast.File, error) {
	f := &ast.File{}
	var err error
	f.Node, err = p.objectList()
	if err != nil {
		return nil, err
	}

	f.Comments = p.comments
	return f, nil
}

func (p *Parser) objectList() (*ast.ObjectList, error) {
	defer un(trace(p, "ParseObjectList"))
	node := &ast.ObjectList{}

	for {
		n, err := p.objectItem()
		if err == errEofToken {
			break // we are finished
		}

		// we don't return a nil node, because might want to use already
		// collected items.
		if err != nil {
			return node, err
		}

		node.Add(n)
	}
	return node, nil
}

func (p *Parser) consumeComment() (comment *ast.Comment, endline int) {
	endline = p.tok.Pos.Line

	// count the endline if it's multiline comment, ie starting with /*
	if len(p.tok.Text) > 1 && p.tok.Text[1] == '*' {
		// don't use range here - no need to decode Unicode code points
		for i := 0; i < len(p.tok.Text); i++ {
			if p.tok.Text[i] == '\n' {
				endline++
			}
		}
	}

	comment = &ast.Comment{Start: p.tok.Pos, Text: p.tok.Text}
	p.tok = p.sc.Scan()
	return
}

func (p *Parser) consumeCommentGroup(n int) (comments *ast.CommentGroup, endline int) {
	var list []*ast.Comment
	endline = p.tok.Pos.Line

	for p.tok.Type == token.COMMENT && p.tok.Pos.Line <= endline+n {
		var comment *ast.Comment
		comment, endline = p.consumeComment()
		list = append(list, comment)
	}

	// add comment group to the comments list
	comments = &ast.CommentGroup{List: list}
	p.comments = append(p.comments, comments)

	return
}

// objectItem parses a single object item
func (p *Parser) objectItem() (*ast.ObjectItem, error) {
	defer un(trace(p, "ParseObjectItem"))

	keys, err := p.objectKey()
	if err != nil {
		return nil, err
	}

	o := &ast.ObjectItem{
		Keys: keys,
	}

	if p.leadComment != nil {
		o.LeadComment = p.leadComment
		p.leadComment = nil
	}

	switch p.tok.Type {
	case token.ASSIGN:
		o.Assign = p.tok.Pos
		o.Val, err = p.object()
		if err != nil {
			return nil, err
		}
	case token.LBRACE:
		o.Val, err = p.objectType()
		if err != nil {
			return nil, err
		}
	}

	// do a look-ahead for line comment
	p.scan()
	if o.Val.Pos().Line == keys[0].Pos().Line && p.lineComment != nil {
		o.LineComment = p.lineComment
		p.lineComment = nil
	}
	p.unscan()
	return o, nil
}

// objectKey parses an object key and returns a ObjectKey AST
func (p *Parser) objectKey() ([]*ast.ObjectKey, error) {
	keyCount := 0
	keys := make([]*ast.ObjectKey, 0)

	for {
		tok := p.scan()
		switch tok.Type {
		case token.EOF:
			return nil, errEofToken
		case token.ASSIGN:
			// assignment or object only, but not nested objects. this is not
			// allowed: `foo bar = {}`
			if keyCount > 1 {
				return nil, fmt.Errorf("nested object expected: LBRACE got: %s", p.tok.Type)
			}

			if keyCount == 0 {
				return nil, errors.New("no keys found!!!")
			}

			return keys, nil
		case token.LBRACE:
			// object
			return keys, nil
		case token.IDENT, token.STRING:
			keyCount++
			keys = append(keys, &ast.ObjectKey{Token: p.tok})
		case token.ILLEGAL:
			fmt.Println("illegal")
		default:
			return nil, fmt.Errorf("expected: IDENT | STRING | ASSIGN | LBRACE got: %s", p.tok.Type)
		}
	}
}

// object parses any type of object, such as number, bool, string, object or
// list.
func (p *Parser) object() (ast.Node, error) {
	defer un(trace(p, "ParseType"))
	tok := p.scan()

	switch tok.Type {
	case token.NUMBER, token.FLOAT, token.BOOL, token.STRING:
		return p.literalType()
	case token.LBRACE:
		return p.objectType()
	case token.LBRACK:
		return p.listType()
	case token.COMMENT:
		// implement comment
	case token.EOF:
		return nil, errEofToken
	}

	return nil, fmt.Errorf("Unknown token: %+v", tok)
}

// objectType parses an object type and returns a ObjectType AST
func (p *Parser) objectType() (*ast.ObjectType, error) {
	defer un(trace(p, "ParseObjectType"))

	// we assume that the currently scanned token is a LBRACE
	o := &ast.ObjectType{
		Lbrace: p.tok.Pos,
	}

	l, err := p.objectList()

	// if we hit RBRACE, we are good to go (means we parsed all Items), if it's
	// not a RBRACE, it's an syntax error and we just return it.
	if err != nil && p.tok.Type != token.RBRACE {
		return nil, err
	}

	o.List = l
	o.Rbrace = p.tok.Pos // advanced via parseObjectList
	return o, nil
}

// listType parses a list type and returns a ListType AST
func (p *Parser) listType() (*ast.ListType, error) {
	defer un(trace(p, "ParseListType"))

	// we assume that the currently scanned token is a LBRACK
	l := &ast.ListType{
		Lbrack: p.tok.Pos,
	}

	for {
		tok := p.scan()
		switch tok.Type {
		case token.NUMBER, token.FLOAT, token.STRING:
			node, err := p.literalType()
			if err != nil {
				return nil, err
			}

			l.Add(node)
		case token.COMMA:
			// get next list item or we are at the end
			// do a look-ahead for line comment
			p.scan()
			if p.lineComment != nil {
				lit, ok := l.List[len(l.List)-1].(*ast.LiteralType)
				if ok {
					lit.LineComment = p.lineComment
					l.List[len(l.List)-1] = lit
					p.lineComment = nil
				}
			}
			p.unscan()
			continue
		case token.BOOL:
			// TODO(arslan) should we support? not supported by HCL yet
		case token.LBRACK:
			// TODO(arslan) should we support nested lists? Even though it's
			// written in README of HCL, it's not a part of the grammar
			// (not defined in parse.y)
		case token.RBRACK:
			// finished
			l.Rbrack = p.tok.Pos
			return l, nil
		default:
			return nil, fmt.Errorf("unexpected token while parsing list: %s", tok.Type)
		}

	}
}

// literalType parses a literal type and returns a LiteralType AST
func (p *Parser) literalType() (*ast.LiteralType, error) {
	defer un(trace(p, "ParseLiteral"))

	return &ast.LiteralType{
		Token: p.tok,
	}, nil
}

// scan returns the next token from the underlying scanner. If a token has
// been unscanned then read that instead. In the process, it collects any
// comment groups encountered, and remembers the last lead and line comments.
func (p *Parser) scan() token.Token {
	// If we have a token on the buffer, then return it.
	if p.n != 0 {
		p.n = 0
		return p.tok
	}

	// Otherwise read the next token from the scanner and Save it to the buffer
	// in case we unscan later.
	prev := p.tok
	p.tok = p.sc.Scan()

	if p.tok.Type == token.COMMENT {
		var comment *ast.CommentGroup
		var endline int

		// fmt.Printf("p.tok.Pos.Line = %+v prev: %d endline %d \n",
		// p.tok.Pos.Line, prev.Pos.Line, endline)
		if p.tok.Pos.Line == prev.Pos.Line {
			// The comment is on same line as the previous token; it
			// cannot be a lead comment but may be a line comment.
			comment, endline = p.consumeCommentGroup(0)
			if p.tok.Pos.Line != endline {
				// The next token is on a different line, thus
				// the last comment group is a line comment.
				p.lineComment = comment
			}
		}

		// consume successor comments, if any
		endline = -1
		for p.tok.Type == token.COMMENT {
			comment, endline = p.consumeCommentGroup(1)
		}

		if endline+1 == p.tok.Pos.Line && p.tok.Type != token.RBRACE {
			switch p.tok.Type {
			case token.RBRACE, token.RBRACK:
				// Do not count for these cases
			default:
				// The next token is following on the line immediately after the
				// comment group, thus the last comment group is a lead comment.
				p.leadComment = comment
			}
		}

	}

	return p.tok
}

// unscan pushes the previously read token back onto the buffer.
func (p *Parser) unscan() {
	p.n = 1
}

// ----------------------------------------------------------------------------
// Parsing support

func (p *Parser) printTrace(a ...interface{}) {
	if !p.enableTrace {
		return
	}

	const dots = ". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . "
	const n = len(dots)
	fmt.Printf("%5d:%3d: ", p.tok.Pos.Line, p.tok.Pos.Column)

	i := 2 * p.indent
	for i > n {
		fmt.Print(dots)
		i -= n
	}
	// i <= n
	fmt.Print(dots[0:i])
	fmt.Println(a...)
}

func trace(p *Parser, msg string) *Parser {
	p.printTrace(msg, "(")
	p.indent++
	return p
}

// Usage pattern: defer un(trace(p, "..."))
func un(p *Parser) {
	p.indent--
	p.printTrace(")")
}
<commit_msg>hcl/parser: scan errors should come through as well<commit_after>// Package parser implements a parser for HCL (HashiCorp Configuration
// Language)
package parser

import (
	"errors"
	"fmt"

	"github.com/hashicorp/hcl/hcl/ast"
	"github.com/hashicorp/hcl/hcl/scanner"
	"github.com/hashicorp/hcl/hcl/token"
)

type Parser struct {
	sc *scanner.Scanner

	// Last read token
	tok       token.Token
	commaPrev token.Token

	comments    []*ast.CommentGroup
	leadComment *ast.CommentGroup // last lead comment
	lineComment *ast.CommentGroup // last line comment

	enableTrace bool
	indent      int
	n           int // buffer size (max = 1)
}

func newParser(src []byte) *Parser {
	return &Parser{
		sc: scanner.New(src),
	}
}

// Parse returns the fully parsed source and returns the abstract syntax tree.
func Parse(src []byte) (*ast.File, error) {
	p := newParser(src)
	return p.Parse()
}

var errEofToken = errors.New("EOF token found")

// Parse returns the fully parsed source and returns the abstract syntax tree.
func (p *Parser) Parse() (*ast.File, error) {
	f := &ast.File{}
	var err, scerr error
	p.sc.Error = func(pos token.Pos, msg string) {
		scerr = fmt.Errorf("%s: %s", pos, msg)
	}

	f.Node, err = p.objectList()
	if scerr != nil {
		return nil, scerr
	}
	if err != nil {
		return nil, err
	}

	f.Comments = p.comments
	return f, nil
}

func (p *Parser) objectList() (*ast.ObjectList, error) {
	defer un(trace(p, "ParseObjectList"))
	node := &ast.ObjectList{}

	for {
		n, err := p.objectItem()
		if err == errEofToken {
			break // we are finished
		}

		// we don't return a nil node, because might want to use already
		// collected items.
		if err != nil {
			return node, err
		}

		node.Add(n)
	}
	return node, nil
}

func (p *Parser) consumeComment() (comment *ast.Comment, endline int) {
	endline = p.tok.Pos.Line

	// count the endline if it's multiline comment, ie starting with /*
	if len(p.tok.Text) > 1 && p.tok.Text[1] == '*' {
		// don't use range here - no need to decode Unicode code points
		for i := 0; i < len(p.tok.Text); i++ {
			if p.tok.Text[i] == '\n' {
				endline++
			}
		}
	}

	comment = &ast.Comment{Start: p.tok.Pos, Text: p.tok.Text}
	p.tok = p.sc.Scan()
	return
}

func (p *Parser) consumeCommentGroup(n int) (comments *ast.CommentGroup, endline int) {
	var list []*ast.Comment
	endline = p.tok.Pos.Line

	for p.tok.Type == token.COMMENT && p.tok.Pos.Line <= endline+n {
		var comment *ast.Comment
		comment, endline = p.consumeComment()
		list = append(list, comment)
	}

	// add comment group to the comments list
	comments = &ast.CommentGroup{List: list}
	p.comments = append(p.comments, comments)

	return
}

// objectItem parses a single object item
func (p *Parser) objectItem() (*ast.ObjectItem, error) {
	defer un(trace(p, "ParseObjectItem"))

	keys, err := p.objectKey()
	if err != nil {
		return nil, err
	}

	o := &ast.ObjectItem{
		Keys: keys,
	}

	if p.leadComment != nil {
		o.LeadComment = p.leadComment
		p.leadComment = nil
	}

	switch p.tok.Type {
	case token.ASSIGN:
		o.Assign = p.tok.Pos
		o.Val, err = p.object()
		if err != nil {
			return nil, err
		}
	case token.LBRACE:
		o.Val, err = p.objectType()
		if err != nil {
			return nil, err
		}
	}

	// do a look-ahead for line comment
	p.scan()
	if o.Val.Pos().Line == keys[0].Pos().Line && p.lineComment != nil {
		o.LineComment = p.lineComment
		p.lineComment = nil
	}
	p.unscan()
	return o, nil
}

// objectKey parses an object key and returns a ObjectKey AST
func (p *Parser) objectKey() ([]*ast.ObjectKey, error) {
	keyCount := 0
	keys := make([]*ast.ObjectKey, 0)

	for {
		tok := p.scan()
		switch tok.Type {
		case token.EOF:
			return nil, errEofToken
		case token.ASSIGN:
			// assignment or object only, but not nested objects. this is not
			// allowed: `foo bar = {}`
			if keyCount > 1 {
				return nil, fmt.Errorf("nested object expected: LBRACE got: %s", p.tok.Type)
			}

			if keyCount == 0 {
				return nil, errors.New("no keys found!!!")
			}

			return keys, nil
		case token.LBRACE:
			// object
			return keys, nil
		case token.IDENT, token.STRING:
			keyCount++
			keys = append(keys, &ast.ObjectKey{Token: p.tok})
		case token.ILLEGAL:
			fmt.Println("illegal")
		default:
			return nil, fmt.Errorf("expected: IDENT | STRING | ASSIGN | LBRACE got: %s", p.tok.Type)
		}
	}
}

// object parses any type of object, such as number, bool, string, object or
// list.
func (p *Parser) object() (ast.Node, error) {
	defer un(trace(p, "ParseType"))
	tok := p.scan()

	switch tok.Type {
	case token.NUMBER, token.FLOAT, token.BOOL, token.STRING:
		return p.literalType()
	case token.LBRACE:
		return p.objectType()
	case token.LBRACK:
		return p.listType()
	case token.COMMENT:
		// implement comment
	case token.EOF:
		return nil, errEofToken
	}

	return nil, fmt.Errorf("Unknown token: %+v", tok)
}

// objectType parses an object type and returns a ObjectType AST
func (p *Parser) objectType() (*ast.ObjectType, error) {
	defer un(trace(p, "ParseObjectType"))

	// we assume that the currently scanned token is a LBRACE
	o := &ast.ObjectType{
		Lbrace: p.tok.Pos,
	}

	l, err := p.objectList()

	// if we hit RBRACE, we are good to go (means we parsed all Items), if it's
	// not a RBRACE, it's an syntax error and we just return it.
	if err != nil && p.tok.Type != token.RBRACE {
		return nil, err
	}

	o.List = l
	o.Rbrace = p.tok.Pos // advanced via parseObjectList
	return o, nil
}

// listType parses a list type and returns a ListType AST
func (p *Parser) listType() (*ast.ListType, error) {
	defer un(trace(p, "ParseListType"))

	// we assume that the currently scanned token is a LBRACK
	l := &ast.ListType{
		Lbrack: p.tok.Pos,
	}

	for {
		tok := p.scan()
		switch tok.Type {
		case token.NUMBER, token.FLOAT, token.STRING:
			node, err := p.literalType()
			if err != nil {
				return nil, err
			}

			l.Add(node)
		case token.COMMA:
			// get next list item or we are at the end
			// do a look-ahead for line comment
			p.scan()
			if p.lineComment != nil {
				lit, ok := l.List[len(l.List)-1].(*ast.LiteralType)
				if ok {
					lit.LineComment = p.lineComment
					l.List[len(l.List)-1] = lit
					p.lineComment = nil
				}
			}
			p.unscan()
			continue
		case token.BOOL:
			// TODO(arslan) should we support? not supported by HCL yet
		case token.LBRACK:
			// TODO(arslan) should we support nested lists? Even though it's
			// written in README of HCL, it's not a part of the grammar
			// (not defined in parse.y)
		case token.RBRACK:
			// finished
			l.Rbrack = p.tok.Pos
			return l, nil
		default:
			return nil, fmt.Errorf("unexpected token while parsing list: %s", tok.Type)
		}

	}
}

// literalType parses a literal type and returns a LiteralType AST
func (p *Parser) literalType() (*ast.LiteralType, error) {
	defer un(trace(p, "ParseLiteral"))

	return &ast.LiteralType{
		Token: p.tok,
	}, nil
}

// scan returns the next token from the underlying scanner. If a token has
// been unscanned then read that instead. In the process, it collects any
// comment groups encountered, and remembers the last lead and line comments.
func (p *Parser) scan() token.Token {
	// If we have a token on the buffer, then return it.
	if p.n != 0 {
		p.n = 0
		return p.tok
	}

	// Otherwise read the next token from the scanner and Save it to the buffer
	// in case we unscan later.
	prev := p.tok
	p.tok = p.sc.Scan()

	if p.tok.Type == token.COMMENT {
		var comment *ast.CommentGroup
		var endline int

		// fmt.Printf("p.tok.Pos.Line = %+v prev: %d endline %d \n",
		// p.tok.Pos.Line, prev.Pos.Line, endline)
		if p.tok.Pos.Line == prev.Pos.Line {
			// The comment is on same line as the previous token; it
			// cannot be a lead comment but may be a line comment.
			comment, endline = p.consumeCommentGroup(0)
			if p.tok.Pos.Line != endline {
				// The next token is on a different line, thus
				// the last comment group is a line comment.
				p.lineComment = comment
			}
		}

		// consume successor comments, if any
		endline = -1
		for p.tok.Type == token.COMMENT {
			comment, endline = p.consumeCommentGroup(1)
		}

		if endline+1 == p.tok.Pos.Line && p.tok.Type != token.RBRACE {
			switch p.tok.Type {
			case token.RBRACE, token.RBRACK:
				// Do not count for these cases
			default:
				// The next token is following on the line immediately after the
				// comment group, thus the last comment group is a lead comment.
				p.leadComment = comment
			}
		}

	}

	return p.tok
}

// unscan pushes the previously read token back onto the buffer.
func (p *Parser) unscan() {
	p.n = 1
}

// ----------------------------------------------------------------------------
// Parsing support

func (p *Parser) printTrace(a ...interface{}) {
	if !p.enableTrace {
		return
	}

	const dots = ". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . "
	const n = len(dots)
	fmt.Printf("%5d:%3d: ", p.tok.Pos.Line, p.tok.Pos.Column)

	i := 2 * p.indent
	for i > n {
		fmt.Print(dots)
		i -= n
	}
	// i <= n
	fmt.Print(dots[0:i])
	fmt.Println(a...)
}

func trace(p *Parser, msg string) *Parser {
	p.printTrace(msg, "(")
	p.indent++
	return p
}

// Usage pattern: defer un(trace(p, "..."))
func un(p *Parser) {
	p.indent--
	p.printTrace(")")
}
<|endoftext|>