<commit_before>// +build coprocess

package main

import (
	"github.com/Sirupsen/logrus"
	"github.com/TykTechnologies/tykcommon"
	"github.com/gorilla/context"
	"github.com/mitchellh/mapstructure"
	// "gopkg.in/xmlpath.v2"
	"regexp"

	"crypto/md5"
	"errors"
	"fmt"
	"net/http"
	"strconv"
	"strings"
	"time"
)

// IdExtractor is the base interface for an ID extractor.
type IdExtractor interface {
	ExtractAndCheck(*http.Request) (string, ReturnOverrides)
	PostProcess(*http.Request, SessionState, string)
	GenerateSessionID(string, *TykMiddleware) string
}

// BaseExtractor is the base structure for an ID extractor, it implements the IdExtractor interface. Other extractors may override some of its methods.
type BaseExtractor struct {
	Config        *tykcommon.MiddlewareIdExtractor
	TykMiddleware *TykMiddleware
	Spec          *APISpec
}

// ExtractAndCheck is called from the CP middleware, if ID extractor is enabled for the current API.
func (e *BaseExtractor) ExtractAndCheck(r *http.Request) (SessionID string, returnOverrides ReturnOverrides) {
	log.WithFields(logrus.Fields{
		"prefix": "idextractor",
	}).Error("This extractor doesn't implement an extraction method, rejecting.")
	return "", ReturnOverrides{403, "Key not authorised"}
}

// PostProcess sets context variables and updates the storage.
func (e *BaseExtractor) PostProcess(r *http.Request, thisSessionState SessionState, SessionID string) {
	var sessionLifetime = GetLifetime(e.Spec, &thisSessionState)
	e.Spec.SessionManager.UpdateSession(SessionID, thisSessionState, sessionLifetime)

	context.Set(r, SessionData, thisSessionState)
	context.Set(r, AuthHeaderValue, SessionID)

	return
}

// ExtractHeader is used when a HeaderSource is specified.
func (e *BaseExtractor) ExtractHeader(r *http.Request) (headerValue string, err error) {
	var headerName = e.Config.ExtractorConfig["header_name"].(string)
	headerValue = r.Header.Get(headerName)
	if headerValue == "" {
		err = errors.New("Bad header value.")
	}
	return headerValue, err
}

// ExtractForm is used when a FormSource is specified.
func (e *BaseExtractor) ExtractForm(r *http.Request, paramName string) (formValue string, err error) {
	r.ParseForm()
	if paramName == "" {
		// No param name, error?
		err = errors.New("No form param name set")
		return formValue, err
	}

	values := r.Form[paramName]

	if len(values) > 0 {
		formValue = strings.Join(values, "")
	} else {
		// Error, no value!
		err = errors.New("No form value")
	}
	return formValue, err
}

func (e *BaseExtractor) ExtractBody(r *http.Request) (bodyValue string, err error) {
	return bodyValue, err
}

// Error is a helper for logging the extractor errors. It always returns HTTP 400 (so we don't expose any details).
func (e *BaseExtractor) Error(r *http.Request, err error, message string) (returnOverrides ReturnOverrides) {
	log.WithFields(logrus.Fields{
		"path":   r.URL.Path,
		"origin": GetIPFromRequest(r),
	}).Info("Extractor error: ", message, ", ", err)

	return ReturnOverrides{
		ResponseCode:  400,
		ResponseError: "Authorization field missing",
	}
}

// GenerateSessionID is a helper for generating session IDs, it takes an input (usually the extractor output) and a middleware pointer.
func (e *BaseExtractor) GenerateSessionID(input string, mw *TykMiddleware) (SessionID string) {
	data := []byte(input)
	tokenID := fmt.Sprintf("%x", md5.Sum(data))
	SessionID = mw.Spec.OrgID + tokenID
	return SessionID
}

type ValueExtractor struct {
	BaseExtractor
}

type ValueExtractorConfig struct {
	HeaderName    string `mapstructure:"header_name" bson:"header_name" json:"header_name"`
	FormParamName string `mapstructure:"param_name" bson:"param_name" json:"param_name"`
}

func (e *ValueExtractor) Extract(input interface{}) string {
	headerValue := input.(string)
	return headerValue
}

func (e *ValueExtractor) ExtractAndCheck(r *http.Request) (SessionID string, returnOverrides ReturnOverrides) {
	var extractorOutput string
	var err error
	var config ValueExtractorConfig

	err = mapstructure.Decode(e.Config.ExtractorConfig, &config)
	if err != nil {
		returnOverrides = e.Error(r, err, "Couldn't decode ValueExtractor configuration")
		return SessionID, returnOverrides
	}

	switch e.Config.ExtractFrom {
	case tykcommon.HeaderSource:
		extractorOutput, err = e.ExtractHeader(r)
	case tykcommon.FormSource:
		extractorOutput, err = e.ExtractForm(r, config.FormParamName)
	}

	if err != nil {
		returnOverrides = e.Error(r, err, "ValueExtractor error")
		return SessionID, returnOverrides
	}

	SessionID = e.GenerateSessionID(extractorOutput, e.TykMiddleware)

	var keyExists bool
	var previousSessionState SessionState
	previousSessionState, keyExists = e.TykMiddleware.CheckSessionAndIdentityForValidKey(SessionID)

	if keyExists {

		lastUpdated, _ := strconv.Atoi(previousSessionState.LastUpdated)

		deadlineTs := int64(lastUpdated) + previousSessionState.IdExtractorDeadline

		if deadlineTs > time.Now().Unix() {
			e.PostProcess(r, previousSessionState, SessionID)
			returnOverrides = ReturnOverrides{
				ResponseCode: 200,
			}
		}
	}

	return SessionID, returnOverrides
}

type RegexExtractor struct {
	BaseExtractor
}

type RegexExtractorConfig struct {
	HeaderName      string `mapstructure:"header_name" bson:"header_name" json:"header_name"`
	RegexExpression string `mapstructure:"regex_expression" bson:"regex_expression" json:"regex_expression"`
	RegexMatchIndex int    `mapstructure:"regex_match_index" bson:"regex_match_index" json:"regex_match_index"`
	FormParamName   string `mapstructure:"param_name" bson:"param_name" json:"param_name"`
}

func (e *RegexExtractor) ExtractAndCheck(r *http.Request) (SessionID string, returnOverrides ReturnOverrides) {
	var extractorOutput string
	var err error

	var config RegexExtractorConfig

	err = mapstructure.Decode(e.Config.ExtractorConfig, &config)

	if err != nil {
		returnOverrides = e.Error(r, err, "Can't decode RegexExtractor configuration")
		return SessionID, returnOverrides
	}

	if e.Config.ExtractorConfig["regex_expression"] == nil {
		returnOverrides = e.Error(r, nil, "RegexExtractor expects an expression")
		return SessionID, returnOverrides
	}

	var expression *regexp.Regexp
	expression, err = regexp.Compile(config.RegexExpression)

	if err != nil {
		returnOverrides = e.Error(r, nil, "RegexExtractor found an invalid expression")
		return SessionID, returnOverrides
	}

	switch e.Config.ExtractFrom {
	case tykcommon.HeaderSource:
		extractorOutput, err = e.ExtractHeader(r)
	case tykcommon.BodySource:
		extractorOutput, err = e.ExtractBody(r)
	case tykcommon.FormSource:
		extractorOutput, err = e.ExtractForm(r, config.FormParamName)
	}

	if err != nil {
		returnOverrides = e.Error(r, err, "RegexExtractor error")
		return SessionID, returnOverrides
	}

	var regexOutput []string
	regexOutput = expression.FindAllString(extractorOutput, -1)

	SessionID = e.GenerateSessionID(regexOutput[config.RegexMatchIndex], e.TykMiddleware)

	var keyExists bool
	var previousSessionState SessionState
	previousSessionState, keyExists = e.TykMiddleware.CheckSessionAndIdentityForValidKey(SessionID)

	if keyExists {

		lastUpdated, _ := strconv.Atoi(previousSessionState.LastUpdated)

		deadlineTs := int64(lastUpdated) + previousSessionState.IdExtractorDeadline

		if deadlineTs > time.Now().Unix() {
			e.PostProcess(r, previousSessionState, SessionID)
			returnOverrides = ReturnOverrides{
				ResponseCode: 200,
			}
		}
	}

	return SessionID, returnOverrides
}

type XPathExtractor struct {
	BaseExtractor
}

func (e *XPathExtractor) ExtractAndCheck(r *http.Request) (SessionID string, returnOverrides ReturnOverrides) {
	var extractorOutput string

	if e.Config.ExtractorConfig["regex_expression"] == nil {
		// TODO: Error, no expression set!
	}

	var expressionString string
	expressionString = e.Config.ExtractorConfig["regex_expression"].(string)

	expression, err := regexp.Compile(expressionString)

	if err != nil {
		// TODO: error, the expression is bad!
	}

	switch e.Config.ExtractFrom {
	case tykcommon.HeaderSource:
		var headerName, headerValue string

		// TODO: check if header_name is set
		headerName = e.Config.ExtractorConfig["header_name"].(string)
		headerValue = r.Header.Get(headerName)

		if headerValue == "" {
			log.WithFields(logrus.Fields{
				"path":   r.URL.Path,
				"origin": GetIPFromRequest(r),
			}).Info("Attempted access with malformed header, no auth header found.")

			log.Debug("Looked in: ", headerName)
			log.Debug("Raw data was: ", headerValue)
			log.Debug("Headers are: ", r.Header)

			returnOverrides = ReturnOverrides{
				ResponseCode:  400,
				ResponseError: "Authorization field missing",
			}

			// m.reportLoginFailure(tykId, r)
		}

		// TODO: check if header_name setting exists!
		extractorOutput = r.Header.Get(headerName)
	case tykcommon.BodySource:
		log.Println("Using RegexExtractor with BodySource")
	case tykcommon.FormSource:
		log.Println("Using RegexExtractor with FormSource")
	}

	var regexOutput []string
	regexOutput = expression.FindAllString(extractorOutput, -1)

	var matchIndex = 1

	SessionID = e.GenerateSessionID(regexOutput[matchIndex], e.TykMiddleware)

	var keyExists bool
	var previousSessionState SessionState
	previousSessionState, keyExists = e.TykMiddleware.CheckSessionAndIdentityForValidKey(SessionID)

	if keyExists {

		lastUpdated, _ := strconv.Atoi(previousSessionState.LastUpdated)

		deadlineTs := int64(lastUpdated) + previousSessionState.IdExtractorDeadline

		if deadlineTs > time.Now().Unix() {
			e.PostProcess(r, previousSessionState, SessionID)
			returnOverrides = ReturnOverrides{
				ResponseCode: 200,
			}
		}
	}

	return SessionID, returnOverrides
}

// newExtractor is called from the CP middleware for every API that specifies extractor settings.
func newExtractor(referenceSpec *APISpec, mw *TykMiddleware) {
	var thisExtractor IdExtractor

	baseExtractor := BaseExtractor{&referenceSpec.CustomMiddleware.IdExtractor, mw, referenceSpec}

	// Initialize a extractor based on the API spec.
	switch referenceSpec.CustomMiddleware.IdExtractor.ExtractWith {
	case tykcommon.ValueExtractor:
		thisExtractor = &ValueExtractor{baseExtractor}
	case tykcommon.RegexExtractor:
		thisExtractor = &RegexExtractor{baseExtractor}
	case tykcommon.XPathExtractor:
		thisExtractor = &XPathExtractor{baseExtractor}
	}

	referenceSpec.CustomMiddleware.IdExtractor.Extractor = thisExtractor
}
<commit_msg>Fix extractor.<commit_after>// +build coprocess

package main

import (
	"github.com/Sirupsen/logrus"
	"github.com/TykTechnologies/tykcommon"
	"github.com/gorilla/context"
	"github.com/mitchellh/mapstructure"
	"gopkg.in/xmlpath.v2"
	"regexp"

	"bytes"
	"crypto/md5"
	"errors"
	"fmt"
	"net/http"
	"strconv"
	"strings"
	"time"
)

// IdExtractor is the base interface for an ID extractor.
type IdExtractor interface {
	ExtractAndCheck(*http.Request) (string, ReturnOverrides)
	PostProcess(*http.Request, SessionState, string)
	GenerateSessionID(string, *TykMiddleware) string
}

// BaseExtractor is the base structure for an ID extractor, it implements the IdExtractor interface. Other extractors may override some of its methods.
type BaseExtractor struct {
	Config        *tykcommon.MiddlewareIdExtractor
	TykMiddleware *TykMiddleware
	Spec          *APISpec
}

// ExtractAndCheck is called from the CP middleware, if ID extractor is enabled for the current API.
func (e *BaseExtractor) ExtractAndCheck(r *http.Request) (SessionID string, returnOverrides ReturnOverrides) {
	log.WithFields(logrus.Fields{
		"prefix": "idextractor",
	}).Error("This extractor doesn't implement an extraction method, rejecting.")
	return "", ReturnOverrides{403, "Key not authorised"}
}

