<commit_before>package api

import (
	"sort"
	"strings"

	"github.com/awslabs/aws-sdk-go/internal/util"
)

type ShapeRef struct {
	API           *API   `json: "-"`
	Shape         *Shape `json: "-"`
	Documentation string
	ShapeName     string `json:"shape"`
	Location      string
	LocationName  string
	QueryName     string
	Flattened     bool
	XMLNamespace  XMLInfo
}

type XMLInfo struct {
	Prefix string
	URI    string
}

type Shape struct {
	API           *API `json: "-"`
	ShapeName     string
	Documentation string
	MemberRefs    map[string]*ShapeRef `json:"members"`
	MemberRef     ShapeRef             `json:"member"`
	KeyRef        ShapeRef             `json:"key"`
	ValueRef      ShapeRef             `json:"value"`
	Required      []string
	Payload       string
	Type          string
	Exception     bool
	Enum          []string
	Flattened     bool
	ResultWrapper string
	Streaming     bool

	refs []*ShapeRef
}

func (s *Shape) Rename(newName string) {
	for _, r := range s.refs {
		r.ShapeName = newName
	}

	delete(s.API.Shapes, s.ShapeName)
	s.API.Shapes[newName] = s
	s.ShapeName = newName
}

func (s *Shape) MemberNames() []string {
	i, names := 0, make([]string, len(s.MemberRefs))
	for n, _ := range s.MemberRefs {
		names[i] = n
		i++
	}
	sort.Strings(names)
	return names
}

func (ref *ShapeRef) GoTypeElem() string {
	switch ref.Shape.Type {
	case "structure":
		return ref.Shape.ShapeName
	case "map":
		return "map[" + ref.Shape.KeyRef.GoTypeElem() + "]" + ref.Shape.ValueRef.GoType()
	case "list":
		return "[]" + ref.Shape.MemberRef.GoType()
	case "boolean":
		return "bool"
	case "string", "character":
		return "string"
	case "blob":
		return "[]byte"
	case "integer":
		return "int"
	case "long":
		return "int64"
	case "float":
		return "float32"
	case "double":
		return "float64"
	case "timestamp":
		ref.API.imports["time"] = true
		return "time.Time"
	default:
		panic("Unsupported shape type: " + ref.Shape.Type)
	}
}

func (ref *ShapeRef) GoType() string {
	t := ref.GoTypeElem()
	if !strings.HasPrefix(t, "[]") {
		t = "*" + t
	}
	return t
}

func (ref *ShapeRef) GoTags(toplevel bool) string {
	code := "`"
	if ref.Location != "" {
		code += `location:"` + ref.Location + `" `
	}
	if ref.LocationName != "" {
		code += `locationName:"` + ref.LocationName + `" `
	}
	if ref.QueryName != "" {
		code += `queryName:"` + ref.QueryName + `" `
	}
	if ref.Shape.MemberRef.LocationName != "" {
		code += `locationNameList:"` + ref.Shape.MemberRef.LocationName + `" `
	}
	if ref.Shape.KeyRef.LocationName != "" {
		code += `locationNameKey:"` + ref.Shape.KeyRef.LocationName + `" `
	}
	if ref.Shape.ValueRef.LocationName != "" {
		code += `locationNameValue:"` + ref.Shape.ValueRef.LocationName + `" `
	}
	code += `type:"` + ref.Shape.Type + `" `

	// embed the timestamp type for easier lookups
	if ref.Shape.Type == "timestamp" {
		code += `timestampFormat:"`
		if ref.Location == "header" {
			code += "rfc822"
		} else {
			switch ref.API.Metadata.Protocol {
			case "json", "rest-json":
				code += "unix"
			case "rest-xml", "ec2", "query":
				code += "iso8601"
			}
		}
		code += `" `
	}

	if ref.Shape.Flattened || ref.Flattened {
		code += `flattened:"true" `
	}

	if toplevel {
		if ref.Shape.ResultWrapper != "" {
			code += `resultWrapper:"` + ref.Shape.ResultWrapper + `" `
		}
		if ref.Shape.Payload != "" {
			code += `payload:"` + ref.Shape.Payload + `" `
		}
		if len(ref.Shape.Required) > 0 {
			code += `required:"` + strings.Join(ref.Shape.Required, ",") + `" `
		}
	}
	if strings.Contains(ref.API.Metadata.Protocol, "json") {
		code += `json:",omitempty"`
	}

	return strings.TrimSpace(code) + "`"
}

func (s *Shape) GoCode() string {
	code := "type " + s.ShapeName + " "
	switch s.Type {
	case "structure":
		code += "struct {\n"
		for _, n := range s.MemberNames() {
			m := s.MemberRefs[n]
			if s.Streaming && s.Payload == n {
				code += n + " io.ReadSeeker " + m.GoTags(false) + "\n"
			} else {
				code += n + " " + m.GoType() + " " + m.GoTags(false) + "\n"
			}
		}
		metaStruct := "metadata" + s.ShapeName
		ref := &ShapeRef{ShapeName: s.ShapeName, API: s.API, Shape: s}
		code += "\n" + metaStruct + "  `json:\"-\", xml:\"-\"`\n"
		code += "}\n\n"
		code += "type " + metaStruct + " struct {\n"
		code += "SDKShapeTraits bool " + ref.GoTags(true)
		code += "}"
	default:
		panic("Cannot generate toplevel shape for " + s.Type)
	}

	return util.GoFmt(code)
}
<commit_msg>Don't JSON serialize any non-body location traits<commit_after>package api

import (
	"sort"
	"strings"

	"github.com/awslabs/aws-sdk-go/internal/util"
)

type ShapeRef struct {
	API           *API   `json: "-"`
	Shape         *Shape `json: "-"`
	Documentation string
	ShapeName     string `json:"shape"`
	Location      string
	LocationName  string
	QueryName     string
	Flattened     bool
	XMLNamespace  XMLInfo
}

type XMLInfo struct {
	Prefix string
	URI    string
}

type Shape struct {
	API           *API `json: "-"`
	ShapeName     string
	Documentation string
	MemberRefs    map[string]*ShapeRef `json:"members"`
	MemberRef     ShapeRef             `json:"member"`
	KeyRef        ShapeRef             `json:"key"`
	ValueRef      ShapeRef             `json:"value"`
	Required      []string
	Payload       string
	Type          string
	Exception     bool
	Enum          []string
	Flattened     bool
	ResultWrapper string
	Streaming     bool

	refs []*ShapeRef
}

func (s *Shape) Rename(newName string) {
	for _, r := range s.refs {
		r.ShapeName = newName
	}

	delete(s.API.Shapes, s.ShapeName)
	s.API.Shapes[newName] = s
	s.ShapeName = newName
}

func (s *Shape) MemberNames() []string {
	i, names := 0, make([]string, len(s.MemberRefs))
	for n, _ := range s.MemberRefs {
		names[i] = n
		i++
	}
	sort.Strings(names)
	return names
}

func (ref *ShapeRef) GoTypeElem() string {
	switch ref.Shape.Type {
	case "structure":
		return ref.Shape.ShapeName
	case "map":
		return "map[" + ref.Shape.KeyRef.GoTypeElem() + "]" + ref.Shape.ValueRef.GoType()
	case "list":
		return "[]" + ref.Shape.MemberRef.GoType()
	case "boolean":
		return "bool"
	case "string", "character":
		return "string"
	case "blob":
		return "[]byte"
	case "integer":
		return "int"
	case "long":
		return "int64"
	case "float":
		return "float32"
	case "double":
		return "float64"
	case "timestamp":
		ref.API.imports["time"] = true
		return "time.Time"
	default:
		panic("Unsupported shape type: " + ref.Shape.Type)
	}
}

func (ref *ShapeRef) GoType() string {
	t := ref.GoTypeElem()
	if !strings.HasPrefix(t, "[]") {
		t = "*" + t
	}
	return t
}

func (ref *ShapeRef) GoTags(toplevel bool) string {
	code := "`"
	if ref.Location != "" {
		code += `location:"` + ref.Location + `" `
	}
	if ref.LocationName != "" {
		code += `locationName:"` + ref.LocationName + `" `
	}
	if ref.QueryName != "" {
		code += `queryName:"` + ref.QueryName + `" `
	}
	if ref.Shape.MemberRef.LocationName != "" {
		code += `locationNameList:"` + ref.Shape.MemberRef.LocationName + `" `
	}
	if ref.Shape.KeyRef.LocationName != "" {
		code += `locationNameKey:"` + ref.Shape.KeyRef.LocationName + `" `
	}
	if ref.Shape.ValueRef.LocationName != "" {
		code += `locationNameValue:"` + ref.Shape.ValueRef.LocationName + `" `
	}
	code += `type:"` + ref.Shape.Type + `" `

	// embed the timestamp type for easier lookups
	if ref.Shape.Type == "timestamp" {
		code += `timestampFormat:"`
		if ref.Location == "header" {
			code += "rfc822"
		} else {
			switch ref.API.Metadata.Protocol {
			case "json", "rest-json":
				code += "unix"
			case "rest-xml", "ec2", "query":
				code += "iso8601"
			}
		}
		code += `" `
	}

	if ref.Shape.Flattened || ref.Flattened {
		code += `flattened:"true" `
	}

	if toplevel {
		if ref.Shape.ResultWrapper != "" {
			code += `resultWrapper:"` + ref.Shape.ResultWrapper + `" `
		}
		if ref.Shape.Payload != "" {
			code += `payload:"` + ref.Shape.Payload + `" `
		}
		if len(ref.Shape.Required) > 0 {
			code += `required:"` + strings.Join(ref.Shape.Required, ",") + `" `
		}
	}

	if ref.Location != "" { // omit non-body location elements from JSON/XML
		code += `json:"-" xml:"-" `
	} else if strings.Contains(ref.API.Metadata.Protocol, "json") {
		code += `json:",omitempty"`
	}

	return strings.TrimSpace(code) + "`"
}

func (s *Shape) GoCode() string {
	code := "type " + s.ShapeName + " "
	switch s.Type {
	case "structure":
		code += "struct {\n"
		for _, n := range s.MemberNames() {
			m := s.MemberRefs[n]
			if s.Streaming && s.Payload == n {
				code += n + " io.ReadSeeker " + m.GoTags(false) + "\n"
			} else {
				code += n + " " + m.GoType() + " " + m.GoTags(false) + "\n"
			}
		}
		metaStruct := "metadata" + s.ShapeName
		ref := &ShapeRef{ShapeName: s.ShapeName, API: s.API, Shape: s}
		code += "\n" + metaStruct + "  `json:\"-\", xml:\"-\"`\n"
		code += "}\n\n"
		code += "type " + metaStruct + " struct {\n"
		code += "SDKShapeTraits bool " + ref.GoTags(true)
		code += "}"
	default:
		panic("Cannot generate toplevel shape for " + s.Type)
	}

	return util.GoFmt(code)
}
<|endoftext|>


<commit_before>package plot

import (
	"code.google.com/p/plotinum/vg"
	"code.google.com/p/plotinum/vg/veceps"
	"code.google.com/p/plotinum/vg/vecimg"
	"math/rand"
	"time"
	"testing"
)

var seed = time.Now().UnixNano()

func TestDrawImage(t *testing.T) {
	w, h := vg.Inches(4), vg.Inches(4)
	img, err := vecimg.New(w, h)
	if err != nil {
		t.Error(err)
	}
	da := NewDrawArea(img, w, h)
	draw(da)
	err = img.SavePNG("test.png")
	if err != nil {
		t.Fatal(err)
	}
}

func TestDrawEps(t *testing.T) {
	w, h := vg.Inches(4), vg.Inches(4)
	da := NewDrawArea(veceps.New(w, h, "test"), w, h)
	draw(da)
	err := da.Canvas.(*veceps.Canvas).Save("test.eps")
	if err != nil {
		t.Fatal(err)
	}
}

// draw draws a simple test plot
func draw(da *DrawArea) {
	p := New()
	p.Title.Text = "Title"
	p.Y.Label.Text = "Y Label"
	rand.Seed(seed)
	vs0 := make(Values, 10)
	for i := range vs0 {
		vs0[i] = rand.Float64()*1000
	}
	vs1 := make(Values, 10)
	for i := range vs1 {
		vs1[i] = rand.NormFloat64()*200 + 500
	}
	vs2 := make(Values, 10)
	for i := range vs2 {
		vs2[i] = rand.ExpFloat64()*300
	}
	p.AddData(NewBox(vg.Points(20), 0, vs0))
	p.AddData(NewBox(vg.Points(20), 1, vs1))
	p.AddData(NewBox(vg.Points(20), 2, vs2))
	p.X.Tick.Marker = ConstantTicks([]Tick{
		{0, "Uniform\nDistribution",}, {1, "Normal\nDistribution",},
		{2, "Exponential\nDistribution"},
	})
	p.Y.Padding = p.X.Tick.Label.Width("Uniform\nDistribution")/2
	p.X.Tick.Label.Font.Size = vg.Points(12)
	p.X.Tick.Width = 0
	p.X.Tick.Length = 0
	p.X.Width = 0

	p.Y.Min = 0
	p.Y.Max = 1000
	p.Draw(da)
}
<commit_msg>Play around with the demo plot.<commit_after>package plot