// PostProcess sets context variables and updates the storage.
func (e *BaseExtractor) PostProcess(r *http.Request, thisSessionState SessionState, SessionID string) {
	var sessionLifetime = GetLifetime(e.Spec, &thisSessionState)
	e.Spec.SessionManager.UpdateSession(SessionID, thisSessionState, sessionLifetime)

	context.Set(r, SessionData, thisSessionState)
	context.Set(r, AuthHeaderValue, SessionID)

	return
}

// ExtractHeader is used when a HeaderSource is specified.
func (e *BaseExtractor) ExtractHeader(r *http.Request) (headerValue string, err error) {
	var headerName = e.Config.ExtractorConfig["header_name"].(string)
	headerValue = r.Header.Get(headerName)
	if headerValue == "" {
		err = errors.New("Bad header value.")
	}
	return headerValue, err
}

// ExtractForm is used when a FormSource is specified.
func (e *BaseExtractor) ExtractForm(r *http.Request, paramName string) (formValue string, err error) {
	r.ParseForm()
	if paramName == "" {
		// No param name, error?
		err = errors.New("No form param name set")
		return formValue, err
	}

	values := r.Form[paramName]

	if len(values) > 0 {
		formValue = strings.Join(values, "")
	} else {
		// Error, no value!
		err = errors.New("No form value")
	}
	return formValue, err
}

func (e *BaseExtractor) ExtractBody(r *http.Request) (bodyValue string, err error) {
	return bodyValue, err
}

// Error is a helper for logging the extractor errors. It always returns HTTP 400 (so we don't expose any details).
func (e *BaseExtractor) Error(r *http.Request, err error, message string) (returnOverrides ReturnOverrides) {
	log.WithFields(logrus.Fields{
		"path":   r.URL.Path,
		"origin": GetIPFromRequest(r),
	}).Info("Extractor error: ", message, ", ", err)

	return ReturnOverrides{
		ResponseCode:  400,
		ResponseError: "Authorization field missing",
	}
}

// GenerateSessionID is a helper for generating session IDs, it takes an input (usually the extractor output) and a middleware pointer.
func (e *BaseExtractor) GenerateSessionID(input string, mw *TykMiddleware) (SessionID string) {
	data := []byte(input)
	tokenID := fmt.Sprintf("%x", md5.Sum(data))
	SessionID = mw.Spec.OrgID + tokenID
	return SessionID
}

type ValueExtractor struct {
	BaseExtractor
}

type ValueExtractorConfig struct {
	HeaderName    string `mapstructure:"header_name" bson:"header_name" json:"header_name"`
	FormParamName string `mapstructure:"param_name" bson:"param_name" json:"param_name"`
}

func (e *ValueExtractor) Extract(input interface{}) string {
	headerValue := input.(string)
	return headerValue
}

func (e *ValueExtractor) ExtractAndCheck(r *http.Request) (SessionID string, returnOverrides ReturnOverrides) {
	var extractorOutput string
	var err error
	var config ValueExtractorConfig

	err = mapstructure.Decode(e.Config.ExtractorConfig, &config)
	if err != nil {
		returnOverrides = e.Error(r, err, "Couldn't decode ValueExtractor configuration")
		return SessionID, returnOverrides
	}

	switch e.Config.ExtractFrom {
	case tykcommon.HeaderSource:
		extractorOutput, err = e.ExtractHeader(r)
	case tykcommon.FormSource:
		extractorOutput, err = e.ExtractForm(r, config.FormParamName)
	}

	if err != nil {
		returnOverrides = e.Error(r, err, "ValueExtractor error")
		return SessionID, returnOverrides
	}

	SessionID = e.GenerateSessionID(extractorOutput, e.TykMiddleware)

	var keyExists bool
	var previousSessionState SessionState
	previousSessionState, keyExists = e.TykMiddleware.CheckSessionAndIdentityForValidKey(SessionID)

	if keyExists {

		lastUpdated, _ := strconv.Atoi(previousSessionState.LastUpdated)

		deadlineTs := int64(lastUpdated) + previousSessionState.IdExtractorDeadline

		if deadlineTs > time.Now().Unix() {
			e.PostProcess(r, previousSessionState, SessionID)
			returnOverrides = ReturnOverrides{
				ResponseCode: 200,
			}
		}
	}

	return SessionID, returnOverrides
}

type RegexExtractor struct {
	BaseExtractor
}

type RegexExtractorConfig struct {
	HeaderName      string `mapstructure:"header_name" bson:"header_name" json:"header_name"`
	RegexExpression string `mapstructure:"regex_expression" bson:"regex_expression" json:"regex_expression"`
	RegexMatchIndex int    `mapstructure:"regex_match_index" bson:"regex_match_index" json:"regex_match_index"`
	FormParamName   string `mapstructure:"param_name" bson:"param_name" json:"param_name"`
}

func (e *RegexExtractor) ExtractAndCheck(r *http.Request) (SessionID string, returnOverrides ReturnOverrides) {
	var extractorOutput string
	var err error

	var config RegexExtractorConfig

	err = mapstructure.Decode(e.Config.ExtractorConfig, &config)

	if err != nil {
		returnOverrides = e.Error(r, err, "Can't decode RegexExtractor configuration")
		return SessionID, returnOverrides
	}

	if e.Config.ExtractorConfig["regex_expression"] == nil {
		returnOverrides = e.Error(r, nil, "RegexExtractor expects an expression")
		return SessionID, returnOverrides
	}

	var expression *regexp.Regexp
	expression, err = regexp.Compile(config.RegexExpression)

	if err != nil {
		returnOverrides = e.Error(r, nil, "RegexExtractor found an invalid expression")
		return SessionID, returnOverrides
	}

	switch e.Config.ExtractFrom {
	case tykcommon.HeaderSource:
		extractorOutput, err = e.ExtractHeader(r)
	case tykcommon.BodySource:
		extractorOutput, err = e.ExtractBody(r)
	case tykcommon.FormSource:
		extractorOutput, err = e.ExtractForm(r, config.FormParamName)
	}

	if err != nil {
		returnOverrides = e.Error(r, err, "RegexExtractor error")
		return SessionID, returnOverrides
	}

	var regexOutput []string
	regexOutput = expression.FindAllString(extractorOutput, -1)

	SessionID = e.GenerateSessionID(regexOutput[config.RegexMatchIndex], e.TykMiddleware)

	var keyExists bool
	var previousSessionState SessionState
	previousSessionState, keyExists = e.TykMiddleware.CheckSessionAndIdentityForValidKey(SessionID)

	if keyExists {

		lastUpdated, _ := strconv.Atoi(previousSessionState.LastUpdated)

		deadlineTs := int64(lastUpdated) + previousSessionState.IdExtractorDeadline

		if deadlineTs > time.Now().Unix() {
			e.PostProcess(r, previousSessionState, SessionID)
			returnOverrides = ReturnOverrides{
				ResponseCode: 200,
			}
		}
	}

	return SessionID, returnOverrides
}

type XPathExtractor struct {
	BaseExtractor
}

type XPathExtractorConfig struct {
	HeaderName      string `mapstructure:"header_name" bson:"header_name" json:"header_name"`
	RegexExpression string `mapstructure:"regex_expression" bson:"regex_expression" json:"regex_expression"`
	RegexMatchIndex int    `mapstructure:"regex_match_index" bson:"regex_match_index" json:"regex_match_index"`
	FormParamName   string `mapstructure:"param_name" bson:"param_name" json:"param_name"`
}

func (e *XPathExtractor) ExtractAndCheck(r *http.Request) (SessionID string, returnOverrides ReturnOverrides) {
	var extractorOutput string
	var err error

	var config XPathExtractorConfig
	err = mapstructure.Decode(e.Config.ExtractorConfig, &config)

	if e.Config.ExtractorConfig["xpath_expression"] == nil {
		returnOverrides = e.Error(r, err, "XPathExtractor: no expression set")
		return SessionID, returnOverrides
	}

	var expressionString string
	expressionString = e.Config.ExtractorConfig["xpath_expression"].(string)

	var expression *xmlpath.Path
	expression, err = xmlpath.Compile(expressionString)

	if err != nil {
		returnOverrides = e.Error(r, err, "XPathExtractor: bad expression")
		return SessionID, returnOverrides
	}

	switch e.Config.ExtractFrom {
	case tykcommon.HeaderSource:
		extractorOutput, err = e.ExtractHeader(r)
	case tykcommon.BodySource:
		extractorOutput, err = e.ExtractBody(r)
	case tykcommon.FormSource:
		extractorOutput, err = e.ExtractForm(r, config.FormParamName)
	}

	if err != nil {
		returnOverrides = e.Error(r, err, "XPathExtractor error")
		return SessionID, returnOverrides
	}

	var extractedXml *xmlpath.Node
	extractedXml, err = xmlpath.Parse(bytes.NewBufferString(extractorOutput))

	if err != nil {
		returnOverrides = e.Error(r, err, "XPathExtractor: couldn't parse input")
		return SessionID, returnOverrides
	}

	output, ok := expression.String(extractedXml)

	if !ok {
		returnOverrides = e.Error(r, err, "XPathExtractor: no input")
		return SessionID, returnOverrides
	}

	SessionID = e.GenerateSessionID(output, e.TykMiddleware)

	var keyExists bool
	var previousSessionState SessionState
	previousSessionState, keyExists = e.TykMiddleware.CheckSessionAndIdentityForValidKey(SessionID)

	if keyExists {

		lastUpdated, _ := strconv.Atoi(previousSessionState.LastUpdated)

		deadlineTs := int64(lastUpdated) + previousSessionState.IdExtractorDeadline

		if deadlineTs > time.Now().Unix() {
			e.PostProcess(r, previousSessionState, SessionID)
			returnOverrides = ReturnOverrides{
				ResponseCode: 200,
			}
		}
	}

	return SessionID, returnOverrides
}

// newExtractor is called from the CP middleware for every API that specifies extractor settings.
func newExtractor(referenceSpec *APISpec, mw *TykMiddleware) {
	var thisExtractor IdExtractor

	baseExtractor := BaseExtractor{&referenceSpec.CustomMiddleware.IdExtractor, mw, referenceSpec}

	// Initialize a extractor based on the API spec.
	switch referenceSpec.CustomMiddleware.IdExtractor.ExtractWith {
	case tykcommon.ValueExtractor:
		thisExtractor = &ValueExtractor{baseExtractor}
	case tykcommon.RegexExtractor:
		thisExtractor = &RegexExtractor{baseExtractor}
	case tykcommon.XPathExtractor:
		thisExtractor = &XPathExtractor{baseExtractor}
	}

	referenceSpec.CustomMiddleware.IdExtractor.Extractor = thisExtractor
}
<|endoftext|>


<commit_before>// Copyright 2021 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 slices defines various functions useful with slices of any type.
// Unless otherwise specified, these functions all apply to the elements
// of a slice at index 0 <= i < len(s).
package slices

import "golang.org/x/exp/constraints"

// Equal reports whether two slices are equal: the same length and all
// elements equal. If the lengths are different, Equal returns false.
// Otherwise, the elements are compared in increasing index order, and the
// comparison stops at the first unequal pair.
// Floating point NaNs are not considered equal.
func Equal[E comparable](s1, s2 []E) bool {
	if len(s1) != len(s2) {
		return false
	}
	for i := range s1 {
		if s1[i] != s2[i] {
			return false
		}
	}
	return true
}

// EqualFunc reports whether two slices are equal using a comparison
// function on each pair of elements. If the lengths are different,
// EqualFunc returns false. Otherwise, the elements are compared in
// increasing index order, and the comparison stops at the first index
// for which eq returns false.
func EqualFunc[E1, E2 any](s1 []E1, s2 []E2, eq func(E1, E2) bool) bool {
	if len(s1) != len(s2) {
		return false
	}
	for i, v1 := range s1 {
		v2 := s2[i]
		if !eq(v1, v2) {
			return false
		}
	}
	return true
}

// Compare compares the elements of s1 and s2.
// The elements are compared sequentially, starting at index 0,
// until one element is not equal to the other.
// The result of comparing the first non-matching elements is returned.
// If both slices are equal until one of them ends, the shorter slice is
// considered less than the longer one.
// The result is 0 if s1 == s2, -1 if s1 < s2, and +1 if s1 > s2.
// Comparisons involving floating point NaNs are ignored.
func Compare[E constraints.Ordered](s1, s2 []E) int {
	s2len := len(s2)
	for i, v1 := range s1 {
		if i >= s2len {
			return +1
		}
		v2 := s2[i]
		switch {
		case v1 < v2:
			return -1
		case v1 > v2:
			return +1
		}
	}
	if len(s1) < s2len {
		return -1
	}
	return 0
}

// CompareFunc is like Compare but uses a comparison function
// on each pair of elements. The elements are compared in increasing
// index order, and the comparisons stop after the first time cmp
// returns non-zero.
// The result is the first non-zero result of cmp; if cmp always
// returns 0 the result is 0 if len(s1) == len(s2), -1 if len(s1) < len(s2),
// and +1 if len(s1) > len(s2).
func CompareFunc[E1, E2 any](s1 []E1, s2 []E2, cmp func(E1, E2) int) int {
	s2len := len(s2)
	for i, v1 := range s1 {
		if i >= s2len {
			return +1
		}
		v2 := s2[i]
		if c := cmp(v1, v2); c != 0 {
			return c
		}
	}
	if len(s1) < s2len {
		return -1
	}
	return 0
}

// Index returns the index of the first occurrence of v in s,
// or -1 if not present.
func Index[E comparable](s []E, v E) int {
	for i, vs := range s {
		if v == vs {
			return i
		}
	}
	return -1
}

// IndexFunc returns the first index i satisfying f(s[i]),
// or -1 if none do.
func IndexFunc[E any](s []E, f func(E) bool) int {
	for i, v := range s {
		if f(v) {
			return i
		}
	}
	return -1
}

// Contains reports whether v is present in s.
func Contains[E comparable](s []E, v E) bool {
	return Index(s, v) >= 0
}

// Insert inserts the values v... into s at index i,
// returning the modified slice.
// In the returned slice r, r[i] == v[0].
// Insert panics if i is out of range.
// This function is O(len(s) + len(v)).
func Insert[S ~[]E, E any](s S, i int, v ...E) S {
	tot := len(s) + len(v)
	if tot <= cap(s) {
		s2 := s[:tot]
		copy(s2[i+len(v):], s[i:])
		copy(s2[i:], v)
		return s2
	}
	s2 := make(S, tot)
	copy(s2, s[:i])
	copy(s2[i:], v)
	copy(s2[i+len(v):], s[i:])
	return s2
}

// Delete removes the elements s[i:j] from s, returning the modified slice.
// Delete panics if s[i:j] is not a valid slice of s.
// Delete modifies the contents of the slice s; it does not create a new slice.
// Delete is O(len(s)-(j-i)), so if many items must be deleted, it is better to
// make a single call deleting them all together than to delete one at a time.
func Delete[S ~[]E, E any](s S, i, j int) S {
	return append(s[:i], s[j:]...)
}

// Clone returns a copy of the slice.
// The elements are copied using assignment, so this is a shallow clone.
func Clone[S ~[]E, E any](s S) S {
	// Preserve nil in case it matters.
	if s == nil {
		return nil
	}
	return append(S([]E{}), s...)
}

// Compact replaces consecutive runs of equal elements with a single copy.
// This is like the uniq command found on Unix.
// Compact modifies the contents of the slice s; it does not create a new slice.
func Compact[S ~[]E, E comparable](s S) S {
	if len(s) == 0 {
		return s
	}
	i := 1
	last := s[0]
	for _, v := range s[1:] {
		if v != last {
			s[i] = v
			i++
			last = v
		}
	}
	return s[:i]
}

// CompactFunc is like Compact but uses a comparison function.
func CompactFunc[S ~[]E, E any](s S, eq func(E, E) bool) S {
	if len(s) == 0 {
		return s
	}
	i := 1
	last := s[0]
	for _, v := range s[1:] {
		if !eq(v, last) {
			s[i] = v
			i++
			last = v
		}
	}
	return s[:i]
}

// Grow increases the slice's capacity, if necessary, to guarantee space for
// another n elements. After Grow(n), at least n elements can be appended
// to the slice without another allocation. If n is negative or too large to
// allocate the memory, Grow panics.
func Grow[S ~[]E, E any](s S, n int) S {
	return append(s, make(S, n)...)[:len(s)]
}

// Clip removes unused capacity from the slice, returning s[:len(s):len(s)].
func Clip[S ~[]E, E any](s S) S {
	return s[:len(s):len(s)]
}
<commit_msg>slices: clarify that Grow can modify elements past the length<commit_after>// Copyright 2021 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 slices defines various functions useful with slices of any type.
// Unless otherwise specified, these functions all apply to the elements
// of a slice at index 0 <= i < len(s).
package slices

import "golang.org/x/exp/constraints"

// Equal reports whether two slices are equal: the same length and all
// elements equal. If the lengths are different, Equal returns false.
// Otherwise, the elements are compared in increasing index order, and the
// comparison stops at the first unequal pair.
// Floating point NaNs are not considered equal.
func Equal[E comparable](s1, s2 []E) bool {
	if len(s1) != len(s2) {
		return false
	}
	for i := range s1 {
		if s1[i] != s2[i] {
			return false
		}
	}
	return true
}

// EqualFunc reports whether two slices are equal using a comparison
// function on each pair of elements. If the lengths are different,
// EqualFunc returns false. Otherwise, the elements are compared in
// increasing index order, and the comparison stops at the first index
// for which eq returns false.
func EqualFunc[E1, E2 any](s1 []E1, s2 []E2, eq func(E1, E2) bool) bool {
	if len(s1) != len(s2) {
		return false
	}
	for i, v1 := range s1 {
		v2 := s2[i]
		if !eq(v1, v2) {
			return false
		}
	}
	return true
}

// Compare compares the elements of s1 and s2.
// The elements are compared sequentially, starting at index 0,
// until one element is not equal to the other.
// The result of comparing the first non-matching elements is returned.
// If both slices are equal until one of them ends, the shorter slice is
// considered less than the longer one.
// The result is 0 if s1 == s2, -1 if s1 < s2, and +1 if s1 > s2.
// Comparisons involving floating point NaNs are ignored.
func Compare[E constraints.Ordered](s1, s2 []E) int {
	s2len := len(s2)
	for i, v1 := range s1 {
		if i >= s2len {
			return +1
		}
		v2 := s2[i]
		switch {
		case v1 < v2:
			return -1
		case v1 > v2:
			return +1
		}
	}
	if len(s1) < s2len {
		return -1
	}
	return 0
}

// CompareFunc is like Compare but uses a comparison function
// on each pair of elements. The elements are compared in increasing
// index order, and the comparisons stop after the first time cmp
// returns non-zero.
// The result is the first non-zero result of cmp; if cmp always
// returns 0 the result is 0 if len(s1) == len(s2), -1 if len(s1) < len(s2),
// and +1 if len(s1) > len(s2).
func CompareFunc[E1, E2 any](s1 []E1, s2 []E2, cmp func(E1, E2) int) int {
	s2len := len(s2)
	for i, v1 := range s1 {
		if i >= s2len {
			return +1
		}
		v2 := s2[i]
		if c := cmp(v1, v2); c != 0 {
			return c
		}
	}
	if len(s1) < s2len {
		return -1
	}
	return 0
}

// Index returns the index of the first occurrence of v in s,
// or -1 if not present.
func Index[E comparable](s []E, v E) int {
	for i, vs := range s {
		if v == vs {
			return i
		}
	}
	return -1
}

// IndexFunc returns the first index i satisfying f(s[i]),
// or -1 if none do.
func IndexFunc[E any](s []E, f func(E) bool) int {
	for i, v := range s {
		if f(v) {
			return i
		}
	}
	return -1
}

// Contains reports whether v is present in s.
func Contains[E comparable](s []E, v E) bool {
	return Index(s, v) >= 0
}

// Insert inserts the values v... into s at index i,
// returning the modified slice.
// In the returned slice r, r[i] == v[0].
// Insert panics if i is out of range.
// This function is O(len(s) + len(v)).
func Insert[S ~[]E, E any](s S, i int, v ...E) S {
	tot := len(s) + len(v)
	if tot <= cap(s) {
		s2 := s[:tot]
		copy(s2[i+len(v):], s[i:])
		copy(s2[i:], v)
		return s2
	}
	s2 := make(S, tot)
	copy(s2, s[:i])
	copy(s2[i:], v)
	copy(s2[i+len(v):], s[i:])
	return s2
}

// Delete removes the elements s[i:j] from s, returning the modified slice.
// Delete panics if s[i:j] is not a valid slice of s.
// Delete modifies the contents of the slice s; it does not create a new slice.
// Delete is O(len(s)-(j-i)), so if many items must be deleted, it is better to
// make a single call deleting them all together than to delete one at a time.
func Delete[S ~[]E, E any](s S, i, j int) S {
	return append(s[:i], s[j:]...)
}

// Clone returns a copy of the slice.
// The elements are copied using assignment, so this is a shallow clone.
func Clone[S ~[]E, E any](s S) S {
	// Preserve nil in case it matters.
	if s == nil {
		return nil
	}
	return append(S([]E{}), s...)
}

// Compact replaces consecutive runs of equal elements with a single copy.
// This is like the uniq command found on Unix.
// Compact modifies the contents of the slice s; it does not create a new slice.
func Compact[S ~[]E, E comparable](s S) S {
	if len(s) == 0 {
		return s
	}
	i := 1
	last := s[0]
	for _, v := range s[1:] {
		if v != last {
			s[i] = v
			i++
			last = v
		}
	}
	return s[:i]
}

// CompactFunc is like Compact but uses a comparison function.
func CompactFunc[S ~[]E, E any](s S, eq func(E, E) bool) S {
	if len(s) == 0 {
		return s
	}
	i := 1
	last := s[0]
	for _, v := range s[1:] {
		if !eq(v, last) {
			s[i] = v
			i++
			last = v
		}
	}
	return s[:i]
}

// Grow increases the slice's capacity, if necessary, to guarantee space for
// another n elements. After Grow(n), at least n elements can be appended
// to the slice without another allocation. Grow may modify elements of the
// slice between the length and the capacity. If n is negative or too large to
// allocate the memory, Grow panics.
func Grow[S ~[]E, E any](s S, n int) S {
	return append(s, make(S, n)...)[:len(s)]
}

// Clip removes unused capacity from the slice, returning s[:len(s):len(s)].
func Clip[S ~[]E, E any](s S) S {
	return s[:len(s):len(s)]
}
<|endoftext|>


<commit_before>package contractor

import (
	"testing"
	"time"

	"github.com/NebulousLabs/Sia/build"
	"github.com/NebulousLabs/Sia/modules"
	"github.com/NebulousLabs/Sia/types"
)

// uptimeHostDB overrides an existing hostDB so that it always returns
// isOffline == true for a specified address.
type uptimeHostDB struct {
	hostDB
	addr modules.NetAddress
}

func (u uptimeHostDB) Host(addr modules.NetAddress) (modules.HostDBEntry, bool) {
	host, ok := u.hostDB.Host(addr)
	if ok && addr == u.addr {
		// fake three scans over the past uptimeWindow, all of which failed
		badScan1 := modules.HostDBScan{Timestamp: time.Now().Add(-uptimeWindow * 2), Success: false}
		badScan2 := modules.HostDBScan{Timestamp: time.Now().Add(-uptimeWindow), Success: false}
		badScan3 := modules.HostDBScan{Timestamp: time.Now(), Success: false}
		host.ScanHistory = []modules.HostDBScan{badScan1, badScan2, badScan3}
	}
	return host, ok
}

// TestIntegrationReplaceOffline tests that when a host goes offline, its
// contract is eventually replaced.
func TestIntegrationReplaceOffline(t *testing.T) {
	if testing.Short() {
		t.SkipNow()
	}
	t.Parallel()
	h, c, m, err := newTestingTrio("TestIntegrationMonitorUptime")
	if err != nil {
		t.Fatal(err)
	}
	defer h.Close()

	// override IsOffline to always return true for h
	c.hdb = uptimeHostDB{c.hdb, h.ExternalSettings().NetAddress}

	// create another host
	dir := build.TempDir("contractor", "TestIntegrationMonitorUptime", "Host2")
	h2, err := newTestingHost(dir, c.cs.(modules.ConsensusSet), c.tpool.(modules.TransactionPool))
	if err != nil {
		t.Fatal(err)
	}

	// form a contract with h
	c.SetAllowance(modules.Allowance{
		Funds:       types.SiacoinPrecision.Mul64(100),
		Hosts:       1,
		Period:      100,
		RenewWindow: 10,
	})
	// we should have a contract, but it will be marked as offline due to the
	// hocked hostDB
	if len(c.contracts) != 1 {
		t.Fatal("contract not formed")
	}
	if len(c.onlineContracts()) != 0 {
		t.Fatal("contract should not be reported as online")
	}

	// announce the second host
	err = h2.Announce()
	if err != nil {
		t.Fatal(err)
	}

	// mine a block, processing the announcement
	m.AddBlock()

	// wait for hostdb to scan host
	for i := 0; i < 100 && len(c.hdb.RandomHosts(2, nil)) != 2; i++ {
		time.Sleep(50 * time.Millisecond)
	}
	if len(c.hdb.RandomHosts(2, nil)) != 2 {
		t.Fatal("host did not make it into the contractor hostdb in time", c.hdb.RandomHosts(2, nil))
	}

	// mine a block and wait for a new contract is formed. ProcessConsensusChange will
	// trigger managedFormAllowanceContracts, which should form a new contract
	// with h2
	m.AddBlock()
	for i := 0; i < 100 && len(c.Contracts()) != 1; i++ {
		time.Sleep(100 * time.Millisecond)
	}
	if len(c.Contracts()) != 1 {
		t.Fatal("contract was not replaced")
	}
	if c.Contracts()[0].NetAddress != h2.ExternalSettings().NetAddress {
		t.Fatal("contractor formed replacement contract with wrong host")
	}
}