import (
	"code.google.com/p/plotinum/vg"
	"code.google.com/p/plotinum/vg/veceps"
	"code.google.com/p/plotinum/vg/vecimg"
	"math/rand"
	"time"
	"testing"
)

var seed = time.Now().UnixNano()

func TestDrawImage(t *testing.T) {
	w, h := vg.Inches(4), vg.Inches(4)
	img, err := vecimg.New(w, h)
	if err != nil {
		t.Error(err)
	}
	da := NewDrawArea(img, w, h)
	draw(da)
	err = img.SavePNG("test.png")
	if err != nil {
		t.Fatal(err)
	}
}

func TestDrawEps(t *testing.T) {
	w, h := vg.Inches(4), vg.Inches(4)
	da := NewDrawArea(veceps.New(w, h, "test"), w, h)
	draw(da)
	err := da.Canvas.(*veceps.Canvas).Save("test.eps")
	if err != nil {
		t.Fatal(err)
	}
}

// draw draws a simple test plot
func draw(da *DrawArea) {
	p := New()
	p.Title.Text = "Title"
	p.Y.Label.Text = "Y Label"
	rand.Seed(seed)
	vs0 := make(Values, 10)
	for i := range vs0 {
		vs0[i] = rand.Float64()*1000
	}
	b0 := NewBox(vg.Points(20), 0, vs0)

	vs1 := make(Values, 10)
	for i := range vs1 {
		vs1[i] = rand.NormFloat64()*200 + 500
	}
	b1 := NewBox(vg.Points(20), 1, vs1)

	vs2 := make(Values, 10)
	for i := range vs2 {
		vs2[i] = rand.ExpFloat64()*300
	}
	b2 := NewBox(vg.Points(20), 2, vs2)

	meds :=  Points{ { b0.X, b0.Med }, { b1.X, b1.Med }, { b2.X, b2.Med } }

	p.AddData(b0, b1, b2,
		Line{ meds, DefaultLineStyle },
		Scatter{ meds, GlyphStyle{Shape: CircleGlyph, Radius: vg.Points(2)} })

	p.X.Tick.Marker = ConstantTicks([]Tick{
		{0, "Uniform\nDistribution",}, {1, "Normal\nDistribution",},
		{2, "Exponential\nDistribution"},
	})
	p.Y.Padding = p.X.Tick.Label.Width("Uniform\nDistribution")/2
	p.X.Tick.Label.Font.Size = vg.Points(12)
	p.X.Tick.Width = 0
	p.X.Tick.Length = 0
	p.X.Width = 0

	p.Y.Min = 0
	p.Y.Max = 1000
	p.Draw(da)
}
<|endoftext|>


<commit_before>// Copyright © 2017 Circonus, Inc. <support@circonus.com>
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//

package server

import (
	"bytes"
	"compress/gzip"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"strconv"
	"strings"
	"time"

	"github.com/circonus-labs/circonus-agent/internal/config"
	"github.com/circonus-labs/circonus-agent/internal/server/promrecv"
	"github.com/circonus-labs/circonus-agent/internal/server/receiver"
	cgm "github.com/circonus-labs/circonus-gometrics"
	appstats "github.com/maier/go-appstats"
	"github.com/spf13/viper"
)

// run handles requests to execute plugins and return metrics emitted
// handles /, /run, or /run/plugin_name
func (s *Server) run(w http.ResponseWriter, r *http.Request) {
	id := ""

	if strings.HasPrefix(r.URL.Path, "/run/") { // run specific item
		id = strings.Replace(r.URL.Path, "/run/", "", -1)
		if id != "" {
			idOK := false

			// highest priority, internal servers (receiver, statsd, etc.)
			if !idOK {
				s.logger.Debug().Str("id", id).Msg("checking internals")
				idOK = s.plugins.IsInternal(id)
			}
			// check builtins before plugins, builtins offer better efficiency
			if !idOK {
				s.logger.Debug().Str("id", id).Msg("checking bulitins")
				idOK = s.builtins.IsBuiltin(id)
			}
			// lastly, check active plugins, if any
			if !idOK {
				s.logger.Debug().Str("id", id).Msg("checking plugins")
				idOK = s.plugins.IsValid(id)
			}

			if !idOK {
				s.logger.Warn().
					Str("id", id).
					Msg("unknown item requested")
				http.NotFound(w, r)
				return
			}
		}
	}

	lastMeticsmu.Lock()
	defer lastMeticsmu.Unlock()

	metrics := map[string]interface{}{}

	// default to true if id is blank, otherwise set all to false
	runBuiltins := id == ""
	runPlugins := id == ""
	flushProm := id == ""
	flushReceiver := id == ""
	flushStatsd := id == ""

	if id != "" {
		// identify _what_ to run based on the id
		switch {
		case id == "prom":
			flushProm = true
		case id == "write":
			flushReceiver = true
		case id == "statsd":
			flushStatsd = true
		case s.builtins.IsBuiltin(id):
			runBuiltins = true
		default:
			runPlugins = true
		}
	}

	if runBuiltins {
		s.builtins.Run(id)
		builtinMetrics := s.builtins.Flush(id)
		for metricName, metric := range *builtinMetrics {
			metrics[metricName] = metric
		}
	}

	if runPlugins {
		// NOTE: errors are ignored from plugins.Run
		//       1. errors are already logged by Run
		//       2. do not expose execution state to callers
		s.logger.Debug().Msg("calling plugin run")
		s.plugins.Run(id)
		s.logger.Debug().Msg("plugin run done")
		s.logger.Debug().Msg("calling plugin flush")
		pluginMetrics := s.plugins.Flush(id)
		s.logger.Debug().Msg("plugin flush done")
		for metricName, metric := range *pluginMetrics {
			if v, ok := metric.(*cgm.Metrics); ok {
				for mn, mv := range *v {
					metrics[metricName+config.MetricNameSeparator+mn] = mv
				}
			} else {
				metrics[metricName] = metric
			}
		}
	}

	if flushReceiver {
		s.logger.Debug().Msg("calling receiver flush")
		receiverMetrics := receiver.Flush()
		s.logger.Debug().Msg("receiver flush done")
		for metricName, metric := range *receiverMetrics {
			metrics[metricName] = metric
		}
	}

	if flushStatsd {
		if s.statsdSvr != nil {
			s.logger.Debug().Msg("calling statsd flush")
			statsdMetrics := s.statsdSvr.Flush()
			s.logger.Debug().Msg("statsd flush done")
			if statsdMetrics != nil {
				pfx := viper.GetString(config.KeyStatsdHostCategory)
				for metricName, metric := range *statsdMetrics {
					metrics[pfx+config.MetricNameSeparator+metricName] = metric
				}
				// metrics[viper.GetString(config.KeyStatsdHostCategory)] = statsdMetrics
			}
		}
	}

	if flushProm {
		s.logger.Debug().Msg("calling prom flush")
		promMetrics := promrecv.Flush()
		s.logger.Debug().Msg("prom flush done")
		for metricName, metric := range *promMetrics {
			metrics[metricName] = metric
		}
	}

	s.logger.Debug().Msg("update lastMetrics")
	lastMetrics.metrics = metrics
	lastMetrics.ts = time.Now()
	s.logger.Debug().Msg("update lastMetrics done")

	s.logger.Debug().Msg("calling enable new metrics")
	if err := s.check.EnableNewMetrics(&metrics); err != nil {
		s.logger.Warn().Err(err).Msg("unable to update check metrics")
	}
	s.logger.Debug().Msg("enable new metrics done")

	s.logger.Debug().Msg("encoding metrics")
	s.encodeResponse(&metrics, w, r)
	s.logger.Debug().Msg("encoding done")
}

// encodeResponse takes care of encoding the response to an HTTP request for metrics.
// The broker does not handle chunk encoded data correctly and will emit an error if
// it receives it. The agent does support gzip compression when the correct header
// is supplied (Accept-Encoding: * or Accept-Encoding: gzip). The command line option
// --no-gzip overrides and will result in unencoded response regardless of what the
// Accept-Encoding header specifies.
func (s *Server) encodeResponse(m *map[string]interface{}, w http.ResponseWriter, r *http.Request) {
	//
	// if an error occurs, it is logged and empty {} metrics are returned
	//

	// basically, turn off chunking
	w.Header().Set("Transfer-Encoding", "identity")
	w.Header().Set("Content-Type", "application/json")

	var data []byte
	var err error
	var useGzip bool

	if viper.GetBool(config.KeyDisableGzip) {
		useGzip = false
	} else {
		acceptedEncodings := r.Header.Get("Accept-Encoding")
		useGzip = strings.Contains(acceptedEncodings, "*") || strings.Contains(acceptedEncodings, "gzip")
	}

	if useGzip {
		var buf bytes.Buffer
		gz := gzip.NewWriter(&buf)
		err = json.NewEncoder(gz).Encode(m)
		gz.Close()

		if err != nil {
			// log the error and respond with empty metrics
			s.logger.Error().
				Err(err).
				Msg("Writing metrics to response")
			data = []byte("{}")
		} else {
			w.Header().Set("Content-Encoding", "gzip")
			data = buf.Bytes()
		}
	} else {
		data, err = json.Marshal(m)
		if err != nil {
			// log the error and respond with empty metrics
			s.logger.Error().
				Err(err).
				Interface("metrics", m).
				Msg("Encoding metrics to JSON for response")
			data = []byte("{}")
		}
	}

	w.Header().Set("Content-Length", fmt.Sprintf("%d", len(data)))
	w.Write(data)
}

// inventory returns the current, active plugin inventory
func (s *Server) inventory(w http.ResponseWriter, r *http.Request) {
	inventory := s.plugins.Inventory()
	if inventory == nil {
		inventory = []byte(`{"error": "empty inventory"}`)
		s.logger.Error().Msg("inventory is nil/empty...")
	}

	w.Header().Set("Content-Type", "application/json")
	w.Write(inventory)
}

// socketHandler gates /write for the socket server only
func (s *Server) socketHandler(w http.ResponseWriter, r *http.Request) {
	if !writePathRx.MatchString(r.URL.Path) {
		appstats.IncrementInt("requests_bad")
		s.logger.Warn().
			Str("method", r.Method).
			Str("url", r.URL.String()).
			Msg("Not found")
		http.NotFound(w, r)
		return
	}

	if r.Method != "PUT" && r.Method != "POST" {
		appstats.IncrementInt("requests_bad")
		s.logger.Warn().
			Str("method", r.Method).
			Str("url", r.URL.String()).
			Msg("Not found")
		http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
		return
	}

	s.write(w, r)
}

// write handles PUT/POST requests with a JSON playload containing "freeform"
// metrics. No validation is applied to the "format" of the metrics beyond k/v.
// Where 'key' is the metric name and 'value' is the metric value as either a
// simple value (e.g. {"name": 1, "foo": "bar", ...}) or a structured value
// representation (e.g. {"foo": {_type: "i", _value: 1}, ...}).
func (s *Server) write(w http.ResponseWriter, r *http.Request) {
	id := strings.Replace(r.URL.Path, "/write/", "", -1)

	s.logger.Debug().Str("path", r.URL.Path).Str("id", id).Msg("write request")
	// a write request *MUST* include a metric group id to act as a namespace.
	// in other words, a "plugin name", all metrics for that write will appear
	// _under_ the metric group id (aka plugin name)
	if id == "" {
		s.logger.Warn().Msg("write recevier - invalid id (empty)")
		http.NotFound(w, r)
		return
	}

	if err := receiver.Parse(id, r.Body); err != nil {
		s.logger.Warn().Err(err).Msg("write recevier")
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}

	w.WriteHeader(http.StatusNoContent)
}

// promReceiver handles PUT/POST requests with prometheus TEXT formatted metrics
// https://prometheus.io/docs/instrumenting/exposition_formats/
func (s *Server) promReceiver(w http.ResponseWriter, r *http.Request) {
	s.logger.Debug().Str("path", r.URL.Path).Msg("prom metrics recevied")

	if err := promrecv.Parse(r.Body); err != nil {
		s.logger.Warn().Err(err).Msg("prom recevier")
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}

	w.WriteHeader(http.StatusNoContent)
}

// promOutput returns the last metrics in prom format
func (s *Server) promOutput(w http.ResponseWriter, r *http.Request) {
	if lastMetrics.metrics == nil || len(lastMetrics.metrics) == 0 {
		w.WriteHeader(http.StatusNoContent)
		return
	}

	ms := lastMetrics.ts.UnixNano() / int64(time.Millisecond)

	w.Header().Set("Content-Type", "text/plain")
	w.WriteHeader(http.StatusOK)
	for id, data := range lastMetrics.metrics {
		s.metricsToPromFormat(w, id, ms, data)
	}
}