// TestIsOffline tests the isOffline helper function.
func TestIsOffline(t *testing.T) {
	now := time.Now()
	oldBadScan := modules.HostDBScan{Timestamp: now.Add(-uptimeWindow * 2), Success: false}
	newBadScan := modules.HostDBScan{Timestamp: now.Add(-uptimeWindow / 2), Success: false}
	newGoodScan := modules.HostDBScan{Timestamp: now.Add(-uptimeWindow / 2), Success: true}
	currentBadScan := modules.HostDBScan{Timestamp: now, Success: false}

	tests := []struct {
		scans   []modules.HostDBScan
		offline bool
	}{
		// no data
		{nil, false},
		// not enough data
		{[]modules.HostDBScan{oldBadScan, newGoodScan}, false},
		// data covers small range
		{[]modules.HostDBScan{oldBadScan, oldBadScan, oldBadScan}, false},
		// data covers large range, but at least 1 scan succeded
		{[]modules.HostDBScan{oldBadScan, newGoodScan, currentBadScan}, false},
		// data covers large range, no scans succeded
		{[]modules.HostDBScan{oldBadScan, newBadScan, currentBadScan}, true},
	}
	for i, test := range tests {
		h := modules.HostDBEntry{ScanHistory: test.scans}
		if offline := isOffline(h); offline != test.offline {
			t.Errorf("IsOffline(%v) = %v, expected %v", i, offline, test.offline)
		}
	}
}
<commit_msg>fixup uptime tests<commit_after>package contractor

import (
	"testing"
	"time"

	"github.com/NebulousLabs/Sia/build"
	"github.com/NebulousLabs/Sia/modules"
	"github.com/NebulousLabs/Sia/types"
)

// offlineHostDB overrides an existing hostDB so that it always returns
// isOffline == true for a specified address.
type offlineHostDB struct {
	hostDB
	addr modules.NetAddress
}

// Host returns the host with address addr. If addr matches hdb.addr, the
// host's scan history will be modified to make the host appear offline.
func (hdb offlineHostDB) Host(addr modules.NetAddress) (modules.HostDBEntry, bool) {
	host, ok := hdb.hostDB.Host(addr)
	if ok && addr == hdb.addr {
		// fake three scans over the past uptimeWindow, all of which failed
		badScan1 := modules.HostDBScan{Timestamp: time.Now().Add(-uptimeWindow * 2), Success: false}
		badScan2 := modules.HostDBScan{Timestamp: time.Now().Add(-uptimeWindow), Success: false}
		badScan3 := modules.HostDBScan{Timestamp: time.Now(), Success: false}
		host.ScanHistory = []modules.HostDBScan{badScan1, badScan2, badScan3}
	}
	return host, ok
}

// TestIntegrationReplaceOffline tests that when a host goes offline, its
// contract is eventually replaced.
func TestIntegrationReplaceOffline(t *testing.T) {
	if testing.Short() {
		t.SkipNow()
	}
	t.Parallel()
	h, c, m, err := newTestingTrio("TestIntegrationMonitorUptime")
	if err != nil {
		t.Fatal(err)
	}
	defer h.Close()

	// override IsOffline to always return true for h
	c.hdb = offlineHostDB{c.hdb, h.ExternalSettings().NetAddress}

	// create another host
	dir := build.TempDir("contractor", "TestIntegrationMonitorUptime", "Host2")
	h2, err := newTestingHost(dir, c.cs.(modules.ConsensusSet), c.tpool.(modules.TransactionPool))
	if err != nil {
		t.Fatal(err)
	}

	// form a contract with h
	c.SetAllowance(modules.Allowance{
		Funds:       types.SiacoinPrecision.Mul64(100),
		Hosts:       1,
		Period:      100,
		RenewWindow: 10,
	})
	// we should have a contract, but it will be marked as offline due to the
	// hocked hostDB
	if len(c.contracts) != 1 {
		t.Fatal("contract not formed")
	}
	if len(c.onlineContracts()) != 0 {
		t.Fatal("contract should not be reported as online")
	}

	// announce the second host
	err = h2.Announce()
	if err != nil {
		t.Fatal(err)
	}

	// mine a block, processing the announcement
	m.AddBlock()

	// wait for hostdb to scan host
	for i := 0; i < 100 && len(c.hdb.RandomHosts(2, nil)) != 2; i++ {
		time.Sleep(50 * time.Millisecond)
	}
	if len(c.hdb.RandomHosts(2, nil)) != 2 {
		t.Fatal("host did not make it into the contractor hostdb in time", c.hdb.RandomHosts(2, nil))
	}

	// mine a block and wait for a new contract is formed. ProcessConsensusChange will
	// trigger managedFormAllowanceContracts, which should form a new contract
	// with h2
	m.AddBlock()
	for i := 0; i < 100 && len(c.Contracts()) != 1; i++ {
		time.Sleep(100 * time.Millisecond)
	}
	if len(c.Contracts()) != 1 {
		t.Fatal("contract was not replaced")
	}
	if c.Contracts()[0].NetAddress != h2.ExternalSettings().NetAddress {
		t.Fatal("contractor formed replacement contract with wrong host")
	}
}

// TestIsOffline tests the isOffline helper function.
func TestIsOffline(t *testing.T) {
	now := time.Now()
	oldBadScan := modules.HostDBScan{Timestamp: now.Add(-uptimeWindow * 2), Success: false}
	oldGoodScan := modules.HostDBScan{Timestamp: now.Add(-uptimeWindow * 2), Success: true}
	newBadScan := modules.HostDBScan{Timestamp: now.Add(-uptimeWindow / 2), Success: false}
	newGoodScan := modules.HostDBScan{Timestamp: now.Add(-uptimeWindow / 2), Success: true}
	currentBadScan := modules.HostDBScan{Timestamp: now, Success: false}
	currentGoodScan := modules.HostDBScan{Timestamp: now, Success: true}

	tests := []struct {
		scans   []modules.HostDBScan
		offline bool
	}{
		// no data
		{nil, false},
		// not enough data
		{[]modules.HostDBScan{oldBadScan, newGoodScan}, false},
		// data covers small range
		{[]modules.HostDBScan{oldBadScan, oldBadScan, oldBadScan}, false},
		// data covers large range, but at least 1 scan succeded
		{[]modules.HostDBScan{oldBadScan, newGoodScan, currentBadScan}, false},
		// data covers large range, no scans succeded
		{[]modules.HostDBScan{oldBadScan, newBadScan, currentBadScan}, true},
		// recent scan was good (and within uptimeWindow of oldBadScan, but that shouldn't matter)
		{[]modules.HostDBScan{oldBadScan, oldBadScan, oldBadScan, oldGoodScan}, false},
		// recent scan was good (and outside uptimeWindow of oldBadScan, but that shouldn't matter)
		{[]modules.HostDBScan{oldBadScan, oldBadScan, oldBadScan, currentGoodScan}, false},
	}
	for i, test := range tests {
		h := modules.HostDBEntry{ScanHistory: test.scans}
		if offline := isOffline(h); offline != test.offline {
			t.Errorf("IsOffline(%v) = %v, expected %v", i, offline, test.offline)
		}
	}
}
<|endoftext|>


<commit_before>package sobjects

type Lead struct {
	BaseSObject
	Company       string `force:",omitempty"`
	ConvertedDate string `force:",omitempty"`
	FirstName     string `force:",omitempty"`
	IsConverted   bool   `force:",omitempty"`
	IsDeleted     bool   `force:",omitempty"`
	LastName      string `force:",omitempty"`
	Status        string `force:",omitempty"`
}

func (t *Lead) ApiName() string {
	return "Lead"
}

type LeadQueryResponse struct {
	BaseQuery
	Records []Lead `json:"Records" force:"records"`
}
<commit_msg>Add the OwnerId field.<commit_after>package sobjects

type Lead struct {
	BaseSObject
	Company       string `force:",omitempty"`
	ConvertedDate string `force:",omitempty"`
	FirstName     string `force:",omitempty"`
	IsConverted   bool   `force:",omitempty"`
	IsDeleted     bool   `force:",omitempty"`
	LastName      string `force:",omitempty"`
	OwnerId       string `force:",omitempty"`
	Status        string `force:",omitempty"`
}

func (t *Lead) ApiName() string {
	return "Lead"
}

type LeadQueryResponse struct {
	BaseQuery
	Records []Lead `json:"Records" force:"records"`
}
<|endoftext|>


<commit_before>package acceptance

import (
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
)

var _ = Describe("UAA", func() {
	It("should return UAA user metrics", func() {
		Expect(metricFamilies).To(SatisfyAll(
			HaveKey("paas_uaa_users_count"),
			HaveKey("paas_uaa_active_users_count"),
		))
	})
})
<commit_msg>metrics: uaa acceptance uses Eventually<commit_after>package acceptance

import (
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
)

var _ = Describe("UAA", func() {
	It("should return UAA user metrics", func() {
		Eventually(getMetrics).Should(SatisfyAll(
			HaveKey("paas_uaa_users_count"),
			HaveKey("paas_uaa_active_users_count"),
		))
	})
})
<|endoftext|>


<commit_before>package web

import (
	"fmt"
	"net/http"
	"path/filepath"
	"sort"
	"strings"
	"time"

	"github.com/labstack/echo"
	"github.com/labstack/echo/middleware"
	log "github.com/sirupsen/logrus"

	"github.com/erik/gruppo/drive"
	"github.com/erik/gruppo/model"
	"github.com/erik/gruppo/render"
	"github.com/erik/gruppo/store"
)

type Configuration struct {
	Host         string
	Port         int
	TemplatePath string
}

type siteMapping map[string][]model.Site

type Web struct {
	echo  *echo.Echo
	db    *store.RedisStore
	conf  Configuration
	sites siteMapping // host -> [site, ...]
}

// buildSiteMap generates a mapping of `host -> [site, ...]`, with sites sorted
// by the longest base path. This is to ensure e.g. example.com/foobar/ matches
// before example.com/foo
func buildSiteMap(sites []model.Site) siteMapping {
	var m siteMapping = make(siteMapping, len(sites))

	for _, s := range sites {
		if _, ok := m[s.Host]; !ok {
			m[s.Host] = []model.Site{}
		}

		m[s.Host] = append(m[s.Host], s)
	}

	for _, v := range m {
		// We want to match the longest path first.
		sort.Slice(v, func(i, j int) bool {
			return len(v[i].BasePath) > len(v[j].BasePath)
		})
	}

	log.WithFields(log.Fields{"map": m}).Debug("built site map")

	return m
}

func logger() echo.MiddlewareFunc {
	return func(next echo.HandlerFunc) echo.HandlerFunc {
		return func(c echo.Context) error {
			req := c.Request()
			res := c.Response()
			start := time.Now()

			var err error
			if err = next(c); err != nil {
				c.Error(err)
			}

			stop := time.Now()

			log.Infof("%s %s %s %s - %d [%s]",
				req.Host,
				c.RealIP(),
				req.Method,
				req.RequestURI,
				res.Status,
				stop.Sub(start).String(),
			)

			return err
		}
	}
}

func (w *Web) RegisterDriveHooks(c *drive.Client) error {
	route := c.ChangeHookRoute()

	log.WithFields(log.Fields{}).Info("setting up drive hook")

	w.echo.POST(route, func(ctx echo.Context) error {
		if err := c.HandleChangeHook(ctx.Request()); err != nil {
			log.WithError(err).
				WithFields(log.Fields{}).
				Error("failed to handle change hook")

			return ctx.String(http.StatusInternalServerError, "something bad")
		}

		return ctx.String(http.StatusOK, "")
	})

	return nil
}

func (w *Web) registerRoutes() {
	// Site-specific
	w.echo.GET("/*", func(c echo.Context) error {
		site, found := w.siteForContext(c)

		if !found {
			return c.String(http.StatusNotFound, "unknown site")
		}

		return w.handleSiteRequest(site, c)
	})
}

// Look up the correct Site configuration for a given request by matching host
// and path.
func (w Web) siteForContext(c echo.Context) (model.Site, bool) {
	for host, sites := range w.sites {
		if host != c.Request().Host {
			continue
		}

		for _, site := range sites {
			path := c.Request().URL.String()
			if strings.HasPrefix(path, site.BasePath) {
				return site, true
			}
		}
	}

	return model.Site{}, false
}

func pageForSlug(site model.Site, slug string) (model.PageConfig, bool) {
	for _, pg := range site.Pages {
		if pg.URL == slug {
			return pg, true
		}
	}

	return model.PageConfig{}, false
}

func assetForSlug(site model.Site, slug string) (string, bool) {
	if strings.HasPrefix(slug, site.AssetPath) {
		return strings.TrimPrefix(slug, site.AssetPath), true
	}

	return "", false
}

func (w Web) handlePage(site model.Site, pg model.PageConfig, slug string, c echo.Context) error {
	posts, err := w.db.ListPostOverviews(site, slug, 0, 10)
	if err != nil {
		return err
	}

	html, err := render.Render(pg.Template, site.Theme, &render.Context{
		Title: pg.Title,
		Site:  &site,
		Posts: posts,
	})

	if err != nil {
		log.WithFields(log.Fields{
			"error":    err,
			"site":     site.HostPathPrefix(),
			"template": pg.Template,
			"theme":    site.Theme,
		}).Error("failed to render page template")

		return err
	}

	return c.HTML(http.StatusOK, html)
}

func (w Web) handlePost(site model.Site, post model.Post, c echo.Context) error {
	html, err := render.Render("post", site.Theme, &render.Context{
		Title: post.Title,
		Post:  &post,
	})

	if err != nil {
		log.WithFields(log.Fields{
			"error": err,
			"site":  site.HostPathPrefix(),
			"theme": site.Theme,
		}).Error("failed to render post template")

		return c.String(http.StatusInternalServerError, "failed to render")
	}

	return c.HTML(http.StatusOK, html)
}

func (w Web) handleAsset(site model.Site, slug string, c echo.Context) error {
	// FIXME: this might be vulnerable to directory traversal
	slug = filepath.Clean(slug)
	path := filepath.Join(site.SiteDir, "assets", slug)
	return c.File(path)
}

// Main URL routing dispatch.
func (w Web) handleSiteRequest(site model.Site, c echo.Context) error {
	slug := strings.TrimPrefix(c.Request().URL.String(), site.BasePath)

	// Slugs should be absolute
	if !strings.HasPrefix(slug, "/") {
		slug = "/" + slug
	}

	if pg, found := pageForSlug(site, slug); found {
		return w.handlePage(site, pg, slug, c)
	}

	if asset, found := assetForSlug(site, slug); found {
		return w.handleAsset(site, asset, c)
	}

	if post, err := w.db.GetPost(site, slug); post != nil {
		return w.handlePost(site, *post, c)
	} else if err != nil {
		log.WithFields(log.Fields{
			"error": err,
			"site":  site.HostPathPrefix(),
			"slug":  slug,
		}).Error("failed to fetch post")

		return c.String(http.StatusInternalServerError, "something went wrong")
	}

	if slugs, err := w.db.ListMatchingSlugs(site, slug); len(slugs) > 0 {
		if site.IndexPage != nil && len(slugs) > 0 {
			pg := model.PageConfig{
				URL:      slug,
				Template: site.IndexPage.Template,
				Title:    site.IndexPage.Title,
			}

			return w.handlePage(site, pg, slug, c)
		}

	} else if err != nil {
		log.WithFields(log.Fields{
			"error": err,
			"site":  site.HostPathPrefix(),
			"slug":  slug,
		}).Error("something went wrong")

		return c.String(http.StatusInternalServerError, "something went wrong")
	}

	return c.String(http.StatusNotFound, "404.")
}

func (w *Web) registerMiddleware() {
	// Echo's logger sucks, use a custom one
	w.echo.Use(logger())
	w.echo.Use(middleware.Recover())
}

func New(sites []model.Site, conf Configuration, db *store.RedisStore) Web {
	e := echo.New()
	e.HideBanner = true

	w := Web{
		echo:  e,
		db:    db,
		conf:  conf,
		sites: buildSiteMap(sites),
	}

	w.registerMiddleware()
	w.registerRoutes()

	return w
}

func (w Web) Serve() error {
	address := fmt.Sprintf("%s:%d", w.conf.Host, w.conf.Port)

	return w.echo.Start(address)
}
<commit_msg>Fix post not found<commit_after>package web

import (
	"fmt"
	"net/http"
	"path/filepath"
	"sort"
	"strings"
	"time"

	"github.com/labstack/echo"
	"github.com/labstack/echo/middleware"
	log "github.com/sirupsen/logrus"

	"github.com/erik/gruppo/drive"
	"github.com/erik/gruppo/model"
	"github.com/erik/gruppo/render"
	"github.com/erik/gruppo/store"
)

type Configuration struct {
	Host         string
	Port         int
	TemplatePath string
}

type siteMapping map[string][]model.Site

type Web struct {
	echo  *echo.Echo
	db    *store.RedisStore
	conf  Configuration
	sites siteMapping // host -> [site, ...]
}

// buildSiteMap generates a mapping of `host -> [site, ...]`, with sites sorted
// by the longest base path. This is to ensure e.g. example.com/foobar/ matches
// before example.com/foo
func buildSiteMap(sites []model.Site) siteMapping {
	var m siteMapping = make(siteMapping, len(sites))

	for _, s := range sites {
		if _, ok := m[s.Host]; !ok {
			m[s.Host] = []model.Site{}
		}

		m[s.Host] = append(m[s.Host], s)
	}

	for _, v := range m {
		// We want to match the longest path first.
		sort.Slice(v, func(i, j int) bool {
			return len(v[i].BasePath) > len(v[j].BasePath)
		})
	}

	log.WithFields(log.Fields{"map": m}).Debug("built site map")

	return m
}

func logger() echo.MiddlewareFunc {
	return func(next echo.HandlerFunc) echo.HandlerFunc {
		return func(c echo.Context) error {
			req := c.Request()
			res := c.Response()
			start := time.Now()

			var err error
			if err = next(c); err != nil {
				c.Error(err)
			}

			stop := time.Now()

			log.Infof("%s %s %s %s - %d [%s]",
				req.Host,
				c.RealIP(),
				req.Method,
				req.RequestURI,
				res.Status,
				stop.Sub(start).String(),
			)

			return err
		}
	}
}

func (w *Web) RegisterDriveHooks(c *drive.Client) error {
	route := c.ChangeHookRoute()

	log.WithFields(log.Fields{}).Info("setting up drive hook")

	w.echo.POST(route, func(ctx echo.Context) error {
		if err := c.HandleChangeHook(ctx.Request()); err != nil {
			log.WithError(err).
				WithFields(log.Fields{}).
				Error("failed to handle change hook")

			return ctx.String(http.StatusInternalServerError, "something bad")
		}

		return ctx.String(http.StatusOK, "")
	})

	return nil
}

func (w *Web) registerRoutes() {
	// Site-specific
	w.echo.GET("/*", func(c echo.Context) error {
		site, found := w.siteForContext(c)

		if !found {
			return c.String(http.StatusNotFound, "unknown site")
		}

		return w.handleSiteRequest(site, c)
	})
}

// Look up the correct Site configuration for a given request by matching host
// and path.
func (w Web) siteForContext(c echo.Context) (model.Site, bool) {
	for host, sites := range w.sites {
		if host != c.Request().Host {
			continue
		}

		for _, site := range sites {
			path := c.Request().URL.String()
			if strings.HasPrefix(path, site.BasePath) {
				return site, true
			}
		}
	}

	return model.Site{}, false
}

func pageForSlug(site model.Site, slug string) (model.PageConfig, bool) {
	for _, pg := range site.Pages {
		if pg.URL == slug {
			return pg, true
		}
	}

	return model.PageConfig{}, false
}

func assetForSlug(site model.Site, slug string) (string, bool) {
	if strings.HasPrefix(slug, site.AssetPath) {
		return strings.TrimPrefix(slug, site.AssetPath), true
	}

	return "", false
}

func (w Web) handlePage(site model.Site, pg model.PageConfig, slug string, c echo.Context) error {
	posts, err := w.db.ListPostOverviews(site, slug, 0, 10)
	if err != nil {
		return err
	}

	html, err := render.Render(pg.Template, site.Theme, &render.Context{
		Title: pg.Title,
		Site:  &site,
		Posts: posts,
	})

	if err != nil {
		log.WithFields(log.Fields{
			"error":    err,
			"site":     site.HostPathPrefix(),
			"template": pg.Template,
			"theme":    site.Theme,
		}).Error("failed to render page template")

		return err
	}

	return c.HTML(http.StatusOK, html)
}

func (w Web) handlePost(site model.Site, post model.Post, c echo.Context) error {
	html, err := render.Render("post", site.Theme, &render.Context{
		Title: post.Title,
		Post:  &post,
	})

	if err != nil {
		log.WithFields(log.Fields{
			"error": err,
			"site":  site.HostPathPrefix(),
			"theme": site.Theme,
		}).Error("failed to render post template")

		return c.String(http.StatusInternalServerError, "failed to render")
	}

	return c.HTML(http.StatusOK, html)
}

func (w Web) handleAsset(site model.Site, slug string, c echo.Context) error {
	// FIXME: this might be vulnerable to directory traversal
	slug = filepath.Clean(slug)
	path := filepath.Join(site.SiteDir, "assets", slug)
	return c.File(path)
}

// Main URL routing dispatch.
func (w Web) handleSiteRequest(site model.Site, c echo.Context) error {
	slug := strings.TrimPrefix(c.Request().URL.String(), site.BasePath)

	// Slugs should be absolute
	if !strings.HasPrefix(slug, "/") {
		slug = "/" + slug
	}

	if pg, found := pageForSlug(site, slug); found {
		return w.handlePage(site, pg, slug, c)
	}

	if asset, found := assetForSlug(site, slug); found {
		return w.handleAsset(site, asset, c)
	}

	if post, err := w.db.GetPost(site, slug); post != nil {
		return w.handlePost(site, *post, c)
	} else if err != store.NotFound {
		log.WithFields(log.Fields{
			"error": err,
			"site":  site.HostPathPrefix(),
			"slug":  slug,
		}).Error("failed to fetch post")

		return c.String(http.StatusInternalServerError, "something went wrong")
	}

	if slugs, err := w.db.ListMatchingSlugs(site, slug); len(slugs) > 0 {
		if site.IndexPage != nil && len(slugs) > 0 {
			pg := model.PageConfig{
				URL:      slug,
				Template: site.IndexPage.Template,
				Title:    site.IndexPage.Title,
			}

			return w.handlePage(site, pg, slug, c)
		}

	} else if err != nil {
		log.WithFields(log.Fields{
			"error": err,
			"site":  site.HostPathPrefix(),
			"slug":  slug,
		}).Error("something went wrong")

		return c.String(http.StatusInternalServerError, "something went wrong")
	}

	return c.String(http.StatusNotFound, "404.")
}

func (w *Web) registerMiddleware() {
	// Echo's logger sucks, use a custom one
	w.echo.Use(logger())
	w.echo.Use(middleware.Recover())
}

func New(sites []model.Site, conf Configuration, db *store.RedisStore) Web {
	e := echo.New()
	e.HideBanner = true

	w := Web{
		echo:  e,
		db:    db,
		conf:  conf,
		sites: buildSiteMap(sites),
	}

	w.registerMiddleware()
	w.registerRoutes()

	return w
}

func (w Web) Serve() error {
	address := fmt.Sprintf("%s:%d", w.conf.Host, w.conf.Port)

	return w.echo.Start(address)
}
<|endoftext|>


<commit_before>package commands

import (
	"context"
	"testing"

	cmdkit "gx/ipfs/QmceUdzxkimdYsgtX733uNgzf1DLHyBKN6ehGSp85ayppM/go-ipfs-cmdkit"
	cmds "gx/ipfs/QmfVXM8xWBJZZMC3mJkv64dkWUeoqGKTcKDSMtiJ6AdZXM/go-ipfs-cmds"
)

func TestGetOutputPath(t *testing.T) {
	cases := []struct {
		args    []string
		opts    cmdkit.OptMap
		outPath string
	}{
		{
			args: []string{"/ipns/multiformats.io/"},
			opts: map[string]interface{}{
				"output": "takes-precedence",
			},
			outPath: "takes-precedence",
		},
		{
			args: []string{"/ipns/multiformats.io/", "some-other-arg-to-be-ignored"},
			opts: cmdkit.OptMap{
				"output": "takes-precedence",
			},
			outPath: "takes-precedence",
		},
		{
			args:    []string{"/ipns/multiformats.io/"},
			outPath: "multiformats.io",
			opts:    cmdkit.OptMap{},
		},
		{
			args:    []string{"/ipns/multiformats.io/logo.svg/"},
			outPath: "logo.svg",
			opts:    cmdkit.OptMap{},
		},
		{
			args:    []string{"/ipns/multiformats.io", "some-other-arg-to-be-ignored"},
			outPath: "multiformats.io",
			opts:    cmdkit.OptMap{},
		},
	}

	_, err := GetCmd.GetOptions([]string{})
	if err != nil {
		t.Fatalf("error getting default command options: %v", err)
	}

	for _, tc := range cases {
		req, err := cmds.NewRequest(context.TODO(), []string{}, tc.opts, tc.args, nil, GetCmd)
		if err != nil {
			t.Fatalf("error creating a command request: %v", err)
		}
		if outPath := getOutPath(req); outPath != tc.outPath {
			t.Errorf("expected outPath %s to be %s", outPath, tc.outPath)
		}
	}
}
<commit_msg>avoid using the TODO context in tests<commit_after>package commands