func (s *Server) metricsToPromFormat(w io.Writer, prefix string, ts int64, val interface{}) {
	l := s.logger.With().Str("op", "prom export").Logger()
	switch t := val.(type) {
	case cgm.Metric:
		metric := val.(cgm.Metric)
		sv := fmt.Sprintf("%v", metric.Value)
		switch metric.Type {
		case "i":
			fallthrough
		case "I":
			fallthrough
		case "l":
			fallthrough
		case "L":
			v, err := strconv.ParseInt(sv, 10, 64)
			if err != nil {
				l.Error().Err(err).Msg("conv int64")
				return
			}
			if _, err := w.Write([]byte(fmt.Sprintf("%s %d %d\n", prefix, v, ts))); err != nil {
				l.Error().Err(err).Msg("writing prom output")
			}
		case "n":
			if strings.Contains(sv, "[H[") {
				l.Warn().
					Str("type", "histogram != [prom]histogram(percentile)").
					Str("metric", fmt.Sprintf("%s = %s", prefix, sv)).
					Msg("unsupported metric type")
			} else {
				v, err := strconv.ParseFloat(sv, 64)
				if err != nil {
					l.Error().Err(err).Msg("conv float64")
					return
				}
				if _, err := w.Write([]byte(fmt.Sprintf("%s %f %d\n", prefix, v, ts))); err != nil {
					l.Error().Err(err).Msg("writing prom output")
				}
			}
		case "s":
			l.Warn().
				Str("type", "text [prom]???").
				Str("metric", fmt.Sprintf("%s = %s", prefix, sv)).
				Msg("unsuported metric type")
		default:
			l.Warn().
				Str("type", metric.Type).
				Str("name", prefix).
				Interface("metric", metric).
				Msg("invalid metric type")
		}
	case cgm.Metrics:
		metrics := val.(cgm.Metrics)
		for pfx, metric := range metrics {
			name := prefix
			if pfx != "" {
				name = strings.Join([]string{name, pfx}, config.MetricNameSeparator)
			}
			s.metricsToPromFormat(w, name, ts, metric)
		}
	case *cgm.Metrics:
		metrics := val.(*cgm.Metrics)
		s.metricsToPromFormat(w, prefix, ts, *metrics)
	default:
		l.Warn().
			Str("metric", fmt.Sprintf("#TYPE(%T) %v = %#v", t, prefix, val)).
			Msg("unhandled export type")
	}
}
<commit_msg>add: debug msg re:gzip use<commit_after>// Copyright © 2017 Circonus, Inc. <support@circonus.com>
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//

package server

import (
	"bytes"
	"compress/gzip"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"strconv"
	"strings"
	"time"

	"github.com/circonus-labs/circonus-agent/internal/config"
	"github.com/circonus-labs/circonus-agent/internal/server/promrecv"
	"github.com/circonus-labs/circonus-agent/internal/server/receiver"
	cgm "github.com/circonus-labs/circonus-gometrics"
	appstats "github.com/maier/go-appstats"
	"github.com/spf13/viper"
)

// run handles requests to execute plugins and return metrics emitted
// handles /, /run, or /run/plugin_name
func (s *Server) run(w http.ResponseWriter, r *http.Request) {
	id := ""

	if strings.HasPrefix(r.URL.Path, "/run/") { // run specific item
		id = strings.Replace(r.URL.Path, "/run/", "", -1)
		if id != "" {
			idOK := false

			// highest priority, internal servers (receiver, statsd, etc.)
			if !idOK {
				s.logger.Debug().Str("id", id).Msg("checking internals")
				idOK = s.plugins.IsInternal(id)
			}
			// check builtins before plugins, builtins offer better efficiency
			if !idOK {
				s.logger.Debug().Str("id", id).Msg("checking bulitins")
				idOK = s.builtins.IsBuiltin(id)
			}
			// lastly, check active plugins, if any
			if !idOK {
				s.logger.Debug().Str("id", id).Msg("checking plugins")
				idOK = s.plugins.IsValid(id)
			}

			if !idOK {
				s.logger.Warn().
					Str("id", id).
					Msg("unknown item requested")
				http.NotFound(w, r)
				return
			}
		}
	}

	lastMeticsmu.Lock()
	defer lastMeticsmu.Unlock()

	metrics := map[string]interface{}{}

	// default to true if id is blank, otherwise set all to false
	runBuiltins := id == ""
	runPlugins := id == ""
	flushProm := id == ""
	flushReceiver := id == ""
	flushStatsd := id == ""

	if id != "" {
		// identify _what_ to run based on the id
		switch {
		case id == "prom":
			flushProm = true
		case id == "write":
			flushReceiver = true
		case id == "statsd":
			flushStatsd = true
		case s.builtins.IsBuiltin(id):
			runBuiltins = true
		default:
			runPlugins = true
		}
	}

	if runBuiltins {
		s.builtins.Run(id)
		builtinMetrics := s.builtins.Flush(id)
		for metricName, metric := range *builtinMetrics {
			metrics[metricName] = metric
		}
	}

	if runPlugins {
		// NOTE: errors are ignored from plugins.Run
		//       1. errors are already logged by Run
		//       2. do not expose execution state to callers
		s.logger.Debug().Msg("calling plugin run")
		s.plugins.Run(id)
		s.logger.Debug().Msg("plugin run done")
		s.logger.Debug().Msg("calling plugin flush")
		pluginMetrics := s.plugins.Flush(id)
		s.logger.Debug().Msg("plugin flush done")
		for metricName, metric := range *pluginMetrics {
			if v, ok := metric.(*cgm.Metrics); ok {
				for mn, mv := range *v {
					metrics[metricName+config.MetricNameSeparator+mn] = mv
				}
			} else {
				metrics[metricName] = metric
			}
		}
	}

	if flushReceiver {
		s.logger.Debug().Msg("calling receiver flush")
		receiverMetrics := receiver.Flush()
		s.logger.Debug().Msg("receiver flush done")
		for metricName, metric := range *receiverMetrics {
			metrics[metricName] = metric
		}
	}

	if flushStatsd {
		if s.statsdSvr != nil {
			s.logger.Debug().Msg("calling statsd flush")
			statsdMetrics := s.statsdSvr.Flush()
			s.logger.Debug().Msg("statsd flush done")
			if statsdMetrics != nil {
				pfx := viper.GetString(config.KeyStatsdHostCategory)
				for metricName, metric := range *statsdMetrics {
					metrics[pfx+config.MetricNameSeparator+metricName] = metric
				}
				// metrics[viper.GetString(config.KeyStatsdHostCategory)] = statsdMetrics
			}
		}
	}

	if flushProm {
		s.logger.Debug().Msg("calling prom flush")
		promMetrics := promrecv.Flush()
		s.logger.Debug().Msg("prom flush done")
		for metricName, metric := range *promMetrics {
			metrics[metricName] = metric
		}
	}

	s.logger.Debug().Msg("update lastMetrics")
	lastMetrics.metrics = metrics
	lastMetrics.ts = time.Now()
	s.logger.Debug().Msg("update lastMetrics done")

	s.logger.Debug().Msg("calling enable new metrics")
	if err := s.check.EnableNewMetrics(&metrics); err != nil {
		s.logger.Warn().Err(err).Msg("unable to update check metrics")
	}
	s.logger.Debug().Msg("enable new metrics done")

	s.logger.Debug().Msg("encoding metrics")
	s.encodeResponse(&metrics, w, r)
	s.logger.Debug().Msg("encoding done")
}

// encodeResponse takes care of encoding the response to an HTTP request for metrics.
// The broker does not handle chunk encoded data correctly and will emit an error if
// it receives it. The agent does support gzip compression when the correct header
// is supplied (Accept-Encoding: * or Accept-Encoding: gzip). The command line option
// --no-gzip overrides and will result in unencoded response regardless of what the
// Accept-Encoding header specifies.
func (s *Server) encodeResponse(m *map[string]interface{}, w http.ResponseWriter, r *http.Request) {
	//
	// if an error occurs, it is logged and empty {} metrics are returned
	//

	// basically, turn off chunking
	w.Header().Set("Transfer-Encoding", "identity")
	w.Header().Set("Content-Type", "application/json")

	var data []byte
	var err error
	var useGzip bool

	if viper.GetBool(config.KeyDisableGzip) {
		useGzip = false
	} else {
		acceptedEncodings := r.Header.Get("Accept-Encoding")
		useGzip = strings.Contains(acceptedEncodings, "*") || strings.Contains(acceptedEncodings, "gzip")
		s.logger.Debug().Bool("gzip", useGzip).Str("accept_encoding", acceptedEncodings).Msg("compressing response")
	}

	if useGzip {
		var buf bytes.Buffer
		gz := gzip.NewWriter(&buf)
		err = json.NewEncoder(gz).Encode(m)
		gz.Close()

		if err != nil {
			// log the error and respond with empty metrics
			s.logger.Error().
				Err(err).
				Msg("Writing metrics to response")
			data = []byte("{}")
		} else {
			w.Header().Set("Content-Encoding", "gzip")
			data = buf.Bytes()
		}
	} else {
		data, err = json.Marshal(m)
		if err != nil {
			// log the error and respond with empty metrics
			s.logger.Error().
				Err(err).
				Interface("metrics", m).
				Msg("Encoding metrics to JSON for response")
			data = []byte("{}")
		}
	}

	w.Header().Set("Content-Length", fmt.Sprintf("%d", len(data)))
	w.Write(data)
}

// inventory returns the current, active plugin inventory
func (s *Server) inventory(w http.ResponseWriter, r *http.Request) {
	inventory := s.plugins.Inventory()
	if inventory == nil {
		inventory = []byte(`{"error": "empty inventory"}`)
		s.logger.Error().Msg("inventory is nil/empty...")
	}

	w.Header().Set("Content-Type", "application/json")
	w.Write(inventory)
}

// socketHandler gates /write for the socket server only
func (s *Server) socketHandler(w http.ResponseWriter, r *http.Request) {
	if !writePathRx.MatchString(r.URL.Path) {
		appstats.IncrementInt("requests_bad")
		s.logger.Warn().
			Str("method", r.Method).
			Str("url", r.URL.String()).
			Msg("Not found")
		http.NotFound(w, r)
		return
	}

	if r.Method != "PUT" && r.Method != "POST" {
		appstats.IncrementInt("requests_bad")
		s.logger.Warn().
			Str("method", r.Method).
			Str("url", r.URL.String()).
			Msg("Not found")
		http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
		return
	}

	s.write(w, r)
}

// write handles PUT/POST requests with a JSON playload containing "freeform"
// metrics. No validation is applied to the "format" of the metrics beyond k/v.
// Where 'key' is the metric name and 'value' is the metric value as either a
// simple value (e.g. {"name": 1, "foo": "bar", ...}) or a structured value
// representation (e.g. {"foo": {_type: "i", _value: 1}, ...}).
func (s *Server) write(w http.ResponseWriter, r *http.Request) {
	id := strings.Replace(r.URL.Path, "/write/", "", -1)

	s.logger.Debug().Str("path", r.URL.Path).Str("id", id).Msg("write request")
	// a write request *MUST* include a metric group id to act as a namespace.
	// in other words, a "plugin name", all metrics for that write will appear
	// _under_ the metric group id (aka plugin name)
	if id == "" {
		s.logger.Warn().Msg("write recevier - invalid id (empty)")
		http.NotFound(w, r)
		return
	}

	if err := receiver.Parse(id, r.Body); err != nil {
		s.logger.Warn().Err(err).Msg("write recevier")
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}

	w.WriteHeader(http.StatusNoContent)
}

// promReceiver handles PUT/POST requests with prometheus TEXT formatted metrics
// https://prometheus.io/docs/instrumenting/exposition_formats/
func (s *Server) promReceiver(w http.ResponseWriter, r *http.Request) {
	s.logger.Debug().Str("path", r.URL.Path).Msg("prom metrics recevied")

	if err := promrecv.Parse(r.Body); err != nil {
		s.logger.Warn().Err(err).Msg("prom recevier")
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}

	w.WriteHeader(http.StatusNoContent)
}

// promOutput returns the last metrics in prom format
func (s *Server) promOutput(w http.ResponseWriter, r *http.Request) {
	if lastMetrics.metrics == nil || len(lastMetrics.metrics) == 0 {
		w.WriteHeader(http.StatusNoContent)
		return
	}

	ms := lastMetrics.ts.UnixNano() / int64(time.Millisecond)

	w.Header().Set("Content-Type", "text/plain")
	w.WriteHeader(http.StatusOK)
	for id, data := range lastMetrics.metrics {
		s.metricsToPromFormat(w, id, ms, data)
	}
}