import (
	"context"
	"fmt"
	"testing"

	cmdkit "gx/ipfs/QmceUdzxkimdYsgtX733uNgzf1DLHyBKN6ehGSp85ayppM/go-ipfs-cmdkit"
	cmds "gx/ipfs/QmfVXM8xWBJZZMC3mJkv64dkWUeoqGKTcKDSMtiJ6AdZXM/go-ipfs-cmds"
)

func TestGetOutputPath(t *testing.T) {
	cases := []struct {
		args    []string
		opts    cmdkit.OptMap
		outPath string
	}{
		{
			args: []string{"/ipns/multiformats.io/"},
			opts: map[string]interface{}{
				"output": "takes-precedence",
			},
			outPath: "takes-precedence",
		},
		{
			args: []string{"/ipns/multiformats.io/", "some-other-arg-to-be-ignored"},
			opts: cmdkit.OptMap{
				"output": "takes-precedence",
			},
			outPath: "takes-precedence",
		},
		{
			args:    []string{"/ipns/multiformats.io/"},
			outPath: "multiformats.io",
			opts:    cmdkit.OptMap{},
		},
		{
			args:    []string{"/ipns/multiformats.io/logo.svg/"},
			outPath: "logo.svg",
			opts:    cmdkit.OptMap{},
		},
		{
			args:    []string{"/ipns/multiformats.io", "some-other-arg-to-be-ignored"},
			outPath: "multiformats.io",
			opts:    cmdkit.OptMap{},
		},
	}

	_, err := GetCmd.GetOptions([]string{})
	if err != nil {
		t.Fatalf("error getting default command options: %v", err)
	}

	for i, tc := range cases {
		t.Run(fmt.Sprintf("%s-%d", t.Name(), i), func(t *testing.T) {
			ctx, cancel := context.WithCancel(context.Background())
			defer cancel()

			req, err := cmds.NewRequest(ctx, []string{}, tc.opts, tc.args, nil, GetCmd)
			if err != nil {
				t.Fatalf("error creating a command request: %v", err)
			}

			if outPath := getOutPath(req); outPath != tc.outPath {
				t.Errorf("expected outPath %s to be %s", outPath, tc.outPath)
			}
		})
	}
}
<|endoftext|>


<commit_before>package commands

import (
	"errors"
	"fmt"

	cmds "github.com/jbenet/go-ipfs/commands"
	core "github.com/jbenet/go-ipfs/core"
	crypto "github.com/jbenet/go-ipfs/crypto"
	nsys "github.com/jbenet/go-ipfs/namesys"
	u "github.com/jbenet/go-ipfs/util"
)

type PublishOutput struct {
	Name  string
	Value string
}

var publishCmd = &cmds.Command{
	Help: "TODO",
	Run: func(res cmds.Response, req cmds.Request) {
		n := req.Context().Node
		args := req.Arguments()

		if n.Identity == nil {
			res.SetError(errors.New("Identity not loaded!"), cmds.ErrNormal)
			return
		}

		// name := ""
		ref := ""

		switch len(args) {
		case 2:
			// name = args[0]
			ref = args[1].(string)
			res.SetError(errors.New("keychains not yet implemented"), cmds.ErrNormal)
			return
		case 1:
			// name = n.Identity.ID.String()
			ref = args[0].(string)

		default:
			res.SetError(fmt.Errorf("Publish expects 1 or 2 args; got %d.", len(args)), cmds.ErrClient)
		}

		// TODO n.Keychain.Get(name).PrivKey
		k := n.Identity.PrivKey()
		publishOutput, err := publish(n, k, ref)

		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
		}
		res.SetOutput(publishOutput)
	},
	Marshallers: map[cmds.EncodingType]cmds.Marshaller{
		cmds.Text: func(res cmds.Response) ([]byte, error) {
			v := res.Output().(*PublishOutput)
			s := fmt.Sprintf("Published name %s to %s\n", v.Name, v.Value)
			return []byte(s), nil
		},
	},
	Type: &PublishOutput{},
}

func publish(n *core.IpfsNode, k crypto.PrivKey, ref string) (*PublishOutput, error) {
	pub := nsys.NewRoutingPublisher(n.Routing)
	err := pub.Publish(k, ref)
	if err != nil {
		return nil, err
	}

	hash, err := k.GetPublic().Hash()
	if err != nil {
		return nil, err
	}

	return &PublishOutput{
		Name:  u.Key(hash).String(),
		Value: ref,
	}, nil
}
<commit_msg>core/commands2: Added argument definitions to 'publish'<commit_after>package commands

import (
	"errors"
	"fmt"

	cmds "github.com/jbenet/go-ipfs/commands"
	core "github.com/jbenet/go-ipfs/core"
	crypto "github.com/jbenet/go-ipfs/crypto"
	nsys "github.com/jbenet/go-ipfs/namesys"
	u "github.com/jbenet/go-ipfs/util"
)

type IpnsEntry struct {
	Name  string
	Value string
}

var publishCmd = &cmds.Command{
	Arguments: []cmds.Argument{
		cmds.Argument{"name", cmds.ArgString, false, false},
		cmds.Argument{"object", cmds.ArgString, true, false},
	},
	Help: "TODO",
	Run: func(res cmds.Response, req cmds.Request) {
		n := req.Context().Node
		args := req.Arguments()

		if n.Identity == nil {
			res.SetError(errors.New("Identity not loaded!"), cmds.ErrNormal)
			return
		}

		// name := ""
		ref := ""

		switch len(args) {
		case 2:
			// name = args[0]
			ref = args[1].(string)
			res.SetError(errors.New("keychains not yet implemented"), cmds.ErrNormal)
			return
		case 1:
			// name = n.Identity.ID.String()
			ref = args[0].(string)
		}

		// TODO n.Keychain.Get(name).PrivKey
		k := n.Identity.PrivKey()
		publishOutput, err := publish(n, k, ref)

		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
		}
		res.SetOutput(publishOutput)
	},
	Marshallers: map[cmds.EncodingType]cmds.Marshaller{
		cmds.Text: func(res cmds.Response) ([]byte, error) {
			v := res.Output().(*IpnsEntry)
			s := fmt.Sprintf("Published name %s to %s\n", v.Name, v.Value)
			return []byte(s), nil
		},
	},
	Type: &IpnsEntry{},
}

func publish(n *core.IpfsNode, k crypto.PrivKey, ref string) (*IpnsEntry, error) {
	pub := nsys.NewRoutingPublisher(n.Routing)
	err := pub.Publish(k, ref)
	if err != nil {
		return nil, err
	}

	hash, err := k.GetPublic().Hash()
	if err != nil {
		return nil, err
	}

	return &IpnsEntry{
		Name:  u.Key(hash).String(),
		Value: ref,
	}, nil
}
<|endoftext|>


<commit_before>// Copyright 2013 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package local_test

import (
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"

	jc "github.com/juju/testing/checkers"
	gc "launchpad.net/gocheck"

	"launchpad.net/juju-core/agent/mongo"
	coreCloudinit "launchpad.net/juju-core/cloudinit"
	"launchpad.net/juju-core/constraints"
	"launchpad.net/juju-core/container"
	"launchpad.net/juju-core/container/lxc"
	containertesting "launchpad.net/juju-core/container/testing"
	"launchpad.net/juju-core/environs"
	"launchpad.net/juju-core/environs/cloudinit"
	"launchpad.net/juju-core/environs/config"
	"launchpad.net/juju-core/environs/jujutest"
	envtesting "launchpad.net/juju-core/environs/testing"
	"launchpad.net/juju-core/environs/tools"
	"launchpad.net/juju-core/juju/arch"
	"launchpad.net/juju-core/juju/osenv"
	"launchpad.net/juju-core/provider/local"
	"launchpad.net/juju-core/state/api/params"
	coretesting "launchpad.net/juju-core/testing"
	"launchpad.net/juju-core/upstart"
)

const echoCommandScript = "#!/bin/sh\necho $0 \"$@\" >> $0.args"

type environSuite struct {
	baseProviderSuite
	envtesting.ToolsFixture
}

var _ = gc.Suite(&environSuite{})

func (s *environSuite) SetUpTest(c *gc.C) {
	s.baseProviderSuite.SetUpTest(c)
	s.ToolsFixture.SetUpTest(c)
}

func (s *environSuite) TearDownTest(c *gc.C) {
	s.ToolsFixture.TearDownTest(c)
	s.baseProviderSuite.TearDownTest(c)
}

func (*environSuite) TestOpenFailsWithProtectedDirectories(c *gc.C) {
	testConfig := minimalConfig(c)
	testConfig, err := testConfig.Apply(map[string]interface{}{
		"root-dir": "/usr/lib/juju",
	})
	c.Assert(err, gc.IsNil)

	environ, err := local.Provider.Open(testConfig)
	c.Assert(err, gc.ErrorMatches, "mkdir .* permission denied")
	c.Assert(environ, gc.IsNil)
}

func (s *environSuite) TestNameAndStorage(c *gc.C) {
	testConfig := minimalConfig(c)
	environ, err := local.Provider.Open(testConfig)
	c.Assert(err, gc.IsNil)
	c.Assert(environ.Name(), gc.Equals, "test")
	c.Assert(environ.Storage(), gc.NotNil)
}

func (s *environSuite) TestGetToolsMetadataSources(c *gc.C) {
	testConfig := minimalConfig(c)
	environ, err := local.Provider.Open(testConfig)
	c.Assert(err, gc.IsNil)
	sources, err := tools.GetMetadataSources(environ)
	c.Assert(err, gc.IsNil)
	c.Assert(len(sources), gc.Equals, 1)
	url, err := sources[0].URL("")
	c.Assert(err, gc.IsNil)
	c.Assert(strings.Contains(url, "/tools"), jc.IsTrue)
}

func (*environSuite) TestSupportedArchitectures(c *gc.C) {
	testConfig := minimalConfig(c)
	environ, err := local.Provider.Open(testConfig)
	c.Assert(err, gc.IsNil)
	arches, err := environ.SupportedArchitectures()
	c.Assert(err, gc.IsNil)
	for _, a := range arches {
		c.Assert(arch.IsSupportedArch(a), jc.IsTrue)
	}
}

func (*environSuite) TestSupportNetworks(c *gc.C) {
	testConfig := minimalConfig(c)
	environ, err := local.Provider.Open(testConfig)
	c.Assert(err, gc.IsNil)
	c.Assert(environ.SupportNetworks(), jc.IsFalse)
}

type localJujuTestSuite struct {
	baseProviderSuite
	jujutest.Tests
	oldUpstartLocation string
	testPath           string
	fakesudo           string
}

func (s *localJujuTestSuite) SetUpTest(c *gc.C) {
	s.baseProviderSuite.SetUpTest(c)
	// Construct the directories first.
	err := local.CreateDirs(c, minimalConfig(c))
	c.Assert(err, gc.IsNil)
	s.testPath = c.MkDir()
	s.fakesudo = filepath.Join(s.testPath, "sudo")
	s.PatchEnvPathPrepend(s.testPath)

	// Write a fake "sudo" which records its args to sudo.args.
	err = ioutil.WriteFile(s.fakesudo, []byte(echoCommandScript), 0755)
	c.Assert(err, gc.IsNil)

	// Add in an admin secret
	s.Tests.TestConfig["admin-secret"] = "sekrit"
	s.PatchValue(local.CheckIfRoot, func() bool { return false })
	s.Tests.SetUpTest(c)

	s.PatchValue(local.FinishBootstrap, func(mcfg *cloudinit.MachineConfig, cloudcfg *coreCloudinit.Config, ctx environs.BootstrapContext) error {
		return nil
	})
}

func (s *localJujuTestSuite) TearDownTest(c *gc.C) {
	s.Tests.TearDownTest(c)
	s.baseProviderSuite.TearDownTest(c)
}

func (s *localJujuTestSuite) MakeTool(c *gc.C, name, script string) {
	path := filepath.Join(s.testPath, name)
	script = "#!/bin/bash\n" + script
	err := ioutil.WriteFile(path, []byte(script), 0755)
	c.Assert(err, gc.IsNil)
}

func (s *localJujuTestSuite) StoppedStatus(c *gc.C) {
	s.MakeTool(c, "status", `echo "some-service stop/waiting"`)
}

func (s *localJujuTestSuite) RunningStatus(c *gc.C) {
	s.MakeTool(c, "status", `echo "some-service start/running, process 123"`)
}

var _ = gc.Suite(&localJujuTestSuite{
	Tests: jujutest.Tests{
		TestConfig: minimalConfigValues(),
	},
})

func (s *localJujuTestSuite) TestStartStop(c *gc.C) {
	c.Skip("StartInstance not implemented yet.")
}