func (s *Server) metricsToPromFormat(w io.Writer, prefix string, ts int64, val interface{}) {
	l := s.logger.With().Str("op", "prom export").Logger()
	switch t := val.(type) {
	case cgm.Metric:
		metric := val.(cgm.Metric)
		sv := fmt.Sprintf("%v", metric.Value)
		switch metric.Type {
		case "i":
			fallthrough
		case "I":
			fallthrough
		case "l":
			fallthrough
		case "L":
			v, err := strconv.ParseInt(sv, 10, 64)
			if err != nil {
				l.Error().Err(err).Msg("conv int64")
				return
			}
			if _, err := w.Write([]byte(fmt.Sprintf("%s %d %d\n", prefix, v, ts))); err != nil {
				l.Error().Err(err).Msg("writing prom output")
			}
		case "n":
			if strings.Contains(sv, "[H[") {
				l.Warn().
					Str("type", "histogram != [prom]histogram(percentile)").
					Str("metric", fmt.Sprintf("%s = %s", prefix, sv)).
					Msg("unsupported metric type")
			} else {
				v, err := strconv.ParseFloat(sv, 64)
				if err != nil {
					l.Error().Err(err).Msg("conv float64")
					return
				}
				if _, err := w.Write([]byte(fmt.Sprintf("%s %f %d\n", prefix, v, ts))); err != nil {
					l.Error().Err(err).Msg("writing prom output")
				}
			}
		case "s":
			l.Warn().
				Str("type", "text [prom]???").
				Str("metric", fmt.Sprintf("%s = %s", prefix, sv)).
				Msg("unsuported metric type")
		default:
			l.Warn().
				Str("type", metric.Type).
				Str("name", prefix).
				Interface("metric", metric).
				Msg("invalid metric type")
		}
	case cgm.Metrics:
		metrics := val.(cgm.Metrics)
		for pfx, metric := range metrics {
			name := prefix
			if pfx != "" {
				name = strings.Join([]string{name, pfx}, config.MetricNameSeparator)
			}
			s.metricsToPromFormat(w, name, ts, metric)
		}
	case *cgm.Metrics:
		metrics := val.(*cgm.Metrics)
		s.metricsToPromFormat(w, prefix, ts, *metrics)
	default:
		l.Warn().
			Str("metric", fmt.Sprintf("#TYPE(%T) %v = %#v", t, prefix, val)).
			Msg("unhandled export type")
	}
}
<|endoftext|>


<commit_before>// Package storage hold and abstraction of the filesystem

package storage

import (
	"io"
	"io/fs"
)

// Storage is an abstraction of the filesystem
type Storage interface {
	fs.FS
	// WriteFile writes data to the named file, creating it if necessary. If the file does not exist, WriteFile creates it with permissions perm (before umask); otherwise WriteFile truncates it before writing, without changing permissions.
	WriteFile(name string, data []byte, perm fs.FileMode) error
	// Mkdir creates a new directory with the specified name and permission bits (before umask). If there is an error, it will be of type *PathError.
	Mkdir(name string, perm fs.FileMode) error
	// RemoveAll removes path and any children it contains. It removes everything it can but returns the first error it encounters. If the path does not exist, RemoveAll returns nil (no error). If there is an error, it will be of type *PathError.
	RemoveAll(name string) error
	// Create creates or truncates the named file. If the file already exists, it is truncated. If the file does not exist, it is created with mode 0666 (before umask). If successful, methods on the returned File can be used for I/O; the associated file descriptor has mode O_RDWR. If there is an error, it will be of type *PathError.
	Create(name string) (File, error)
}

type File interface {
	fs.File
	io.Writer
}
<commit_msg>feat: add utility functions<commit_after>// Package storage hold and abstraction of the filesystem

package storage

import (
	"io"
	"io/fs"
	"io/ioutil"
	"os"
	"path/filepath"
)

// Storage is an abstraction of the filesystem
type Storage interface {
	fs.FS
	// WriteFile writes data to the named file, creating it if necessary. If the file does not exist, WriteFile creates it with permissions perm (before umask); otherwise WriteFile truncates it before writing, without changing permissions.
	WriteFile(name string, data []byte, perm fs.FileMode) error
	// Mkdir creates a new directory with the specified name and permission bits (before umask). If there is an error, it will be of type *PathError.
	Mkdir(name string, perm fs.FileMode) error
	// RemoveAll removes path and any children it contains. It removes everything it can but returns the first error it encounters. If the path does not exist, RemoveAll returns nil (no error). If there is an error, it will be of type *PathError.
	RemoveAll(name string) error
	// Create creates or truncates the named file. If the file already exists, it is truncated. If the file does not exist, it is created with mode 0666 (before umask). If successful, methods on the returned File can be used for I/O; the associated file descriptor has mode O_RDWR. If there is an error, it will be of type *PathError.
	Create(name string) (File, error)
}

type File interface {
	fs.File
	io.Writer
}

// ReadFile returns the content of name in the filesystem
func ReadFile(fs Storage, name string) ([]byte, error) {
	f, err := fs.Open(name)
	if err != nil {
		return nil, err
	}
	defer f.Close()
	return ioutil.ReadAll(f)
}
func MkdirAll(fs Storage, dir string, perm fs.FileMode) error {
	list := make([]string, 0)
	for dir := filepath.Dir(dir); dir != string(filepath.Separator) && dir != "."; dir = filepath.Dir(dir) {
		list = append(list, dir)
	}
	for i := len(list); i > 0; i-- {
		err := fs.Mkdir(list[i-1], perm)
		if err != nil && !os.IsExist(err) {
			return err
		}
	}
	return nil

}
<|endoftext|>


<commit_before>// Package streams provide an event stream from Reddit. This package abstracts
// dispatcher.
package streams

import (
	"log"

	"github.com/turnage/graw/internal/api/lurker"
	"github.com/turnage/graw/internal/dispatcher"
	"github.com/turnage/graw/internal/handlers"
	"github.com/turnage/graw/internal/monitor"
	"github.com/turnage/graw/internal/reap"
	"github.com/turnage/graw/internal/rsort"
)

// Config configures a stream set.
type Config struct {
	// LoggedIn indicates whether the bot is running as a logged in user.
	LoggedIn bool
	// Subreddits are a set of subreddits to generate events for new posts
	// in.
	Subreddits       []string
	SubredditHandler handlers.SubredditHandler
	// Users are a set of users to generate events for new posts or comments
	// they make.
	Users       []string
	UserHandler handlers.UserHandler
	// Inbox indicates whether the bot wants events from its inbox.
	Inbox        bool
	InboxHandler handlers.InboxHandler
	// Reaper is the reaper used to make requests.
	Reaper reap.Reaper
	// Bot is the bot who will be fed events for processing.
	Bot interface{}
}

// singleConfig configures one stream. All sources will be reduced to a stream
// config before building.
type singleConfig struct {
	// path is the url path to the event source.
	path string

	ph handlers.PostHandler
	ch handlers.CommentHandler
	mh handlers.MessageHandler
}

func New(c Config) ([]dispatcher.Dispatcher, error) {
	var cfgs []singleConfig

	if cfg, err := subreddits(c.Subreddits, c.SubredditHandler); err != nil {
		return nil, err
	} else {
		cfgs = append(cfgs, cfg)
	}

	if cs, err := users(c.Users, c.UserHandler); err != nil {
		return nil, err
	} else {
		cfgs = append(cfgs, cs...)
	}

	if c.Inbox {
		if cfg, err := inbox(c.LoggedIn, c.InboxHandler); err != nil {
			return nil, err
		} else {
			cfgs = append(cfgs, cfg)
		}
	}

	var streams []dispatcher.Dispatcher
	for _, sc := range cfgs {
		if sc.path == "" {
			continue
		}

		if !c.LoggedIn {
			sc.path = logPathOut(sc.path)
		}

		log.Printf("Adding cfg: %v\n", sc)

		if mon, err := monitor.New(
			monitor.Config{
				Path:   sc.path,
				Lurker: lurker.New(c.Reaper),
				Sorter: rsort.New(),
			},
		); err != nil {
			return nil, err
		} else {
			streams = append(
				streams, dispatcher.New(
					dispatcher.Config{
						Monitor:        mon,
						PostHandler:    sc.ph,
						CommentHandler: sc.ch,
						MessageHandler: sc.mh,
					},
				),
			)
		}

	}

	return streams, nil
}
<commit_msg>Remove logging statement impulsively added.<commit_after>// Package streams provide an event stream from Reddit. This package abstracts
// dispatcher.
package streams

import (
	"github.com/turnage/graw/internal/api/lurker"
	"github.com/turnage/graw/internal/dispatcher"
	"github.com/turnage/graw/internal/handlers"
	"github.com/turnage/graw/internal/monitor"
	"github.com/turnage/graw/internal/reap"
	"github.com/turnage/graw/internal/rsort"
)

// Config configures a stream set.
type Config struct {
	// LoggedIn indicates whether the bot is running as a logged in user.
	LoggedIn bool
	// Subreddits are a set of subreddits to generate events for new posts
	// in.
	Subreddits       []string
	SubredditHandler handlers.SubredditHandler
	// Users are a set of users to generate events for new posts or comments
	// they make.
	Users       []string
	UserHandler handlers.UserHandler
	// Inbox indicates whether the bot wants events from its inbox.
	Inbox        bool
	InboxHandler handlers.InboxHandler
	// Reaper is the reaper used to make requests.
	Reaper reap.Reaper
	// Bot is the bot who will be fed events for processing.
	Bot interface{}
}

// singleConfig configures one stream. All sources will be reduced to a stream
// config before building.
type singleConfig struct {
	// path is the url path to the event source.
	path string

	ph handlers.PostHandler
	ch handlers.CommentHandler
	mh handlers.MessageHandler
}

func New(c Config) ([]dispatcher.Dispatcher, error) {
	var cfgs []singleConfig

	if cfg, err := subreddits(c.Subreddits, c.SubredditHandler); err != nil {
		return nil, err
	} else {
		cfgs = append(cfgs, cfg)
	}

	if cs, err := users(c.Users, c.UserHandler); err != nil {
		return nil, err
	} else {
		cfgs = append(cfgs, cs...)
	}

	if c.Inbox {
		if cfg, err := inbox(c.LoggedIn, c.InboxHandler); err != nil {
			return nil, err
		} else {
			cfgs = append(cfgs, cfg)
		}
	}

	var streams []dispatcher.Dispatcher
	for _, sc := range cfgs {
		if sc.path == "" {
			continue
		}

		if !c.LoggedIn {
			sc.path = logPathOut(sc.path)
		}

		if mon, err := monitor.New(
			monitor.Config{
				Path:   sc.path,
				Lurker: lurker.New(c.Reaper),
				Sorter: rsort.New(),
			},
		); err != nil {
			return nil, err
		} else {
			streams = append(
				streams, dispatcher.New(
					dispatcher.Config{
						Monitor:        mon,
						PostHandler:    sc.ph,
						CommentHandler: sc.ch,
						MessageHandler: sc.mh,
					},
				),
			)
		}

	}

	return streams, nil
}
<|endoftext|>


<commit_before>package api

import (
	"fmt"
	"github.com/Aptomi/aptomi/pkg/engine/apply/action"
	"github.com/Aptomi/aptomi/pkg/engine/diff"
	"github.com/Aptomi/aptomi/pkg/engine/resolve"
	"github.com/Aptomi/aptomi/pkg/event"
	"github.com/Aptomi/aptomi/pkg/lang"
	"github.com/Aptomi/aptomi/pkg/runtime"
	"github.com/julienschmidt/httprouter"
	"github.com/sirupsen/logrus"
	"net/http"
	"strconv"
)

func (api *coreAPI) handlePolicyGet(writer http.ResponseWriter, request *http.Request, params httprouter.Params) {
	gen := params.ByName("gen")

	if len(gen) == 0 {
		gen = strconv.Itoa(int(runtime.LastGen))
	}

	policyData, err := api.store.GetPolicyData(runtime.ParseGeneration(gen))
	if err != nil {
		panic(fmt.Sprintf("error while getting requested policy: %s", err))
	}

	if policyData == nil {
		// policy with the given generation not found
		api.contentType.WriteOneWithStatus(writer, request, nil, http.StatusNotFound)
	} else {
		api.contentType.WriteOne(writer, request, policyData)
	}
}

func (api *coreAPI) handlePolicyObjectGet(writer http.ResponseWriter, request *http.Request, params httprouter.Params) {
	gen := params.ByName("gen")

	if len(gen) == 0 {
		gen = strconv.Itoa(int(runtime.LastGen))
	}

	policy, _, err := api.store.GetPolicy(runtime.ParseGeneration(gen))
	if err != nil {
		panic(fmt.Sprintf("error while getting requested policy: %s", err))
	}

	ns := params.ByName("ns")
	kind := params.ByName("kind")
	name := params.ByName("name")

	obj, err := policy.GetObject(kind, name, ns)
	if err != nil {
		panic(fmt.Sprintf("error while getting object %s/%s/%s in policy #%s", ns, kind, name, gen))
	}
	if obj == nil {
		api.contentType.WriteOneWithStatus(writer, request, nil, http.StatusNotFound)
	}

	api.contentType.WriteOne(writer, request, obj)
}

// PolicyUpdateResultObject is an informational data structure with Kind and Constructor for PolicyUpdateResult
var PolicyUpdateResultObject = &runtime.Info{
	Kind:        "policy-update-result",
	Constructor: func() runtime.Object { return &PolicyUpdateResult{} },
}

// PolicyUpdateResult represents results of the policy update request, including action plan and event log
type PolicyUpdateResult struct {
	runtime.TypeKind `yaml:",inline"`
	PolicyGeneration runtime.Generation
	PolicyChanged    bool
	WaitForRevision  runtime.Generation
	PlanAsText       *action.PlanAsText
	EventLog         []*event.APIEvent
}

// GetDefaultColumns returns default set of columns to be displayed
func (result *PolicyUpdateResult) GetDefaultColumns() []string {
	return []string{"Policy Generation", "Action Plan"}
}

// AsColumns returns PolicyUpdateResult representation as columns
func (result *PolicyUpdateResult) AsColumns() map[string]string {
	var policyChangesStr string
	if result.PolicyChanged {
		policyChangesStr = fmt.Sprintf("%d -> %d", result.PolicyGeneration-1, result.PolicyGeneration)
	} else {
		policyChangesStr = fmt.Sprintf("%d", result.PolicyGeneration)
	}
	var actionPlanStr = result.PlanAsText.String()
	if len(actionPlanStr) <= 0 {
		actionPlanStr = "(none)"
	}
	return map[string]string{
		"Policy Generation": policyChangesStr,
		"Action Plan":       actionPlanStr,
	}
}

func (api *coreAPI) handlePolicyUpdate(writer http.ResponseWriter, request *http.Request, params httprouter.Params) { // nolint: gocyclo
	objects := api.readLang(request)
	user := api.getUserRequired(request)

	// Load the latest policy
	policy, policyGen, err := api.store.GetPolicy(runtime.LastGen)
	if err != nil {
		panic(fmt.Sprintf("error while loading current policy: %s", err))
	}

	// load the latest revision for the given policy
	revision, err := api.store.GetLastRevisionForPolicy(policyGen)
	if err != nil {
		panic(fmt.Sprintf("error while loading latest revision from the store: %s", err))
	}

	// load desired state
	desiredState, err := api.store.GetDesiredState(revision, policy, api.externalData)
	if err != nil {
		panic(fmt.Sprintf("can't load desired state from revision: %s", err))
	}

	// Make a copy of the latest policy, so we can apply changes to it
	policyUpdated, _, err := api.store.GetPolicy(policyGen)
	if err != nil {
		panic(fmt.Sprintf("error while loading current policy: %s", err))
	}

	// Verify that user has permissions to create and update objects
	for _, obj := range objects {
		errAdd := policyUpdated.AddObject(obj)
		if errAdd != nil {
			panic(fmt.Sprintf("error while adding updated object to policy: %s", errAdd))
		}
		errManage := policyUpdated.View(user).ManageObject(obj)
		if errManage != nil {
			panic(fmt.Sprintf("error while adding updated object to policy: %s", errManage))
		}
	}

	// Check that the policy is valid
	err = policyUpdated.Validate()
	if err != nil {
		panic(fmt.Sprintf("updated policy is invalid: %s", err))
	}

	// Validate clusters using corresponding cluster plugins and make sure there are no conflicts
	plugins := api.pluginRegistryFactory()
	for _, obj := range objects {
		// if a cluster was supplied, then
		if cluster, ok := obj.(*lang.Cluster); ok {
			// if a cluster is already present in the policy, tell a user that it can't be modified
			objExisting, _ := policy.GetObject(lang.ClusterObject.Kind, cluster.Name, cluster.Namespace)
			if objExisting != nil {
				panic(fmt.Sprintf("modification of existing cluster objects is not allowed: %s needs to be deleted first", cluster.Name))
			}

			// validate via plugin that connection to it can be established
			plugin, pluginErr := plugins.ForCluster(cluster)
			if pluginErr != nil {
				panic(fmt.Sprintf("error while getting cluster plugin for cluster %s of type %s: %s", cluster.Name, cluster.Type, pluginErr))
			}

			valErr := plugin.Validate()
			if valErr != nil {
				panic(fmt.Sprintf("error while validating cluster %s of type %s: %s", cluster.Name, cluster.Type, valErr))
			}
		}
	}

	// See if noop flag is set
	noop, noopErr := strconv.ParseBool(params.ByName("noop"))
	if noopErr != nil {
		noop = false
	}

	// See what log level is set
	logLevel, logLevelErr := logrus.ParseLevel(params.ByName("loglevel"))
	if logLevelErr != nil {
		logLevel = logrus.WarnLevel
	}

	// Process policy changes, calculate resolution log and action plan
	eventLog := event.NewLog(logLevel, "api-policy-update").AddConsoleHook(api.logLevel)
	desiredStateUpdated := resolve.NewPolicyResolver(policyUpdated, api.externalData, eventLog).ResolveAllDependencies()
	err = desiredStateUpdated.Validate(policyUpdated)
	if err != nil {
		panic(fmt.Sprintf("policy change cannon be made: %s", err))
	}

	actionPlan := diff.NewPolicyResolutionDiff(desiredStateUpdated, desiredState).ActionPlan

	// If we are in noop mode, just return expected changes in a form of an action plan
	if noop {
		api.contentType.WriteOne(writer, request, &PolicyUpdateResult{
			TypeKind:         PolicyUpdateResultObject.GetTypeKind(),
			PolicyGeneration: policyGen,              // policy generation didn't change
			PolicyChanged:    false,                  // policy has not been updated in the store
			WaitForRevision:  runtime.MaxGeneration,  // nothing to wait for
			PlanAsText:       actionPlan.AsText(),    // return action plan, so it can be printed by the client
			EventLog:         eventLog.AsAPIEvents(), // return policy resolution log
		})
		return
	}

	// Here we need to take mutex to handle policy and revision updates
	api.policyAndRevisionUpdateMutex.Lock()
	defer api.policyAndRevisionUpdateMutex.Unlock()

	// Make object changes in the store
	changed, policyData, err := api.store.UpdatePolicy(objects, user.Name)
	if err != nil {
		panic(fmt.Sprintf("error while updating objects in policy: %s", err))
	}

	// If there are changes, create a new revision and say that we should wait for it
	waitForRevision := runtime.MaxGeneration
	if changed {
		newRevision, newRevisionErr := api.store.NewRevision(policyData.GetGeneration(), desiredState, false)
		if newRevisionErr != nil {
			panic(fmt.Errorf("unable to create new revision for policy gen %d", policyGen))
		}
		waitForRevision = newRevision.GetGeneration()
	}

	api.contentType.WriteOne(writer, request, &PolicyUpdateResult{
		TypeKind:         PolicyUpdateResultObject.GetTypeKind(),
		PolicyGeneration: policyData.GetGeneration(), // policy now has a new generation
		PolicyChanged:    changed,                    // have any policy object in the store been changed or not
		WaitForRevision:  waitForRevision,            // which revision to wait for
		PlanAsText:       actionPlan.AsText(),        // return action plan, so it can be printed by the client
		EventLog:         eventLog.AsAPIEvents(),     // return policy resolution log
	})

	if changed {
		// signal to the channel that policy has changed, that will trigger the enforcement right away
		api.runDesiredStateEnforcement <- true
	}

}

func (api *coreAPI) handlePolicyDelete(writer http.ResponseWriter, request *http.Request, params httprouter.Params) {
	objects := api.readLang(request)
	user := api.getUserRequired(request)

	// Load the latest policy
	policy, policyGen, err := api.store.GetPolicy(runtime.LastGen)
	if err != nil {
		panic(fmt.Sprintf("error while loading current policy: %s", err))
	}

	// Load the latest revision for the given policy
	revision, err := api.store.GetLastRevisionForPolicy(policyGen)
	if err != nil {
		panic(fmt.Sprintf("error while loading latest revision from the store: %s", err))
	}

	// Load desired state
	desiredState, err := api.store.GetDesiredState(revision, policy, api.externalData)
	if err != nil {
		panic(fmt.Sprintf("can't load desired state from revision: %s", err))
	}

	// Make a copy of the latest policy, so we can apply changes to it
	policyUpdated, _, err := api.store.GetPolicy(policyGen)
	if err != nil {
		panic(fmt.Sprintf("error while loading current policy: %s", err))
	}

	// Verify that user has permissions to delete objects
	for _, obj := range objects {
		errManage := policyUpdated.View(user).ManageObject(obj)
		if errManage != nil {
			panic(fmt.Sprintf("Error while removing object from policy: %s", errManage))
		}
		policyUpdated.RemoveObject(obj)
	}

	err = policyUpdated.Validate()
	if err != nil {
		panic(fmt.Sprintf("Updated policy is invalid: %s", err))
	}

	// See if noop flag is set
	noop, noopErr := strconv.ParseBool(params.ByName("noop"))
	if noopErr != nil {
		noop = false
	}

	// See what log level is set
	logLevel, logLevelErr := logrus.ParseLevel(params.ByName("loglevel"))
	if logLevelErr != nil {
		logLevel = logrus.WarnLevel
	}

	// Process policy changes, calculate and return resolution log + action plan
	eventLog := event.NewLog(logLevel, "api-policy-delete").AddConsoleHook(api.logLevel)
	desiredStateUpdated := resolve.NewPolicyResolver(policyUpdated, api.externalData, eventLog).ResolveAllDependencies()
	err = desiredStateUpdated.Validate(policyUpdated)
	if err != nil {
		panic(fmt.Sprintf("policy change cannon be made: %s", err))
	}

	actionPlan := diff.NewPolicyResolutionDiff(desiredStateUpdated, desiredState).ActionPlan

	// If we are in noop mode, just return expected changes in a form of an action plan
	if noop {
		api.contentType.WriteOne(writer, request, &PolicyUpdateResult{
			TypeKind:         PolicyUpdateResultObject.GetTypeKind(),
			PolicyGeneration: policyGen,              // policy generation didn't change
			PolicyChanged:    false,                  // policy has not been updated in the store
			WaitForRevision:  runtime.MaxGeneration,  // nothing to wait for
			PlanAsText:       actionPlan.AsText(),    // return action plan, so it can be printed by the client
			EventLog:         eventLog.AsAPIEvents(), // return policy resolution log
		})
		return
	}

	// Here we need to take mutex to handle policy and revision updates
	api.policyAndRevisionUpdateMutex.Lock()
	defer api.policyAndRevisionUpdateMutex.Unlock()

	// Make object changes in the store
	changed, policyData, err := api.store.DeleteFromPolicy(objects, user.Name)
	if err != nil {
		panic(fmt.Sprintf("error while deleting objects from policy: %s", err))
	}

	// If there are changes, create a new revision and say that we should wait for it
	waitForRevision := runtime.MaxGeneration
	if changed {
		newRevision, newRevisionErr := api.store.NewRevision(policyData.GetGeneration(), desiredState, false)
		if newRevisionErr != nil {
			panic(fmt.Errorf("unable to create new revision for policy gen %d", policyGen))
		}
		waitForRevision = newRevision.GetGeneration()
	}

	api.contentType.WriteOne(writer, request, &PolicyUpdateResult{
		TypeKind:         PolicyUpdateResultObject.GetTypeKind(),
		PolicyGeneration: policyData.GetGeneration(), // policy now has a new generation
		PolicyChanged:    changed,                    // have any policy object in the store been changed or not
		WaitForRevision:  waitForRevision,            // which revision to wait for
		PlanAsText:       actionPlan.AsText(),        // return action plan, so it can be printed by the client
		EventLog:         eventLog.AsAPIEvents(),     // return policy resolution log
	})

	if changed {
		// signal to the channel that policy has changed, that will trigger the enforcement right away
		api.runDesiredStateEnforcement <- true
	}

}
<commit_msg>Fix new revision creation by using updated desired state<commit_after>package api

import (
	"fmt"
	"github.com/Aptomi/aptomi/pkg/engine/apply/action"
	"github.com/Aptomi/aptomi/pkg/engine/diff"
	"github.com/Aptomi/aptomi/pkg/engine/resolve"
	"github.com/Aptomi/aptomi/pkg/event"
	"github.com/Aptomi/aptomi/pkg/lang"
	"github.com/Aptomi/aptomi/pkg/runtime"
	"github.com/julienschmidt/httprouter"
	"github.com/sirupsen/logrus"
	"net/http"
	"strconv"
)

func (api *coreAPI) handlePolicyGet(writer http.ResponseWriter, request *http.Request, params httprouter.Params) {
	gen := params.ByName("gen")

	if len(gen) == 0 {
		gen = strconv.Itoa(int(runtime.LastGen))
	}

	policyData, err := api.store.GetPolicyData(runtime.ParseGeneration(gen))
	if err != nil {
		panic(fmt.Sprintf("error while getting requested policy: %s", err))
	}

	if policyData == nil {
		// policy with the given generation not found
		api.contentType.WriteOneWithStatus(writer, request, nil, http.StatusNotFound)
	} else {
		api.contentType.WriteOne(writer, request, policyData)
	}
}