func (s *localJujuTestSuite) testBootstrap(c *gc.C, cfg *config.Config) (env environs.Environ) {
	ctx := coretesting.Context(c)
	environ, err := local.Provider.Prepare(ctx, cfg)
	c.Assert(err, gc.IsNil)
	envtesting.UploadFakeTools(c, environ.Storage())
	defer environ.Storage().RemoveAll()
	err = environ.Bootstrap(ctx, environs.BootstrapParams{})
	c.Assert(err, gc.IsNil)
	return environ
}

func (s *localJujuTestSuite) TestBootstrap(c *gc.C) {
	s.PatchValue(local.FinishBootstrap, func(mcfg *cloudinit.MachineConfig, cloudcfg *coreCloudinit.Config, ctx environs.BootstrapContext) error {
		c.Assert(cloudcfg.AptUpdate(), jc.IsFalse)
		c.Assert(cloudcfg.AptUpgrade(), jc.IsFalse)
		c.Assert(cloudcfg.Packages(), gc.HasLen, 0)
		c.Assert(mcfg.AgentEnvironment, gc.Not(gc.IsNil))
		// local does not allow machine-0 to host units
		c.Assert(mcfg.Jobs, gc.DeepEquals, []params.MachineJob{params.JobManageEnviron})
		return nil
	})
	s.testBootstrap(c, minimalConfig(c))
}

func (s *localJujuTestSuite) TestDestroy(c *gc.C) {
	env := s.testBootstrap(c, minimalConfig(c))
	err := env.Destroy()
	// Succeeds because there's no "agents" directory,
	// so destroy will just return without attempting
	// sudo or anything.
	c.Assert(err, gc.IsNil)
	c.Assert(s.fakesudo+".args", jc.DoesNotExist)
}

func (s *localJujuTestSuite) makeAgentsDir(c *gc.C, env environs.Environ) {
	rootDir := env.Config().AllAttrs()["root-dir"].(string)
	agentsDir := filepath.Join(rootDir, "agents")
	err := os.Mkdir(agentsDir, 0755)
	c.Assert(err, gc.IsNil)
}

func (s *localJujuTestSuite) TestDestroyCallSudo(c *gc.C) {
	env := s.testBootstrap(c, minimalConfig(c))
	s.makeAgentsDir(c, env)
	err := env.Destroy()
	c.Assert(err, gc.IsNil)
	data, err := ioutil.ReadFile(s.fakesudo + ".args")
	c.Assert(err, gc.IsNil)
	expected := []string{
		s.fakesudo,
		"env",
		"JUJU_HOME=" + osenv.JujuHome(),
		os.Args[0],
		"destroy-environment",
		"-y",
		"--force",
		env.Config().Name(),
	}
	c.Assert(string(data), gc.Equals, strings.Join(expected, " ")+"\n")
}

func (s *localJujuTestSuite) makeFakeUpstartScripts(c *gc.C, env environs.Environ,
) (mongoService *upstart.Service, machineAgent *upstart.Service) {
	upstartDir := c.MkDir()
	s.PatchValue(&upstart.InitDir, upstartDir)
	s.MakeTool(c, "start", `echo "some-service start/running, process 123"`)

	namespace := env.Config().AllAttrs()["namespace"].(string)
	mongoService = upstart.NewService(mongo.ServiceName(namespace))
	mongoConf := upstart.Conf{
		Service: *mongoService,
		Desc:    "fake mongo",
		Cmd:     "echo FAKE",
	}
	err := mongoConf.Install()
	c.Assert(err, gc.IsNil)
	c.Assert(mongoService.Installed(), jc.IsTrue)

	machineAgent = upstart.NewService(fmt.Sprintf("juju-agent-%s", namespace))
	agentConf := upstart.Conf{
		Service: *machineAgent,
		Desc:    "fake agent",
		Cmd:     "echo FAKE",
	}
	err = agentConf.Install()
	c.Assert(err, gc.IsNil)
	c.Assert(machineAgent.Installed(), jc.IsTrue)

	return mongoService, machineAgent
}

func (s *localJujuTestSuite) TestDestroyRemovesUpstartServices(c *gc.C) {
	env := s.testBootstrap(c, minimalConfig(c))
	s.makeAgentsDir(c, env)
	mongo, machineAgent := s.makeFakeUpstartScripts(c, env)
	s.PatchValue(local.CheckIfRoot, func() bool { return true })

	err := env.Destroy()
	c.Assert(err, gc.IsNil)

	c.Assert(mongo.Installed(), jc.IsFalse)
	c.Assert(machineAgent.Installed(), jc.IsFalse)
}

func (s *localJujuTestSuite) TestDestroyRemovesContainers(c *gc.C) {
	env := s.testBootstrap(c, minimalConfig(c))
	s.makeAgentsDir(c, env)
	s.PatchValue(local.CheckIfRoot, func() bool { return true })

	namespace := env.Config().AllAttrs()["namespace"].(string)
	manager, err := lxc.NewContainerManager(container.ManagerConfig{
		container.ConfigName:   namespace,
		container.ConfigLogDir: "logdir",
	})
	c.Assert(err, gc.IsNil)

	machine1 := containertesting.CreateContainer(c, manager, "1")

	err = env.Destroy()
	c.Assert(err, gc.IsNil)

	container := s.Factory.New(string(machine1.Id()))
	c.Assert(container.IsConstructed(), jc.IsFalse)
}

func (s *localJujuTestSuite) TestBootstrapRemoveLeftovers(c *gc.C) {
	cfg := minimalConfig(c)
	rootDir := cfg.AllAttrs()["root-dir"].(string)

	// Create a dir inside local/log that should be removed by Bootstrap.
	logThings := filepath.Join(rootDir, "log", "things")
	err := os.MkdirAll(logThings, 0755)
	c.Assert(err, gc.IsNil)

	// Create a cloud-init-output.log in root-dir that should be
	// removed/truncated by Bootstrap.
	cloudInitOutputLog := filepath.Join(rootDir, "cloud-init-output.log")
	err = ioutil.WriteFile(cloudInitOutputLog, []byte("ohai"), 0644)
	c.Assert(err, gc.IsNil)

	s.testBootstrap(c, cfg)
	c.Assert(logThings, jc.DoesNotExist)
	c.Assert(cloudInitOutputLog, jc.DoesNotExist)
	c.Assert(filepath.Join(rootDir, "log"), jc.IsSymlink)
}

func (s *localJujuTestSuite) TestConstraintsValidator(c *gc.C) {
	ctx := coretesting.Context(c)
	env, err := local.Provider.Prepare(ctx, minimalConfig(c))
	c.Assert(err, gc.IsNil)
	validator, err := env.ConstraintsValidator()
	c.Assert(err, gc.IsNil)
	hostArch := arch.HostArch()
	cons := constraints.MustParse(fmt.Sprintf("arch=%s instance-type=foo tags=bar cpu-power=10 cpu-cores=2", hostArch))
	unsupported, err := validator.Validate(cons)
	c.Assert(err, gc.IsNil)
	c.Assert(unsupported, gc.DeepEquals, []string{"cpu-cores", "cpu-power", "instance-type", "tags"})
}

func (s *localJujuTestSuite) TestConstraintsValidatorVocab(c *gc.C) {
	env := s.Prepare(c)
	validator, err := env.ConstraintsValidator()
	c.Assert(err, gc.IsNil)

	hostArch := arch.HostArch()
	var invalidArch string
	for _, a := range arch.AllSupportedArches {
		if a != hostArch {
			invalidArch = a
			break
		}
	}
	cons := constraints.MustParse(fmt.Sprintf("arch=%s", invalidArch))
	_, err = validator.Validate(cons)
	c.Assert(err, gc.ErrorMatches, "invalid constraint value: arch="+invalidArch+"\nvalid values are:.*")
}
<commit_msg>fix test to check new error message<commit_after>// Copyright 2013 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package local_test

import (
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"

	jc "github.com/juju/testing/checkers"
	gc "launchpad.net/gocheck"

	"launchpad.net/juju-core/agent/mongo"
	coreCloudinit "launchpad.net/juju-core/cloudinit"
	"launchpad.net/juju-core/constraints"
	"launchpad.net/juju-core/container"
	"launchpad.net/juju-core/container/lxc"
	containertesting "launchpad.net/juju-core/container/testing"
	"launchpad.net/juju-core/environs"
	"launchpad.net/juju-core/environs/cloudinit"
	"launchpad.net/juju-core/environs/config"
	"launchpad.net/juju-core/environs/jujutest"
	envtesting "launchpad.net/juju-core/environs/testing"
	"launchpad.net/juju-core/environs/tools"
	"launchpad.net/juju-core/juju/arch"
	"launchpad.net/juju-core/juju/osenv"
	"launchpad.net/juju-core/provider/local"
	"launchpad.net/juju-core/state/api/params"
	coretesting "launchpad.net/juju-core/testing"
	"launchpad.net/juju-core/upstart"
)

const echoCommandScript = "#!/bin/sh\necho $0 \"$@\" >> $0.args"

type environSuite struct {
	baseProviderSuite
	envtesting.ToolsFixture
}

var _ = gc.Suite(&environSuite{})

func (s *environSuite) SetUpTest(c *gc.C) {
	s.baseProviderSuite.SetUpTest(c)
	s.ToolsFixture.SetUpTest(c)
}

func (s *environSuite) TearDownTest(c *gc.C) {
	s.ToolsFixture.TearDownTest(c)
	s.baseProviderSuite.TearDownTest(c)
}

func (*environSuite) TestOpenFailsWithProtectedDirectories(c *gc.C) {
	testConfig := minimalConfig(c)
	testConfig, err := testConfig.Apply(map[string]interface{}{
		"root-dir": "/usr/lib/juju",
	})
	c.Assert(err, gc.IsNil)

	environ, err := local.Provider.Open(testConfig)
	c.Assert(err, gc.ErrorMatches, "failure setting config: mkdir .* permission denied")
	c.Assert(environ, gc.IsNil)
}

func (s *environSuite) TestNameAndStorage(c *gc.C) {
	testConfig := minimalConfig(c)
	environ, err := local.Provider.Open(testConfig)
	c.Assert(err, gc.IsNil)
	c.Assert(environ.Name(), gc.Equals, "test")
	c.Assert(environ.Storage(), gc.NotNil)
}

func (s *environSuite) TestGetToolsMetadataSources(c *gc.C) {
	testConfig := minimalConfig(c)
	environ, err := local.Provider.Open(testConfig)
	c.Assert(err, gc.IsNil)
	sources, err := tools.GetMetadataSources(environ)
	c.Assert(err, gc.IsNil)
	c.Assert(len(sources), gc.Equals, 1)
	url, err := sources[0].URL("")
	c.Assert(err, gc.IsNil)
	c.Assert(strings.Contains(url, "/tools"), jc.IsTrue)
}

func (*environSuite) TestSupportedArchitectures(c *gc.C) {
	testConfig := minimalConfig(c)
	environ, err := local.Provider.Open(testConfig)
	c.Assert(err, gc.IsNil)
	arches, err := environ.SupportedArchitectures()
	c.Assert(err, gc.IsNil)
	for _, a := range arches {
		c.Assert(arch.IsSupportedArch(a), jc.IsTrue)
	}
}

func (*environSuite) TestSupportNetworks(c *gc.C) {
	testConfig := minimalConfig(c)
	environ, err := local.Provider.Open(testConfig)
	c.Assert(err, gc.IsNil)
	c.Assert(environ.SupportNetworks(), jc.IsFalse)
}

type localJujuTestSuite struct {
	baseProviderSuite
	jujutest.Tests
	oldUpstartLocation string
	testPath           string
	fakesudo           string
}

func (s *localJujuTestSuite) SetUpTest(c *gc.C) {
	s.baseProviderSuite.SetUpTest(c)
	// Construct the directories first.
	err := local.CreateDirs(c, minimalConfig(c))
	c.Assert(err, gc.IsNil)
	s.testPath = c.MkDir()
	s.fakesudo = filepath.Join(s.testPath, "sudo")
	s.PatchEnvPathPrepend(s.testPath)

	// Write a fake "sudo" which records its args to sudo.args.
	err = ioutil.WriteFile(s.fakesudo, []byte(echoCommandScript), 0755)
	c.Assert(err, gc.IsNil)

	// Add in an admin secret
	s.Tests.TestConfig["admin-secret"] = "sekrit"
	s.PatchValue(local.CheckIfRoot, func() bool { return false })
	s.Tests.SetUpTest(c)

	s.PatchValue(local.FinishBootstrap, func(mcfg *cloudinit.MachineConfig, cloudcfg *coreCloudinit.Config, ctx environs.BootstrapContext) error {
		return nil
	})
}

func (s *localJujuTestSuite) TearDownTest(c *gc.C) {
	s.Tests.TearDownTest(c)
	s.baseProviderSuite.TearDownTest(c)
}

func (s *localJujuTestSuite) MakeTool(c *gc.C, name, script string) {
	path := filepath.Join(s.testPath, name)
	script = "#!/bin/bash\n" + script
	err := ioutil.WriteFile(path, []byte(script), 0755)
	c.Assert(err, gc.IsNil)
}