func (api *coreAPI) handlePolicyObjectGet(writer http.ResponseWriter, request *http.Request, params httprouter.Params) {
	gen := params.ByName("gen")

	if len(gen) == 0 {
		gen = strconv.Itoa(int(runtime.LastGen))
	}

	policy, _, err := api.store.GetPolicy(runtime.ParseGeneration(gen))
	if err != nil {
		panic(fmt.Sprintf("error while getting requested policy: %s", err))
	}

	ns := params.ByName("ns")
	kind := params.ByName("kind")
	name := params.ByName("name")

	obj, err := policy.GetObject(kind, name, ns)
	if err != nil {
		panic(fmt.Sprintf("error while getting object %s/%s/%s in policy #%s", ns, kind, name, gen))
	}
	if obj == nil {
		api.contentType.WriteOneWithStatus(writer, request, nil, http.StatusNotFound)
	}

	api.contentType.WriteOne(writer, request, obj)
}

// PolicyUpdateResultObject is an informational data structure with Kind and Constructor for PolicyUpdateResult
var PolicyUpdateResultObject = &runtime.Info{
	Kind:        "policy-update-result",
	Constructor: func() runtime.Object { return &PolicyUpdateResult{} },
}

// PolicyUpdateResult represents results of the policy update request, including action plan and event log
type PolicyUpdateResult struct {
	runtime.TypeKind `yaml:",inline"`
	PolicyGeneration runtime.Generation
	PolicyChanged    bool
	WaitForRevision  runtime.Generation
	PlanAsText       *action.PlanAsText
	EventLog         []*event.APIEvent
}

// GetDefaultColumns returns default set of columns to be displayed
func (result *PolicyUpdateResult) GetDefaultColumns() []string {
	return []string{"Policy Generation", "Action Plan"}
}

// AsColumns returns PolicyUpdateResult representation as columns
func (result *PolicyUpdateResult) AsColumns() map[string]string {
	var policyChangesStr string
	if result.PolicyChanged {
		policyChangesStr = fmt.Sprintf("%d -> %d", result.PolicyGeneration-1, result.PolicyGeneration)
	} else {
		policyChangesStr = fmt.Sprintf("%d", result.PolicyGeneration)
	}
	var actionPlanStr = result.PlanAsText.String()
	if len(actionPlanStr) <= 0 {
		actionPlanStr = "(none)"
	}
	return map[string]string{
		"Policy Generation": policyChangesStr,
		"Action Plan":       actionPlanStr,
	}
}

func (api *coreAPI) handlePolicyUpdate(writer http.ResponseWriter, request *http.Request, params httprouter.Params) { // nolint: gocyclo
	objects := api.readLang(request)
	user := api.getUserRequired(request)

	// Load the latest policy
	policy, policyGen, err := api.store.GetPolicy(runtime.LastGen)
	if err != nil {
		panic(fmt.Sprintf("error while loading current policy: %s", err))
	}

	// load the latest revision for the given policy
	revision, err := api.store.GetLastRevisionForPolicy(policyGen)
	if err != nil {
		panic(fmt.Sprintf("error while loading latest revision from the store: %s", err))
	}

	// load desired state
	desiredState, err := api.store.GetDesiredState(revision, policy, api.externalData)
	if err != nil {
		panic(fmt.Sprintf("can't load desired state from revision: %s", err))
	}

	// Make a copy of the latest policy, so we can apply changes to it
	policyUpdated, _, err := api.store.GetPolicy(policyGen)
	if err != nil {
		panic(fmt.Sprintf("error while loading current policy: %s", err))
	}

	// Verify that user has permissions to create and update objects
	for _, obj := range objects {
		errAdd := policyUpdated.AddObject(obj)
		if errAdd != nil {
			panic(fmt.Sprintf("error while adding updated object to policy: %s", errAdd))
		}
		errManage := policyUpdated.View(user).ManageObject(obj)
		if errManage != nil {
			panic(fmt.Sprintf("error while adding updated object to policy: %s", errManage))
		}
	}

	// Check that the policy is valid
	err = policyUpdated.Validate()
	if err != nil {
		panic(fmt.Sprintf("updated policy is invalid: %s", err))
	}

	// Validate clusters using corresponding cluster plugins and make sure there are no conflicts
	plugins := api.pluginRegistryFactory()
	for _, obj := range objects {
		// if a cluster was supplied, then
		if cluster, ok := obj.(*lang.Cluster); ok {
			// if a cluster is already present in the policy, tell a user that it can't be modified
			objExisting, _ := policy.GetObject(lang.ClusterObject.Kind, cluster.Name, cluster.Namespace)
			if objExisting != nil {
				panic(fmt.Sprintf("modification of existing cluster objects is not allowed: %s needs to be deleted first", cluster.Name))
			}

			// validate via plugin that connection to it can be established
			plugin, pluginErr := plugins.ForCluster(cluster)
			if pluginErr != nil {
				panic(fmt.Sprintf("error while getting cluster plugin for cluster %s of type %s: %s", cluster.Name, cluster.Type, pluginErr))
			}

			valErr := plugin.Validate()
			if valErr != nil {
				panic(fmt.Sprintf("error while validating cluster %s of type %s: %s", cluster.Name, cluster.Type, valErr))
			}
		}
	}

	// See if noop flag is set
	noop, noopErr := strconv.ParseBool(params.ByName("noop"))
	if noopErr != nil {
		noop = false
	}

	// See what log level is set
	logLevel, logLevelErr := logrus.ParseLevel(params.ByName("loglevel"))
	if logLevelErr != nil {
		logLevel = logrus.WarnLevel
	}

	// Process policy changes, calculate resolution log and action plan
	eventLog := event.NewLog(logLevel, "api-policy-update").AddConsoleHook(api.logLevel)
	desiredStateUpdated := resolve.NewPolicyResolver(policyUpdated, api.externalData, eventLog).ResolveAllDependencies()
	err = desiredStateUpdated.Validate(policyUpdated)
	if err != nil {
		panic(fmt.Sprintf("policy change cannon be made: %s", err))
	}

	actionPlan := diff.NewPolicyResolutionDiff(desiredStateUpdated, desiredState).ActionPlan

	// If we are in noop mode, just return expected changes in a form of an action plan
	if noop {
		api.contentType.WriteOne(writer, request, &PolicyUpdateResult{
			TypeKind:         PolicyUpdateResultObject.GetTypeKind(),
			PolicyGeneration: policyGen,              // policy generation didn't change
			PolicyChanged:    false,                  // policy has not been updated in the store
			WaitForRevision:  runtime.MaxGeneration,  // nothing to wait for
			PlanAsText:       actionPlan.AsText(),    // return action plan, so it can be printed by the client
			EventLog:         eventLog.AsAPIEvents(), // return policy resolution log
		})
		return
	}

	// Here we need to take mutex to handle policy and revision updates
	api.policyAndRevisionUpdateMutex.Lock()
	defer api.policyAndRevisionUpdateMutex.Unlock()

	// Make object changes in the store
	changed, policyData, err := api.store.UpdatePolicy(objects, user.Name)
	if err != nil {
		panic(fmt.Sprintf("error while updating objects in policy: %s", err))
	}

	// If there are changes, create a new revision and say that we should wait for it
	waitForRevision := runtime.MaxGeneration
	if changed {
		newRevision, newRevisionErr := api.store.NewRevision(policyData.GetGeneration(), desiredStateUpdated, false)
		if newRevisionErr != nil {
			panic(fmt.Errorf("unable to create new revision for policy gen %d", policyGen))
		}
		waitForRevision = newRevision.GetGeneration()
	}

	api.contentType.WriteOne(writer, request, &PolicyUpdateResult{
		TypeKind:         PolicyUpdateResultObject.GetTypeKind(),
		PolicyGeneration: policyData.GetGeneration(), // policy now has a new generation
		PolicyChanged:    changed,                    // have any policy object in the store been changed or not
		WaitForRevision:  waitForRevision,            // which revision to wait for
		PlanAsText:       actionPlan.AsText(),        // return action plan, so it can be printed by the client
		EventLog:         eventLog.AsAPIEvents(),     // return policy resolution log
	})

	if changed {
		// signal to the channel that policy has changed, that will trigger the enforcement right away
		api.runDesiredStateEnforcement <- true
	}

}

func (api *coreAPI) handlePolicyDelete(writer http.ResponseWriter, request *http.Request, params httprouter.Params) {
	objects := api.readLang(request)
	user := api.getUserRequired(request)

	// Load the latest policy
	policy, policyGen, err := api.store.GetPolicy(runtime.LastGen)
	if err != nil {
		panic(fmt.Sprintf("error while loading current policy: %s", err))
	}

	// Load the latest revision for the given policy
	revision, err := api.store.GetLastRevisionForPolicy(policyGen)
	if err != nil {
		panic(fmt.Sprintf("error while loading latest revision from the store: %s", err))
	}

	// Load desired state
	desiredState, err := api.store.GetDesiredState(revision, policy, api.externalData)
	if err != nil {
		panic(fmt.Sprintf("can't load desired state from revision: %s", err))
	}

	// Make a copy of the latest policy, so we can apply changes to it
	policyUpdated, _, err := api.store.GetPolicy(policyGen)
	if err != nil {
		panic(fmt.Sprintf("error while loading current policy: %s", err))
	}

	// Verify that user has permissions to delete objects
	for _, obj := range objects {
		errManage := policyUpdated.View(user).ManageObject(obj)
		if errManage != nil {
			panic(fmt.Sprintf("Error while removing object from policy: %s", errManage))
		}
		policyUpdated.RemoveObject(obj)
	}

	err = policyUpdated.Validate()
	if err != nil {
		panic(fmt.Sprintf("Updated policy is invalid: %s", err))
	}

	// See if noop flag is set
	noop, noopErr := strconv.ParseBool(params.ByName("noop"))
	if noopErr != nil {
		noop = false
	}

	// See what log level is set
	logLevel, logLevelErr := logrus.ParseLevel(params.ByName("loglevel"))
	if logLevelErr != nil {
		logLevel = logrus.WarnLevel
	}

	// Process policy changes, calculate and return resolution log + action plan
	eventLog := event.NewLog(logLevel, "api-policy-delete").AddConsoleHook(api.logLevel)
	desiredStateUpdated := resolve.NewPolicyResolver(policyUpdated, api.externalData, eventLog).ResolveAllDependencies()
	err = desiredStateUpdated.Validate(policyUpdated)
	if err != nil {
		panic(fmt.Sprintf("policy change cannon be made: %s", err))
	}

	actionPlan := diff.NewPolicyResolutionDiff(desiredStateUpdated, desiredState).ActionPlan

	// If we are in noop mode, just return expected changes in a form of an action plan
	if noop {
		api.contentType.WriteOne(writer, request, &PolicyUpdateResult{
			TypeKind:         PolicyUpdateResultObject.GetTypeKind(),
			PolicyGeneration: policyGen,              // policy generation didn't change
			PolicyChanged:    false,                  // policy has not been updated in the store
			WaitForRevision:  runtime.MaxGeneration,  // nothing to wait for
			PlanAsText:       actionPlan.AsText(),    // return action plan, so it can be printed by the client
			EventLog:         eventLog.AsAPIEvents(), // return policy resolution log
		})
		return
	}

	// Here we need to take mutex to handle policy and revision updates
	api.policyAndRevisionUpdateMutex.Lock()
	defer api.policyAndRevisionUpdateMutex.Unlock()

	// Make object changes in the store
	changed, policyData, err := api.store.DeleteFromPolicy(objects, user.Name)
	if err != nil {
		panic(fmt.Sprintf("error while deleting objects from policy: %s", err))
	}

	// If there are changes, create a new revision and say that we should wait for it
	waitForRevision := runtime.MaxGeneration
	if changed {
		newRevision, newRevisionErr := api.store.NewRevision(policyData.GetGeneration(), desiredStateUpdated, false)
		if newRevisionErr != nil {
			panic(fmt.Errorf("unable to create new revision for policy gen %d", policyGen))
		}
		waitForRevision = newRevision.GetGeneration()
	}

	api.contentType.WriteOne(writer, request, &PolicyUpdateResult{
		TypeKind:         PolicyUpdateResultObject.GetTypeKind(),
		PolicyGeneration: policyData.GetGeneration(), // policy now has a new generation
		PolicyChanged:    changed,                    // have any policy object in the store been changed or not
		WaitForRevision:  waitForRevision,            // which revision to wait for
		PlanAsText:       actionPlan.AsText(),        // return action plan, so it can be printed by the client
		EventLog:         eventLog.AsAPIEvents(),     // return policy resolution log
	})

	if changed {
		// signal to the channel that policy has changed, that will trigger the enforcement right away
		api.runDesiredStateEnforcement <- true
	}

}
<|endoftext|>


<commit_before>// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Package testenv contains helper functions for skipping tests
// based on which tools are present in the environment.
package testenv

import (
	"bytes"
	"fmt"
	"go/build"
	"io/ioutil"
	"os"
	"runtime"
	"strings"
	"sync"
	"time"

	exec "golang.org/x/sys/execabs"
)

// Testing is an abstraction of a *testing.T.
type Testing interface {
	Skipf(format string, args ...interface{})
	Fatalf(format string, args ...interface{})
}

type helperer interface {
	Helper()
}

// packageMainIsDevel reports whether the module containing package main
// is a development version (if module information is available).
//
// Builds in GOPATH mode and builds that lack module information are assumed to
// be development versions.
var packageMainIsDevel = func() bool { return true }

var checkGoGoroot struct {
	once sync.Once
	err  error
}

func hasTool(tool string) error {
	if tool == "cgo" {
		enabled, err := cgoEnabled(false)
		if err != nil {
			return fmt.Errorf("checking cgo: %v", err)
		}
		if !enabled {
			return fmt.Errorf("cgo not enabled")
		}
		return nil
	}

	_, err := exec.LookPath(tool)
	if err != nil {
		return err
	}

	switch tool {
	case "patch":
		// check that the patch tools supports the -o argument
		temp, err := ioutil.TempFile("", "patch-test")
		if err != nil {
			return err
		}
		temp.Close()
		defer os.Remove(temp.Name())
		cmd := exec.Command(tool, "-o", temp.Name())
		if err := cmd.Run(); err != nil {
			return err
		}

	case "go":
		checkGoGoroot.once.Do(func() {
			// Ensure that the 'go' command found by exec.LookPath is from the correct
			// GOROOT. Otherwise, 'some/path/go test ./...' will test against some
			// version of the 'go' binary other than 'some/path/go', which is almost
			// certainly not what the user intended.
			out, err := exec.Command(tool, "env", "GOROOT").CombinedOutput()
			if err != nil {
				checkGoGoroot.err = err
				return
			}
			GOROOT := strings.TrimSpace(string(out))
			if GOROOT != runtime.GOROOT() {
				checkGoGoroot.err = fmt.Errorf("'go env GOROOT' does not match runtime.GOROOT:\n\tgo env: %s\n\tGOROOT: %s", GOROOT, runtime.GOROOT())
			}
		})
		if checkGoGoroot.err != nil {
			return checkGoGoroot.err
		}

	case "diff":
		// Check that diff is the GNU version, needed for the -u argument and
		// to report missing newlines at the end of files.
		out, err := exec.Command(tool, "-version").Output()
		if err != nil {
			return err
		}
		if !bytes.Contains(out, []byte("GNU diffutils")) {
			return fmt.Errorf("diff is not the GNU version")
		}
	}

	return nil
}

func cgoEnabled(bypassEnvironment bool) (bool, error) {
	cmd := exec.Command("go", "env", "CGO_ENABLED")
	if bypassEnvironment {
		cmd.Env = append(append([]string(nil), os.Environ()...), "CGO_ENABLED=")
	}
	out, err := cmd.CombinedOutput()
	if err != nil {
		return false, err
	}
	enabled := strings.TrimSpace(string(out))
	return enabled == "1", nil
}

func allowMissingTool(tool string) bool {
	if runtime.GOOS == "android" {
		// Android builds generally run tests on a separate machine from the build,
		// so don't expect any external tools to be available.
		return true
	}

	switch tool {
	case "cgo":
		if strings.HasSuffix(os.Getenv("GO_BUILDER_NAME"), "-nocgo") {
			// Explicitly disabled on -nocgo builders.
			return true
		}
		if enabled, err := cgoEnabled(true); err == nil && !enabled {
			// No platform support.
			return true
		}
	case "go":
		if os.Getenv("GO_BUILDER_NAME") == "illumos-amd64-joyent" {
			// Work around a misconfigured builder (see https://golang.org/issue/33950).
			return true
		}
	case "diff":
		if os.Getenv("GO_BUILDER_NAME") != "" {
			return true
		}
	case "patch":
		if os.Getenv("GO_BUILDER_NAME") != "" {
			return true
		}
	}

	// If a developer is actively working on this test, we expect them to have all
	// of its dependencies installed. However, if it's just a dependency of some
	// other module (for example, being run via 'go test all'), we should be more
	// tolerant of unusual environments.
	return !packageMainIsDevel()
}

// NeedsTool skips t if the named tool is not present in the path.
// As a special case, "cgo" means "go" is present and can compile cgo programs.
func NeedsTool(t Testing, tool string) {
	if t, ok := t.(helperer); ok {
		t.Helper()
	}
	err := hasTool(tool)
	if err == nil {
		return
	}
	if allowMissingTool(tool) {
		t.Skipf("skipping because %s tool not available: %v", tool, err)
	} else {
		t.Fatalf("%s tool not available: %v", tool, err)
	}
}

// NeedsGoPackages skips t if the go/packages driver (or 'go' tool) implied by
// the current process environment is not present in the path.
func NeedsGoPackages(t Testing) {
	if t, ok := t.(helperer); ok {
		t.Helper()
	}

	tool := os.Getenv("GOPACKAGESDRIVER")
	switch tool {
	case "off":
		// "off" forces go/packages to use the go command.
		tool = "go"
	case "":
		if _, err := exec.LookPath("gopackagesdriver"); err == nil {
			tool = "gopackagesdriver"
		} else {
			tool = "go"
		}
	}

	NeedsTool(t, tool)
}

// NeedsGoPackagesEnv skips t if the go/packages driver (or 'go' tool) implied
// by env is not present in the path.
func NeedsGoPackagesEnv(t Testing, env []string) {
	if t, ok := t.(helperer); ok {
		t.Helper()
	}

	for _, v := range env {
		if strings.HasPrefix(v, "GOPACKAGESDRIVER=") {
			tool := strings.TrimPrefix(v, "GOPACKAGESDRIVER=")
			if tool == "off" {
				NeedsTool(t, "go")
			} else {
				NeedsTool(t, tool)
			}
			return
		}
	}

	NeedsGoPackages(t)
}

// NeedsGoBuild skips t if the current system can't build programs with “go build”
// and then run them with os.StartProcess or exec.Command.
// android, and darwin/arm systems don't have the userspace go build needs to run,
// and js/wasm doesn't support running subprocesses.
func NeedsGoBuild(t Testing) {
	if t, ok := t.(helperer); ok {
		t.Helper()
	}

	NeedsTool(t, "go")

	switch runtime.GOOS {
	case "android", "js":
		t.Skipf("skipping test: %v can't build and run Go binaries", runtime.GOOS)
	case "darwin":
		if strings.HasPrefix(runtime.GOARCH, "arm") {
			t.Skipf("skipping test: darwin/arm can't build and run Go binaries")
		}
	}
}

// ExitIfSmallMachine emits a helpful diagnostic and calls os.Exit(0) if the
// current machine is a builder known to have scarce resources.
//
// It should be called from within a TestMain function.
func ExitIfSmallMachine() {
	switch b := os.Getenv("GO_BUILDER_NAME"); b {
	case "linux-arm-scaleway":
		// "linux-arm" was renamed to "linux-arm-scaleway" in CL 303230.
		fmt.Fprintln(os.Stderr, "skipping test: linux-arm-scaleway builder lacks sufficient memory (https://golang.org/issue/32834)")
	case "plan9-arm":
		fmt.Fprintln(os.Stderr, "skipping test: plan9-arm builder lacks sufficient memory (https://golang.org/issue/38772)")
	case "netbsd-arm-bsiegert", "netbsd-arm64-bsiegert":
		// As of 2021-06-02, these builders are running with GO_TEST_TIMEOUT_SCALE=10,
		// and there is only one of each. We shouldn't waste those scarce resources
		// running very slow tests.
		fmt.Fprintf(os.Stderr, "skipping test: %s builder is very slow\n", b)
	case "dragonfly-amd64":
		// As of 2021-11-02, this builder is running with GO_TEST_TIMEOUT_SCALE=2,
		// and seems to have unusually slow disk performance.
		fmt.Fprintln(os.Stderr, "skipping test: dragonfly-amd64 has slow disk (https://golang.org/issue/45216)")
	case "linux-riscv64-unmatched":
		// As of 2021-11-03, this builder is empirically not fast enough to run
		// gopls tests. Ideally we should make the tests faster in short mode
		// and/or fix them to not assume arbitrary deadlines.
		// For now, we'll skip them instead.
		fmt.Fprintf(os.Stderr, "skipping test: %s builder is too slow (https://golang.org/issue/49321)\n", b)
	default:
		return
	}
	os.Exit(0)
}

// Go1Point returns the x in Go 1.x.
func Go1Point() int {
	for i := len(build.Default.ReleaseTags) - 1; i >= 0; i-- {
		var version int
		if _, err := fmt.Sscanf(build.Default.ReleaseTags[i], "go1.%d", &version); err != nil {
			continue
		}
		return version
	}
	panic("bad release tags")
}

// NeedsGo1Point skips t if the Go version used to run the test is older than
// 1.x.
func NeedsGo1Point(t Testing, x int) {
	if t, ok := t.(helperer); ok {
		t.Helper()
	}
	if Go1Point() < x {
		t.Skipf("running Go version %q is version 1.%d, older than required 1.%d", runtime.Version(), Go1Point(), x)
	}
}

// SkipAfterGo1Point skips t if the Go version used to run the test is newer than
// 1.x.
func SkipAfterGo1Point(t Testing, x int) {
	if t, ok := t.(helperer); ok {
		t.Helper()
	}
	if Go1Point() > x {
		t.Skipf("running Go version %q is version 1.%d, newer than maximum 1.%d", runtime.Version(), Go1Point(), x)
	}
}

// Deadline returns the deadline of t, if known,
// using the Deadline method added in Go 1.15.
func Deadline(t Testing) (time.Time, bool) {
	td, ok := t.(interface {
		Deadline() (time.Time, bool)
	})
	if !ok {
		return time.Time{}, false
	}
	return td.Deadline()
}
<commit_msg>internal/testenv: remove darwin/arm case from NeedsGoBuild<commit_after>// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Package testenv contains helper functions for skipping tests
// based on which tools are present in the environment.
package testenv

import (
	"bytes"
	"fmt"
	"go/build"
	"io/ioutil"
	"os"
	"runtime"
	"strings"
	"sync"
	"time"

	exec "golang.org/x/sys/execabs"
)

// Testing is an abstraction of a *testing.T.
type Testing interface {
	Skipf(format string, args ...interface{})
	Fatalf(format string, args ...interface{})
}

type helperer interface {
	Helper()
}

// packageMainIsDevel reports whether the module containing package main
// is a development version (if module information is available).
//
// Builds in GOPATH mode and builds that lack module information are assumed to
// be development versions.
var packageMainIsDevel = func() bool { return true }

var checkGoGoroot struct {
	once sync.Once
	err  error
}

func hasTool(tool string) error {
	if tool == "cgo" {
		enabled, err := cgoEnabled(false)
		if err != nil {
			return fmt.Errorf("checking cgo: %v", err)
		}
		if !enabled {
			return fmt.Errorf("cgo not enabled")
		}
		return nil
	}

	_, err := exec.LookPath(tool)
	if err != nil {
		return err
	}

	switch tool {
	case "patch":
		// check that the patch tools supports the -o argument
		temp, err := ioutil.TempFile("", "patch-test")
		if err != nil {
			return err
		}
		temp.Close()
		defer os.Remove(temp.Name())
		cmd := exec.Command(tool, "-o", temp.Name())
		if err := cmd.Run(); err != nil {
			return err
		}

	case "go":
		checkGoGoroot.once.Do(func() {
			// Ensure that the 'go' command found by exec.LookPath is from the correct
			// GOROOT. Otherwise, 'some/path/go test ./...' will test against some
			// version of the 'go' binary other than 'some/path/go', which is almost
			// certainly not what the user intended.
			out, err := exec.Command(tool, "env", "GOROOT").CombinedOutput()
			if err != nil {
				checkGoGoroot.err = err
				return
			}
			GOROOT := strings.TrimSpace(string(out))
			if GOROOT != runtime.GOROOT() {
				checkGoGoroot.err = fmt.Errorf("'go env GOROOT' does not match runtime.GOROOT:\n\tgo env: %s\n\tGOROOT: %s", GOROOT, runtime.GOROOT())
			}
		})
		if checkGoGoroot.err != nil {
			return checkGoGoroot.err
		}

	case "diff":
		// Check that diff is the GNU version, needed for the -u argument and
		// to report missing newlines at the end of files.
		out, err := exec.Command(tool, "-version").Output()
		if err != nil {
			return err
		}
		if !bytes.Contains(out, []byte("GNU diffutils")) {
			return fmt.Errorf("diff is not the GNU version")
		}
	}

	return nil
}

func cgoEnabled(bypassEnvironment bool) (bool, error) {
	cmd := exec.Command("go", "env", "CGO_ENABLED")
	if bypassEnvironment {
		cmd.Env = append(append([]string(nil), os.Environ()...), "CGO_ENABLED=")
	}
	out, err := cmd.CombinedOutput()
	if err != nil {
		return false, err
	}
	enabled := strings.TrimSpace(string(out))
	return enabled == "1", nil
}