func (s *localJujuTestSuite) StoppedStatus(c *gc.C) {
	s.MakeTool(c, "status", `echo "some-service stop/waiting"`)
}

func (s *localJujuTestSuite) RunningStatus(c *gc.C) {
	s.MakeTool(c, "status", `echo "some-service start/running, process 123"`)
}

var _ = gc.Suite(&localJujuTestSuite{
	Tests: jujutest.Tests{
		TestConfig: minimalConfigValues(),
	},
})

func (s *localJujuTestSuite) TestStartStop(c *gc.C) {
	c.Skip("StartInstance not implemented yet.")
}

func (s *localJujuTestSuite) testBootstrap(c *gc.C, cfg *config.Config) (env environs.Environ) {
	ctx := coretesting.Context(c)
	environ, err := local.Provider.Prepare(ctx, cfg)
	c.Assert(err, gc.IsNil)
	envtesting.UploadFakeTools(c, environ.Storage())
	defer environ.Storage().RemoveAll()
	err = environ.Bootstrap(ctx, environs.BootstrapParams{})
	c.Assert(err, gc.IsNil)
	return environ
}

func (s *localJujuTestSuite) TestBootstrap(c *gc.C) {
	s.PatchValue(local.FinishBootstrap, func(mcfg *cloudinit.MachineConfig, cloudcfg *coreCloudinit.Config, ctx environs.BootstrapContext) error {
		c.Assert(cloudcfg.AptUpdate(), jc.IsFalse)
		c.Assert(cloudcfg.AptUpgrade(), jc.IsFalse)
		c.Assert(cloudcfg.Packages(), gc.HasLen, 0)
		c.Assert(mcfg.AgentEnvironment, gc.Not(gc.IsNil))
		// local does not allow machine-0 to host units
		c.Assert(mcfg.Jobs, gc.DeepEquals, []params.MachineJob{params.JobManageEnviron})
		return nil
	})
	s.testBootstrap(c, minimalConfig(c))
}

func (s *localJujuTestSuite) TestDestroy(c *gc.C) {
	env := s.testBootstrap(c, minimalConfig(c))
	err := env.Destroy()
	// Succeeds because there's no "agents" directory,
	// so destroy will just return without attempting
	// sudo or anything.
	c.Assert(err, gc.IsNil)
	c.Assert(s.fakesudo+".args", jc.DoesNotExist)
}

func (s *localJujuTestSuite) makeAgentsDir(c *gc.C, env environs.Environ) {
	rootDir := env.Config().AllAttrs()["root-dir"].(string)
	agentsDir := filepath.Join(rootDir, "agents")
	err := os.Mkdir(agentsDir, 0755)
	c.Assert(err, gc.IsNil)
}

func (s *localJujuTestSuite) TestDestroyCallSudo(c *gc.C) {
	env := s.testBootstrap(c, minimalConfig(c))
	s.makeAgentsDir(c, env)
	err := env.Destroy()
	c.Assert(err, gc.IsNil)
	data, err := ioutil.ReadFile(s.fakesudo + ".args")
	c.Assert(err, gc.IsNil)
	expected := []string{
		s.fakesudo,
		"env",
		"JUJU_HOME=" + osenv.JujuHome(),
		os.Args[0],
		"destroy-environment",
		"-y",
		"--force",
		env.Config().Name(),
	}
	c.Assert(string(data), gc.Equals, strings.Join(expected, " ")+"\n")
}

func (s *localJujuTestSuite) makeFakeUpstartScripts(c *gc.C, env environs.Environ,
) (mongoService *upstart.Service, machineAgent *upstart.Service) {
	upstartDir := c.MkDir()
	s.PatchValue(&upstart.InitDir, upstartDir)
	s.MakeTool(c, "start", `echo "some-service start/running, process 123"`)

	namespace := env.Config().AllAttrs()["namespace"].(string)
	mongoService = upstart.NewService(mongo.ServiceName(namespace))
	mongoConf := upstart.Conf{
		Service: *mongoService,
		Desc:    "fake mongo",
		Cmd:     "echo FAKE",
	}
	err := mongoConf.Install()
	c.Assert(err, gc.IsNil)
	c.Assert(mongoService.Installed(), jc.IsTrue)

	machineAgent = upstart.NewService(fmt.Sprintf("juju-agent-%s", namespace))
	agentConf := upstart.Conf{
		Service: *machineAgent,
		Desc:    "fake agent",
		Cmd:     "echo FAKE",
	}
	err = agentConf.Install()
	c.Assert(err, gc.IsNil)
	c.Assert(machineAgent.Installed(), jc.IsTrue)

	return mongoService, machineAgent
}

func (s *localJujuTestSuite) TestDestroyRemovesUpstartServices(c *gc.C) {
	env := s.testBootstrap(c, minimalConfig(c))
	s.makeAgentsDir(c, env)
	mongo, machineAgent := s.makeFakeUpstartScripts(c, env)
	s.PatchValue(local.CheckIfRoot, func() bool { return true })

	err := env.Destroy()
	c.Assert(err, gc.IsNil)

	c.Assert(mongo.Installed(), jc.IsFalse)
	c.Assert(machineAgent.Installed(), jc.IsFalse)
}

func (s *localJujuTestSuite) TestDestroyRemovesContainers(c *gc.C) {
	env := s.testBootstrap(c, minimalConfig(c))
	s.makeAgentsDir(c, env)
	s.PatchValue(local.CheckIfRoot, func() bool { return true })

	namespace := env.Config().AllAttrs()["namespace"].(string)
	manager, err := lxc.NewContainerManager(container.ManagerConfig{
		container.ConfigName:   namespace,
		container.ConfigLogDir: "logdir",
	})
	c.Assert(err, gc.IsNil)

	machine1 := containertesting.CreateContainer(c, manager, "1")

	err = env.Destroy()
	c.Assert(err, gc.IsNil)

	container := s.Factory.New(string(machine1.Id()))
	c.Assert(container.IsConstructed(), jc.IsFalse)
}

func (s *localJujuTestSuite) TestBootstrapRemoveLeftovers(c *gc.C) {
	cfg := minimalConfig(c)
	rootDir := cfg.AllAttrs()["root-dir"].(string)

	// Create a dir inside local/log that should be removed by Bootstrap.
	logThings := filepath.Join(rootDir, "log", "things")
	err := os.MkdirAll(logThings, 0755)
	c.Assert(err, gc.IsNil)

	// Create a cloud-init-output.log in root-dir that should be
	// removed/truncated by Bootstrap.
	cloudInitOutputLog := filepath.Join(rootDir, "cloud-init-output.log")
	err = ioutil.WriteFile(cloudInitOutputLog, []byte("ohai"), 0644)
	c.Assert(err, gc.IsNil)

	s.testBootstrap(c, cfg)
	c.Assert(logThings, jc.DoesNotExist)
	c.Assert(cloudInitOutputLog, jc.DoesNotExist)
	c.Assert(filepath.Join(rootDir, "log"), jc.IsSymlink)
}

func (s *localJujuTestSuite) TestConstraintsValidator(c *gc.C) {
	ctx := coretesting.Context(c)
	env, err := local.Provider.Prepare(ctx, minimalConfig(c))
	c.Assert(err, gc.IsNil)
	validator, err := env.ConstraintsValidator()
	c.Assert(err, gc.IsNil)
	hostArch := arch.HostArch()
	cons := constraints.MustParse(fmt.Sprintf("arch=%s instance-type=foo tags=bar cpu-power=10 cpu-cores=2", hostArch))
	unsupported, err := validator.Validate(cons)
	c.Assert(err, gc.IsNil)
	c.Assert(unsupported, gc.DeepEquals, []string{"cpu-cores", "cpu-power", "instance-type", "tags"})
}

func (s *localJujuTestSuite) TestConstraintsValidatorVocab(c *gc.C) {
	env := s.Prepare(c)
	validator, err := env.ConstraintsValidator()
	c.Assert(err, gc.IsNil)

	hostArch := arch.HostArch()
	var invalidArch string
	for _, a := range arch.AllSupportedArches {
		if a != hostArch {
			invalidArch = a
			break
		}
	}
	cons := constraints.MustParse(fmt.Sprintf("arch=%s", invalidArch))
	_, err = validator.Validate(cons)
	c.Assert(err, gc.ErrorMatches, "invalid constraint value: arch="+invalidArch+"\nvalid values are:.*")
}
<|endoftext|>


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

package datadog

import (
	"context"
	"fmt"

	datadogV1 "github.com/DataDog/datadog-api-client-go/api/v1/datadog"

	"github.com/GoogleCloudPlatform/terraformer/terraformutils"
)

var (
	// DashboardAllowEmptyValues ...
	DashboardAllowEmptyValues = []string{"tags."}
)

// DashboardGenerator ...
type DashboardGenerator struct {
	DatadogService
}

func (g *DashboardGenerator) createResources(dashboards []datadogV1.DashboardSummaryDashboards) []terraformutils.Resource {
	resources := []terraformutils.Resource{}
	for _, dashboard := range dashboards {
		resourceName := dashboard.GetId()
		resources = append(resources, g.createResource(resourceName))
	}

	return resources
}

func (g *DashboardGenerator) createResource(dashboardID string) terraformutils.Resource {
	return terraformutils.NewSimpleResource(
		dashboardID,
		fmt.Sprintf("dashboard_%s", dashboardID),
		"datadog_dashboard",
		"datadog",
		DashboardAllowEmptyValues,
	)
}

// InitResources Generate TerraformResources from Datadog API,
// from each dashboard create 1 TerraformResource.
// Need Dashboard ID as ID for terraform resource
func (g *DashboardGenerator) InitResources() error {
	authV1 := context.WithValue(
		context.Background(),
		datadogV1.ContextAPIKeys,
		map[string]datadogV1.APIKey{
			"apiKeyAuth": {
				Key: g.Args["api-key"].(string),
			},
			"appKeyAuth": {
				Key: g.Args["app-key"].(string),
			},
		},
	)
	config := datadogV1.NewConfiguration()
	client := datadogV1.NewAPIClient(config)

	resources := []terraformutils.Resource{}
	for _, filter := range g.Filter {
		if filter.FieldPath == "id" && filter.IsApplicable("dashboard") {
			for _, value := range filter.AcceptableValues {
				dashboard, _, err := client.DashboardsApi.GetDashboard(authV1, value).Execute()
				if err != nil {
					return err
				}

				resources = append(resources, g.createResource(dashboard.GetId()))
			}
		}
	}

	if len(resources) > 0 {
		g.Resources = resources
		return nil
	}

	summary, _, err := client.DashboardsApi.ListDashboards(authV1).Execute()
	if err != nil {
		return err
	}
	g.Resources = g.createResources(summary.GetDashboards())
	return nil
}
<commit_msg>remove duplicated code in dashboard resource<commit_after>// Copyright 2018 The Terraformer Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package datadog

import (
	"context"
	"fmt"

	datadogV1 "github.com/DataDog/datadog-api-client-go/api/v1/datadog"

	"github.com/GoogleCloudPlatform/terraformer/terraformutils"
)

var (
	// DashboardAllowEmptyValues ...
	DashboardAllowEmptyValues = []string{"tags."}
)

// DashboardGenerator ...
type DashboardGenerator struct {
	DatadogService
}

func (g *DashboardGenerator) createResources(dashboards []datadogV1.DashboardSummaryDashboards) []terraformutils.Resource {
	resources := []terraformutils.Resource{}
	for _, dashboard := range dashboards {
		resourceName := dashboard.GetId()
		resources = append(resources, g.createResource(resourceName))
	}

	return resources
}

func (g *DashboardGenerator) createResource(dashboardID string) terraformutils.Resource {
	return terraformutils.NewSimpleResource(
		dashboardID,
		fmt.Sprintf("dashboard_%s", dashboardID),
		"datadog_dashboard",
		"datadog",
		DashboardAllowEmptyValues,
	)
}

// InitResources Generate TerraformResources from Datadog API,
// from each dashboard create 1 TerraformResource.
// Need Dashboard ID as ID for terraform resource
func (g *DashboardGenerator) InitResources() error {
	datadogClientV1 := g.Args["datadogClientV1"].(*datadogV1.APIClient)
	authV1 := g.Args["authV1"].(context.Context)

	resources := []terraformutils.Resource{}
	for _, filter := range g.Filter {
		if filter.FieldPath == "id" && filter.IsApplicable("dashboard") {
			for _, value := range filter.AcceptableValues {
				dashboard, _, err := datadogClientV1.DashboardsApi.GetDashboard(authV1, value).Execute()
				if err != nil {
					return err
				}

				resources = append(resources, g.createResource(dashboard.GetId()))
			}
		}
	}

	if len(resources) > 0 {
		g.Resources = resources
		return nil
	}

	summary, _, err := datadogClientV1.DashboardsApi.ListDashboards(authV1).Execute()
	if err != nil {
		return err
	}
	g.Resources = g.createResources(summary.GetDashboards())
	return nil
}
<|endoftext|>