func allowMissingTool(tool string) bool {
	if runtime.GOOS == "android" {
		// Android builds generally run tests on a separate machine from the build,
		// so don't expect any external tools to be available.
		return true
	}

	switch tool {
	case "cgo":
		if strings.HasSuffix(os.Getenv("GO_BUILDER_NAME"), "-nocgo") {
			// Explicitly disabled on -nocgo builders.
			return true
		}
		if enabled, err := cgoEnabled(true); err == nil && !enabled {
			// No platform support.
			return true
		}
	case "go":
		if os.Getenv("GO_BUILDER_NAME") == "illumos-amd64-joyent" {
			// Work around a misconfigured builder (see https://golang.org/issue/33950).
			return true
		}
	case "diff":
		if os.Getenv("GO_BUILDER_NAME") != "" {
			return true
		}
	case "patch":
		if os.Getenv("GO_BUILDER_NAME") != "" {
			return true
		}
	}

	// If a developer is actively working on this test, we expect them to have all
	// of its dependencies installed. However, if it's just a dependency of some
	// other module (for example, being run via 'go test all'), we should be more
	// tolerant of unusual environments.
	return !packageMainIsDevel()
}

// NeedsTool skips t if the named tool is not present in the path.
// As a special case, "cgo" means "go" is present and can compile cgo programs.
func NeedsTool(t Testing, tool string) {
	if t, ok := t.(helperer); ok {
		t.Helper()
	}
	err := hasTool(tool)
	if err == nil {
		return
	}
	if allowMissingTool(tool) {
		t.Skipf("skipping because %s tool not available: %v", tool, err)
	} else {
		t.Fatalf("%s tool not available: %v", tool, err)
	}
}

// NeedsGoPackages skips t if the go/packages driver (or 'go' tool) implied by
// the current process environment is not present in the path.
func NeedsGoPackages(t Testing) {
	if t, ok := t.(helperer); ok {
		t.Helper()
	}

	tool := os.Getenv("GOPACKAGESDRIVER")
	switch tool {
	case "off":
		// "off" forces go/packages to use the go command.
		tool = "go"
	case "":
		if _, err := exec.LookPath("gopackagesdriver"); err == nil {
			tool = "gopackagesdriver"
		} else {
			tool = "go"
		}
	}

	NeedsTool(t, tool)
}

// NeedsGoPackagesEnv skips t if the go/packages driver (or 'go' tool) implied
// by env is not present in the path.
func NeedsGoPackagesEnv(t Testing, env []string) {
	if t, ok := t.(helperer); ok {
		t.Helper()
	}

	for _, v := range env {
		if strings.HasPrefix(v, "GOPACKAGESDRIVER=") {
			tool := strings.TrimPrefix(v, "GOPACKAGESDRIVER=")
			if tool == "off" {
				NeedsTool(t, "go")
			} else {
				NeedsTool(t, tool)
			}
			return
		}
	}

	NeedsGoPackages(t)
}

// NeedsGoBuild skips t if the current system can't build programs with “go build”
// and then run them with os.StartProcess or exec.Command.
// Android doesn't have the userspace go build needs to run,
// and js/wasm doesn't support running subprocesses.
func NeedsGoBuild(t Testing) {
	if t, ok := t.(helperer); ok {
		t.Helper()
	}

	// This logic was derived from internal/testing.HasGoBuild and
	// may need to be updated as that function evolves.

	NeedsTool(t, "go")

	switch runtime.GOOS {
	case "android", "js":
		t.Skipf("skipping test: %v can't build and run Go binaries", runtime.GOOS)
	}
}

// ExitIfSmallMachine emits a helpful diagnostic and calls os.Exit(0) if the
// current machine is a builder known to have scarce resources.
//
// It should be called from within a TestMain function.
func ExitIfSmallMachine() {
	switch b := os.Getenv("GO_BUILDER_NAME"); b {
	case "linux-arm-scaleway":
		// "linux-arm" was renamed to "linux-arm-scaleway" in CL 303230.
		fmt.Fprintln(os.Stderr, "skipping test: linux-arm-scaleway builder lacks sufficient memory (https://golang.org/issue/32834)")
	case "plan9-arm":
		fmt.Fprintln(os.Stderr, "skipping test: plan9-arm builder lacks sufficient memory (https://golang.org/issue/38772)")
	case "netbsd-arm-bsiegert", "netbsd-arm64-bsiegert":
		// As of 2021-06-02, these builders are running with GO_TEST_TIMEOUT_SCALE=10,
		// and there is only one of each. We shouldn't waste those scarce resources
		// running very slow tests.
		fmt.Fprintf(os.Stderr, "skipping test: %s builder is very slow\n", b)
	case "dragonfly-amd64":
		// As of 2021-11-02, this builder is running with GO_TEST_TIMEOUT_SCALE=2,
		// and seems to have unusually slow disk performance.
		fmt.Fprintln(os.Stderr, "skipping test: dragonfly-amd64 has slow disk (https://golang.org/issue/45216)")
	case "linux-riscv64-unmatched":
		// As of 2021-11-03, this builder is empirically not fast enough to run
		// gopls tests. Ideally we should make the tests faster in short mode
		// and/or fix them to not assume arbitrary deadlines.
		// For now, we'll skip them instead.
		fmt.Fprintf(os.Stderr, "skipping test: %s builder is too slow (https://golang.org/issue/49321)\n", b)
	default:
		return
	}
	os.Exit(0)
}

// Go1Point returns the x in Go 1.x.
func Go1Point() int {
	for i := len(build.Default.ReleaseTags) - 1; i >= 0; i-- {
		var version int
		if _, err := fmt.Sscanf(build.Default.ReleaseTags[i], "go1.%d", &version); err != nil {
			continue
		}
		return version
	}
	panic("bad release tags")
}

// NeedsGo1Point skips t if the Go version used to run the test is older than
// 1.x.
func NeedsGo1Point(t Testing, x int) {
	if t, ok := t.(helperer); ok {
		t.Helper()
	}
	if Go1Point() < x {
		t.Skipf("running Go version %q is version 1.%d, older than required 1.%d", runtime.Version(), Go1Point(), x)
	}
}

// SkipAfterGo1Point skips t if the Go version used to run the test is newer than
// 1.x.
func SkipAfterGo1Point(t Testing, x int) {
	if t, ok := t.(helperer); ok {
		t.Helper()
	}
	if Go1Point() > x {
		t.Skipf("running Go version %q is version 1.%d, newer than maximum 1.%d", runtime.Version(), Go1Point(), x)
	}
}

// Deadline returns the deadline of t, if known,
// using the Deadline method added in Go 1.15.
func Deadline(t Testing) (time.Time, bool) {
	td, ok := t.(interface {
		Deadline() (time.Time, bool)
	})
	if !ok {
		return time.Time{}, false
	}
	return td.Deadline()
}
<|endoftext|>


<commit_before>// Copyright (c) 2013-2014 The btcsuite developers
// Copyright (c) 2015-2018 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.

// Package version provides a single location to house the version information
// for dcrd and other utilities provided in the same repository.
package version

import (
	"bytes"
	"fmt"
	"strings"
)

const (
	// semanticAlphabet defines the allowed characters for the pre-release
	// portion of a semantic version string.
	semanticAlphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-"

	// semanticBuildAlphabet defines the allowed characters for the build
	// portion of a semantic version string.
	semanticBuildAlphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-."
)

// These constants define the application version and follow the semantic
// versioning 2.0.0 spec (https://semver.org/).
const (
	Major uint = 1
	Minor uint = 5
	Patch uint = 0
)

var (
	// PreRelease is defined as a variable so it can be overridden during the
	// build process with:
	// '-ldflags "-X github.com/decred/dcrd/internal/version.PreRelease=foo"'
	// if needed.  It MUST only contain characters from semanticAlphabet per
	// the semantic versioning spec.
	PreRelease = "pre"

	// BuildMetadata is defined as a variable so it can be overridden during the
	// build process with:
	// '-ldflags "-X github.com/decred/dcrd/internal/version.BuildMetadata=foo"'
	// if needed.  It MUST only contain characters from semanticBuildAlphabet
	// per the semantic versioning spec.
	BuildMetadata = "dev"
)

// String returns the application version as a properly formed string per the
// semantic versioning 2.0.0 spec (https://semver.org/).
func String() string {
	// Start with the major, minor, and patch versions.
	version := fmt.Sprintf("%d.%d.%d", Major, Minor, Patch)

	// Append pre-release version if there is one.  The hyphen called for
	// by the semantic versioning spec is automatically appended and should
	// not be contained in the pre-release string.  The pre-release version
	// is not appended if it contains invalid characters.
	preRelease := NormalizePreRelString(PreRelease)
	if preRelease != "" {
		version = fmt.Sprintf("%s-%s", version, preRelease)
	}

	// Append build metadata if there is any.  The plus called for
	// by the semantic versioning spec is automatically appended and should
	// not be contained in the build metadata string.  The build metadata
	// string is not appended if it contains invalid characters.
	build := NormalizeBuildString(BuildMetadata)
	if build != "" {
		version = fmt.Sprintf("%s+%s", version, build)
	}

	return version
}

// normalizeSemString returns the passed string stripped of all characters
// which are not valid according to the provided semantic versioning alphabet.
func normalizeSemString(str, alphabet string) string {
	var result bytes.Buffer
	for _, r := range str {
		if strings.ContainsRune(alphabet, r) {
			result.WriteRune(r)
		}
	}
	return result.String()
}

// NormalizePreRelString returns the passed string stripped of all characters
// which are not valid according to the semantic versioning guidelines for
// pre-release strings.  In particular they MUST only contain characters in
// semanticAlphabet.
func NormalizePreRelString(str string) string {
	return normalizeSemString(str, semanticAlphabet)
}

// NormalizeBuildString returns the passed string stripped of all characters
// which are not valid according to the semantic versioning guidelines for build
// metadata strings.  In particular they MUST only contain characters in
// semanticBuildAlphabet.
func NormalizeBuildString(str string) string {
	return normalizeSemString(str, semanticBuildAlphabet)
}
<commit_msg>release: Bump for 1.6 release cycle.<commit_after>// Copyright (c) 2013-2014 The btcsuite developers
// Copyright (c) 2015-2018 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.

// Package version provides a single location to house the version information
// for dcrd and other utilities provided in the same repository.
package version

import (
	"bytes"
	"fmt"
	"strings"
)

const (
	// semanticAlphabet defines the allowed characters for the pre-release
	// portion of a semantic version string.
	semanticAlphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-"

	// semanticBuildAlphabet defines the allowed characters for the build
	// portion of a semantic version string.
	semanticBuildAlphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-."
)

// These constants define the application version and follow the semantic
// versioning 2.0.0 spec (https://semver.org/).
const (
	Major uint = 1
	Minor uint = 6
	Patch uint = 0
)

var (
	// PreRelease is defined as a variable so it can be overridden during the
	// build process with:
	// '-ldflags "-X github.com/decred/dcrd/internal/version.PreRelease=foo"'
	// if needed.  It MUST only contain characters from semanticAlphabet per
	// the semantic versioning spec.
	PreRelease = "pre"

	// BuildMetadata is defined as a variable so it can be overridden during the
	// build process with:
	// '-ldflags "-X github.com/decred/dcrd/internal/version.BuildMetadata=foo"'
	// if needed.  It MUST only contain characters from semanticBuildAlphabet
	// per the semantic versioning spec.
	BuildMetadata = "dev"
)

// String returns the application version as a properly formed string per the
// semantic versioning 2.0.0 spec (https://semver.org/).
func String() string {
	// Start with the major, minor, and patch versions.
	version := fmt.Sprintf("%d.%d.%d", Major, Minor, Patch)

	// Append pre-release version if there is one.  The hyphen called for
	// by the semantic versioning spec is automatically appended and should
	// not be contained in the pre-release string.  The pre-release version
	// is not appended if it contains invalid characters.
	preRelease := NormalizePreRelString(PreRelease)
	if preRelease != "" {
		version = fmt.Sprintf("%s-%s", version, preRelease)
	}

	// Append build metadata if there is any.  The plus called for
	// by the semantic versioning spec is automatically appended and should
	// not be contained in the build metadata string.  The build metadata
	// string is not appended if it contains invalid characters.
	build := NormalizeBuildString(BuildMetadata)
	if build != "" {
		version = fmt.Sprintf("%s+%s", version, build)
	}

	return version
}

// normalizeSemString returns the passed string stripped of all characters
// which are not valid according to the provided semantic versioning alphabet.
func normalizeSemString(str, alphabet string) string {
	var result bytes.Buffer
	for _, r := range str {
		if strings.ContainsRune(alphabet, r) {
			result.WriteRune(r)
		}
	}
	return result.String()
}

// NormalizePreRelString returns the passed string stripped of all characters
// which are not valid according to the semantic versioning guidelines for
// pre-release strings.  In particular they MUST only contain characters in
// semanticAlphabet.
func NormalizePreRelString(str string) string {
	return normalizeSemString(str, semanticAlphabet)
}

// NormalizeBuildString returns the passed string stripped of all characters
// which are not valid according to the semantic versioning guidelines for build
// metadata strings.  In particular they MUST only contain characters in
// semanticBuildAlphabet.
func NormalizeBuildString(str string) string {
	return normalizeSemString(str, semanticBuildAlphabet)
}
<|endoftext|>