<commit_before>package csrf

import (
	"crypto/rand"
	"crypto/subtle"
	"encoding/hex"
	"html/template"
	"io"
	"math"
	"net/url"

	"github.com/revel/revel"
)

// allowMethods are HTTP methods that do NOT require a token
var allowedMethods = map[string]bool{
	"GET":     true,
	"HEAD":    true,
	"OPTIONS": true,
	"TRACE":   true,
}

func RandomString(length int) (string, error) {
	buffer := make([]byte, int(math.Ceil(float64(length)/2)))
	if _, err := io.ReadFull(rand.Reader, buffer); err != nil {
		return "", nil
	}
	str := hex.EncodeToString(buffer)
	return str[:length], nil
}

func RefreshToken(c *revel.Controller) {
	token, err := RandomString(64)
	if err != nil {
		panic(err)
	}
	c.Session["csrf_token"] = token
}

func CsrfFilter(c *revel.Controller, fc []revel.Filter) {
	token, foundToken := c.Session["csrf_token"]

	if !foundToken {
		RefreshToken(c)
	}

	referer, refErr := url.Parse(c.Request.Header.Get("Referer"))
	isSameOrigin := sameOrigin(c.Request.URL, referer)

	// If the Request method isn't in the white listed methods
	if !allowedMethods[c.Request.Method] && !IsExempt(c) {
		// Token wasn't present at all
		if !foundToken {
			c.Result = c.Forbidden("REVEL CSRF: Session token missing.")
			return
		}

		// Referer header is invalid
		if refErr != nil {
			c.Result = c.Forbidden("REVEL CSRF: HTTP Referer malformed.")
			return
		}

		// Same origin
		if !isSameOrigin {
			c.Result = c.Forbidden("REVEL CSRF: Same origin mismatch.")
			return
		}

		var requestToken string
		// First check for token in post data
		if c.Request.Method == "POST" {
			requestToken = c.Request.FormValue("csrftoken")
		}

		// Then check for token in custom headers, as with AJAX
		if requestToken == "" {
			requestToken = c.Request.Header.Get("X-CSRFToken")
		}

		if requestToken == "" || !compareToken(requestToken, token) {
			c.Result = c.Forbidden("REVEL CSRF: Invalid token.")
			return
		}
	}

	fc[0](c, fc[1:])

	// Only add token to RenderArgs if the request is: not AJAX, not missing referer header, and is same origin.
	if c.Request.Header.Get("X-CSRFToken") == "" && isSameOrigin {
		c.RenderArgs["_csrftoken"] = token
	}
}

func compareToken(requestToken, token string) bool {
	// ConstantTimeCompare will panic if the []byte aren't the same length
	if len(requestToken) != len(token) {
		return false
	}
	return subtle.ConstantTimeCompare([]byte(requestToken), []byte(token)) == 1
}

// Validates same origin policy
func sameOrigin(u1, u2 *url.URL) bool {
	return u1.Scheme == u2.Scheme && u1.Host == u2.Host
}

func init() {
	revel.TemplateFuncs["csrftoken"] = func(renderArgs map[string]interface{}) template.HTML {
		if tokenFunc, ok := renderArgs["_csrftoken"]; !ok {
			panic("REVEL CSRF: _csrftoken missing from RenderArgs.")
		} else {
			return template.HTML(tokenFunc.(func() string)())
		}
	}
}
<commit_msg>Adds godoc block for CSRF module fitler<commit_after>package csrf

import (
	"crypto/rand"
	"crypto/subtle"
	"encoding/hex"
	"html/template"
	"io"
	"math"
	"net/url"

	"github.com/revel/revel"
)

// allowMethods are HTTP methods that do NOT require a token
var allowedMethods = map[string]bool{
	"GET":     true,
	"HEAD":    true,
	"OPTIONS": true,
	"TRACE":   true,
}

func RandomString(length int) (string, error) {
	buffer := make([]byte, int(math.Ceil(float64(length)/2)))
	if _, err := io.ReadFull(rand.Reader, buffer); err != nil {
		return "", nil
	}
	str := hex.EncodeToString(buffer)
	return str[:length], nil
}

func RefreshToken(c *revel.Controller) {
	token, err := RandomString(64)
	if err != nil {
		panic(err)
	}
	c.Session["csrf_token"] = token
}

// CsrfFilter enables CSRF request token creation and verification.
//
// Usage:
//  1) Add `csrf.CsrfFilter` to the app's filters (it must come after the revel.SessionFilter).
//  2) Add CSRF fields to a form with the template tag `{{ csrftoken . }}`. The filter adds a function closure to the `RenderArgs` that can pull out the secret and make the token as-needed, caching the value in the request. Ajax support provided through the `X-CSRFToken` header.
func CsrfFilter(c *revel.Controller, fc []revel.Filter) {
	token, foundToken := c.Session["csrf_token"]

	if !foundToken {
		RefreshToken(c)
	}

	referer, refErr := url.Parse(c.Request.Header.Get("Referer"))
	isSameOrigin := sameOrigin(c.Request.URL, referer)

	// If the Request method isn't in the white listed methods
	if !allowedMethods[c.Request.Method] && !IsExempt(c) {
		// Token wasn't present at all
		if !foundToken {
			c.Result = c.Forbidden("REVEL CSRF: Session token missing.")
			return
		}

		// Referer header is invalid
		if refErr != nil {
			c.Result = c.Forbidden("REVEL CSRF: HTTP Referer malformed.")
			return
		}

		// Same origin
		if !isSameOrigin {
			c.Result = c.Forbidden("REVEL CSRF: Same origin mismatch.")
			return
		}

		var requestToken string
		// First check for token in post data
		if c.Request.Method == "POST" {
			requestToken = c.Request.FormValue("csrftoken")
		}

		// Then check for token in custom headers, as with AJAX
		if requestToken == "" {
			requestToken = c.Request.Header.Get("X-CSRFToken")
		}

		if requestToken == "" || !compareToken(requestToken, token) {
			c.Result = c.Forbidden("REVEL CSRF: Invalid token.")
			return
		}
	}

	fc[0](c, fc[1:])

	// Only add token to RenderArgs if the request is: not AJAX, not missing referer header, and is same origin.
	if c.Request.Header.Get("X-CSRFToken") == "" && isSameOrigin {
		c.RenderArgs["_csrftoken"] = token
	}
}

func compareToken(requestToken, token string) bool {
	// ConstantTimeCompare will panic if the []byte aren't the same length
	if len(requestToken) != len(token) {
		return false
	}
	return subtle.ConstantTimeCompare([]byte(requestToken), []byte(token)) == 1
}

// Validates same origin policy
func sameOrigin(u1, u2 *url.URL) bool {
	return u1.Scheme == u2.Scheme && u1.Host == u2.Host
}

func init() {
	revel.TemplateFuncs["csrftoken"] = func(renderArgs map[string]interface{}) template.HTML {
		if tokenFunc, ok := renderArgs["_csrftoken"]; !ok {
			panic("REVEL CSRF: _csrftoken missing from RenderArgs.")
		} else {
			return template.HTML(tokenFunc.(func() string)())
		}
	}
}
<|endoftext|>


<commit_before>// Copyright e-Xpert Solutions SA. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package ltm

import "github.com/e-XpertSolutions/f5-rest-client/f5"

/*
   Gets only the stats for the specified pool itself, not include members of the pool.
   Data source URL example: https://url-to-bigip/mgmt/tm/ltm/pool/stats
*/

type PoolStatsList struct {
	Entries  map[string]PoolStatsEntries `json:"entries,omitempty"`
	Kind     string                      `json:"kind,omitempty" pretty:",expanded"`
	SelfLink string                      `json:"selflink,omitempty" pretty:",expanded"`
}

type PoolStatsEntries struct {
	NestedPoolStats PoolStats `json:"nestedStats,omitempty"`
}

type PoolStats struct {
	Entries struct {
		ActiveMemberCnt struct {
			Value int `json:"value"`
		} `json:"activeMemberCnt,omitempty"`
		AvailableMemberCnt struct {
			Value int `json:"value"`
		} `json:"availableMemberCnt,omitempty"`
		ConnqAgeEdm struct {
			Value int `json:"value"`
		} `json:"connq.ageEdm,omitempty"`
		ConnqAgeEma struct {
			Value int `json:"value"`
		} `json:"connq.ageEma,omitempty"`
		ConnqAgeHead struct {
			Value int `json:"value"`
		} `json:"connq.ageHead,omitempty"`
		ConnqAgeMax struct {
			Value int `json:"value"`
		} `json:"connq.ageMax,omitempty"`
		ConnqDepth struct {
			Value int `json:"value"`
		} `json:"connq.depth,omitempty"`
		ConnqServiced struct {
			Value int `json:"value"`
		} `json:"connq.serviced,omitempty"`
		ConnqAllAgeEdm struct {
			Value int `json:"value"`
		} `json:"connqAll.ageEdm,omitempty"`
		ConnqAllAgeEma struct {
			Value int `json:"value"`
		} `json:"connqAll.ageEma,omitempty"`
		ConnqAllAgeHead struct {
			Value int `json:"value"`
		} `json:"connqAll.ageHead,omitempty"`
		ConnqAllAgeMax struct {
			Value int `json:"value"`
		} `json:"connqAll.ageMax,omitempty"`
		ConnqAllDepth struct {
			Value int `json:"value"`
		} `json:"connqAll.depth,omitempty"`
		ConnqAllServiced struct {
			Value int `json:"value"`
		} `json:"connqAll.serviced,omitempty"`
		CurSessions struct {
			Value int `json:"value"`
		} `json:"curSessions,omitempty"`
		MemberCnt struct {
			Value int `json:"value"`
		} `json:"memberCnt,omitempty"`
		MinActiveMembers struct {
			Value int `json:"value"`
		} `json:"minActiveMembers,omitempty"`
		MonitorRule struct {
			Description string `json:"description,omitempty"`
		} `json:"monitorRule,omitempty"`
		ServersideBitsIn struct {
			Value int `json:"value"`
		} `json:"serverside.bitsIn,omitempty"`
		ServersideBitsOut struct {
			Value int `json:"value"`
		} `json:"serverside.bitsOut,omitempty"`
		ServersideCurConns struct {
			Value int `json:"value"`
		} `json:"serverside.curConns,omitempty"`
		ServersideMaxConns struct {
			Value int `json:"value"`
		} `json:"serverside.maxConns,omitempty"`
		ServersidePktsIn struct {
			Value int `json:"value"`
		} `json:"serverside.pktsIn,omitempty"`
		ServersidePktsOut struct {
			Value int `json:"value"`
		} `json:"serverside.pktsOut,omitempty"`
		ServersideTotConns struct {
			Value int `json:"value"`
		} `json:"serverside.totConns,omitempty"`
		StatusAvailabilityState struct {
			Description string `json:"description,omitempty"`
		} `json:"status.availabilityState,omitempty"`
		StatusEnabledState struct {
			Description string `json:"description,omitempty"`
		} `json:"status.enabledState,omitempty"`
		StatusStatusReason struct {
			Description string `json:"description,omitempty"`
		} `json:"status.statusReason,omitempty"`
		TmName struct {
			Description string `json:"description,omitempty"`
		} `json:"tmName,omitempty"`
		TotRequests struct {
			Value int `json:"value"`
		} `json:"totRequests,omitempty"`
	} `json:"entries,omitempty"`
}

// PoolStatsEndpoint represents the REST resource for managing PoolStats.
const PoolStatsEndpoint = "/pool/stats"

// PoolStatsResource provides an API to manage PoolStats configurations.
type PoolStatsResource struct {
	c *f5.Client
}

func (r *PoolStatsResource) All() (*PoolStatsList, error) {
	var list PoolStatsList
	if err := r.c.ReadQuery(BasePath+PoolStatsEndpoint, &list); err != nil {
		return nil, err
	}
	return &list, nil
}

/*
   Gets only the stats for the specified pool itself, not include members of the pool.
   Data source URL example: https://url-to-bigip/mgmt/tm/ltm/pool/~Common~test_pool/stats
   @Author zhangfeng
   @Email  980252055@qq.com
*/

// Gets only the stats for the specified pool itself, not include members of the pool.
func (r *PoolStatsResource) GetPoolStats(pool string) (*PoolStatsList, error) {
	var list PoolStatsList
	if err := r.c.ReadQuery(BasePath+PoolEndpoint+"/~Common~"+pool+"/stats", &list); err != nil {
		return nil, err
	}
	return &list, nil
}

/*
   Get the stats of member specified under the specified pool.
   Data source URL example: https://url-to-bigip/mgmt/tm/ltm/pool/~Common~test_pool/members/~Common~192.168.0.30:8125/stats
   @Author zhangfeng
   @Email  980252055@qq.com
*/
type MemberStatsList struct {
	Entries  map[string]MemberStatsEntries `json:"entries,omitempty"`
	Kind     string                        `json:"kind,omitempty" pretty:",expanded"`
	SelfLink string                        `json:"selflink,omitempty" pretty:",expanded"`
}

type MemberStatsEntries struct {
	MemberNestedStats MemberStats `json:"nestedStats,omitempty"`
}

type MemberStats struct {
	Kind     string `json:"kind,omitempty" pretty:",expanded"`
	SelfLink string `json:"selflink,omitempty" pretty:",expanded"`
	Entries  struct {
		Addr struct {
			Description string `json:"description"`
		} `json:"addr,omitempty"`
		ConnqAgeEdm struct {
			Value int `json:"value"`
		} `json:"connq.ageEdm,omitempty"`
		ConnqAgeEma struct {
			Value int `json:"value"`
		} `json:"connq.ageEma,omitempty"`
		ConnqAgeHead struct {
			Value int `json:"value"`
		} `json:"connq.ageHead,omitempty"`
		ConnqAgeMax struct {
			Value int `json:"value"`
		} `json:"connq.ageMax,omitempty"`
		ConnqDepth struct {
			Value int `json:"value"`
		} `json:"connq.depth,omitempty"`
		ConnqServiced struct {
			Value int `json:"value"`
		} `json:"connq.serviced,omitempty"`
		CurSessions struct {
			Value int `json:"value"`
		} `json:"curSessions,omitempty"`
		MonitorRule struct {
			Description string `json:"description,omitempty"`
		} `json:"monitorRule,omitempty"`
		MonitorStatus struct {
			Description string `json:"description,omitempty"`
		} `json:"monitorStatus,omitempty"`
		NodeName struct {
			Description string `json:"description,omitempty"`
		} `json:"nodeName,omitempty"`
		PoolName struct {
			Description string `json:"description,omitempty"`
		} `json:"poolName,omitempty"`
		Port struct {
			Value int `json:"value"`
		} `json:"port,omitempty"`
		ServersideBitsIn struct {
			Value int `json:"value"`
		} `json:"serverside.bitsIn,omitempty"`
		ServersideBitsOut struct {
			Value int `json:"value"`
		} `json:"serverside.bitsOut,omitempty"`
		ServersideCurConns struct {
			Value int `json:"value"`
		} `json:"serverside.curConns,omitempty"`
		ServersideMaxConns struct {
			Value int `json:"value"`
		} `json:"serverside.maxConns,omitempty"`
		ServersidePktsIn struct {
			Value int `json:"value"`
		} `json:"serverside.pktsIn,omitempty"`
		ServersidePktsOut struct {
			Value int `json:"value"`
		} `json:"serverside.pktsOut,omitempty"`
		ServersideTotConns struct {
			Value int `json:"value"`
		} `json:"serverside.totConns,omitempty"`
		StatusAvailabilityState struct {
			Description string `json:"description,omitempty"`
		} `json:"status.availabilityState,omitempty"`
		StatusEnabledState struct {
			Description string `json:"description,omitempty"`
		} `json:"status.enabledState,omitempty"`
		StatusStatusReason struct {
			Description string `json:"description,omitempty"`
		} `json:"status.statusReason,omitempty"`
		TotRequests struct {
			Value int `json:"value"`
		} `json:"totRequests,omitempty"`
	} `json:"entries,omitempty"`
}

// Specify pool and specify member, get the specified member stats.
func (r *PoolStatsResource) GetMemberStats(pool, id string) (*MemberStatsList, error) {
	var list MemberStatsList
	if err := r.c.ReadQuery(BasePath+PoolEndpoint+"/~Common~"+pool+"/members/~Common~"+id+"/stats", &list); err != nil {
		return nil, err
	}
	return &list, nil
}

/*
   Get stats on all members under pool, not include pool itself.
   Data source URL example: https://url-to-bigip/mgmt/tm/ltm/pool/~Common~test_pool/members/stats
   @Author zhangfeng
   @Email  980252055@qq.com
*/
type PoolAllMemberStatsList struct {
	Entries  map[string]PoolAllMemberStatsEntries `json:"entries,omitempty"`
	Kind     string                               `json:"kind,omitempty" pretty:",expanded"`
	SelfLink string                               `json:"selflink,omitempty" pretty:",expanded"`
}

type PoolAllMemberStatsEntries struct {
	NestedPoolAllMemberStats MemberStats `json:"nestedStats,omitempty"`
}

// Get stats on all members under pool, not include pool itself.
func (r *PoolStatsResource) GetPoolAllMemberStats(pool string) (*PoolAllMemberStatsList, error) {
	var list PoolAllMemberStatsList
	if err := r.c.ReadQuery(BasePath+PoolEndpoint+"/~Common~"+pool+"/members/stats", &list); err != nil {
		return nil, err
	}
	return &list, nil
}
<commit_msg>Update pool_stats.go<commit_after>// Copyright e-Xpert Solutions SA. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package ltm

import "github.com/e-XpertSolutions/f5-rest-client/f5"

/*
   Gets only the stats for the specified pool itself, not include members of the pool.
   Data source URL example: https://url-to-bigip/mgmt/tm/ltm/pool/stats
*/

type PoolStatsList struct {
	Entries  map[string]PoolStatsEntries `json:"entries,omitempty"`
	Kind     string                      `json:"kind,omitempty" pretty:",expanded"`
	SelfLink string                      `json:"selflink,omitempty" pretty:",expanded"`
}

type PoolStatsEntries struct {
	NestedPoolStats PoolStats `json:"nestedStats,omitempty"`
}

type PoolStats struct {
	Entries struct {
		ActiveMemberCnt struct {
			Value int `json:"value"`
		} `json:"activeMemberCnt,omitempty"`
		AvailableMemberCnt struct {
			Value int `json:"value"`
		} `json:"availableMemberCnt,omitempty"`
		ConnqAgeEdm struct {
			Value int `json:"value"`
		} `json:"connq.ageEdm,omitempty"`
		ConnqAgeEma struct {
			Value int `json:"value"`
		} `json:"connq.ageEma,omitempty"`
		ConnqAgeHead struct {
			Value int `json:"value"`
		} `json:"connq.ageHead,omitempty"`
		ConnqAgeMax struct {
			Value int `json:"value"`
		} `json:"connq.ageMax,omitempty"`
		ConnqDepth struct {
			Value int `json:"value"`
		} `json:"connq.depth,omitempty"`
		ConnqServiced struct {
			Value int `json:"value"`
		} `json:"connq.serviced,omitempty"`
		ConnqAllAgeEdm struct {
			Value int `json:"value"`
		} `json:"connqAll.ageEdm,omitempty"`
		ConnqAllAgeEma struct {
			Value int `json:"value"`
		} `json:"connqAll.ageEma,omitempty"`
		ConnqAllAgeHead struct {
			Value int `json:"value"`
		} `json:"connqAll.ageHead,omitempty"`
		ConnqAllAgeMax struct {
			Value int `json:"value"`
		} `json:"connqAll.ageMax,omitempty"`
		ConnqAllDepth struct {
			Value int `json:"value"`
		} `json:"connqAll.depth,omitempty"`
		ConnqAllServiced struct {
			Value int `json:"value"`
		} `json:"connqAll.serviced,omitempty"`
		CurSessions struct {
			Value int `json:"value"`
		} `json:"curSessions,omitempty"`
		MemberCnt struct {
			Value int `json:"value"`
		} `json:"memberCnt,omitempty"`
		MinActiveMembers struct {
			Value int `json:"value"`
		} `json:"minActiveMembers,omitempty"`
		MonitorRule struct {
			Description string `json:"description,omitempty"`
		} `json:"monitorRule,omitempty"`
		ServersideBitsIn struct {
			Value int `json:"value"`
		} `json:"serverside.bitsIn,omitempty"`
		ServersideBitsOut struct {
			Value int `json:"value"`
		} `json:"serverside.bitsOut,omitempty"`
		ServersideCurConns struct {
			Value int `json:"value"`
		} `json:"serverside.curConns,omitempty"`
		ServersideMaxConns struct {
			Value int `json:"value"`
		} `json:"serverside.maxConns,omitempty"`
		ServersidePktsIn struct {
			Value int `json:"value"`
		} `json:"serverside.pktsIn,omitempty"`
		ServersidePktsOut struct {
			Value int `json:"value"`
		} `json:"serverside.pktsOut,omitempty"`
		ServersideTotConns struct {
			Value int `json:"value"`
		} `json:"serverside.totConns,omitempty"`
		StatusAvailabilityState struct {
			Description string `json:"description,omitempty"`
		} `json:"status.availabilityState,omitempty"`
		StatusEnabledState struct {
			Description string `json:"description,omitempty"`
		} `json:"status.enabledState,omitempty"`
		StatusStatusReason struct {
			Description string `json:"description,omitempty"`
		} `json:"status.statusReason,omitempty"`
		TmName struct {
			Description string `json:"description,omitempty"`
		} `json:"tmName,omitempty"`
		TotRequests struct {
			Value int `json:"value"`
		} `json:"totRequests,omitempty"`
	} `json:"entries,omitempty"`
}

// PoolStatsEndpoint represents the REST resource for managing PoolStats.
const PoolStatsEndpoint = "/pool/stats"

// PoolStatsResource provides an API to manage PoolStats configurations.
type PoolStatsResource struct {
	c *f5.Client
}

func (r *PoolStatsResource) All() (*PoolStatsList, error) {
	var list PoolStatsList
	if err := r.c.ReadQuery(BasePath+PoolStatsEndpoint, &list); err != nil {
		return nil, err
	}
	return &list, nil
}

/*
   Gets only the stats for the specified pool itself, not include members of the pool.
   Data source URL example: https://url-to-bigip/mgmt/tm/ltm/pool/test_pool/stats
   @Author zhangfeng
   @Email  980252055@qq.com
*/

// Gets only the stats for the specified pool itself, not include members of the pool.
func (r *PoolStatsResource) GetPoolStats(pool string) (*PoolStatsList, error) {
	var list PoolStatsList
	if err := r.c.ReadQuery(BasePath+PoolEndpoint+"/"+pool+"/stats", &list); err != nil {
		return nil, err
	}
	return &list, nil
}

/*
   Get the stats of member specified under the specified pool.
   Data source URL example: https://url-to-bigip/mgmt/tm/ltm/pool/~Common~test_pool/members/~Common~192.168.0.30:8125/stats
   @Author zhangfeng
   @Email  980252055@qq.com
*/
type MemberStatsList struct {
	Entries  map[string]MemberStatsEntries `json:"entries,omitempty"`
	Kind     string                        `json:"kind,omitempty" pretty:",expanded"`
	SelfLink string                        `json:"selflink,omitempty" pretty:",expanded"`
}

type MemberStatsEntries struct {
	MemberNestedStats MemberStats `json:"nestedStats,omitempty"`
}

type MemberStats struct {
	Kind     string `json:"kind,omitempty" pretty:",expanded"`
	SelfLink string `json:"selflink,omitempty" pretty:",expanded"`
	Entries  struct {
		Addr struct {
			Description string `json:"description"`
		} `json:"addr,omitempty"`
		ConnqAgeEdm struct {
			Value int `json:"value"`
		} `json:"connq.ageEdm,omitempty"`
		ConnqAgeEma struct {
			Value int `json:"value"`
		} `json:"connq.ageEma,omitempty"`
		ConnqAgeHead struct {
			Value int `json:"value"`
		} `json:"connq.ageHead,omitempty"`
		ConnqAgeMax struct {
			Value int `json:"value"`
		} `json:"connq.ageMax,omitempty"`
		ConnqDepth struct {
			Value int `json:"value"`
		} `json:"connq.depth,omitempty"`
		ConnqServiced struct {
			Value int `json:"value"`
		} `json:"connq.serviced,omitempty"`
		CurSessions struct {
			Value int `json:"value"`
		} `json:"curSessions,omitempty"`
		MonitorRule struct {
			Description string `json:"description,omitempty"`
		} `json:"monitorRule,omitempty"`
		MonitorStatus struct {
			Description string `json:"description,omitempty"`
		} `json:"monitorStatus,omitempty"`
		NodeName struct {
			Description string `json:"description,omitempty"`
		} `json:"nodeName,omitempty"`
		PoolName struct {
			Description string `json:"description,omitempty"`
		} `json:"poolName,omitempty"`
		Port struct {
			Value int `json:"value"`
		} `json:"port,omitempty"`
		ServersideBitsIn struct {
			Value int `json:"value"`
		} `json:"serverside.bitsIn,omitempty"`
		ServersideBitsOut struct {
			Value int `json:"value"`
		} `json:"serverside.bitsOut,omitempty"`
		ServersideCurConns struct {
			Value int `json:"value"`
		} `json:"serverside.curConns,omitempty"`
		ServersideMaxConns struct {
			Value int `json:"value"`
		} `json:"serverside.maxConns,omitempty"`
		ServersidePktsIn struct {
			Value int `json:"value"`
		} `json:"serverside.pktsIn,omitempty"`
		ServersidePktsOut struct {
			Value int `json:"value"`
		} `json:"serverside.pktsOut,omitempty"`
		ServersideTotConns struct {
			Value int `json:"value"`
		} `json:"serverside.totConns,omitempty"`
		StatusAvailabilityState struct {
			Description string `json:"description,omitempty"`
		} `json:"status.availabilityState,omitempty"`
		StatusEnabledState struct {
			Description string `json:"description,omitempty"`
		} `json:"status.enabledState,omitempty"`
		StatusStatusReason struct {
			Description string `json:"description,omitempty"`
		} `json:"status.statusReason,omitempty"`
		TotRequests struct {
			Value int `json:"value"`
		} `json:"totRequests,omitempty"`
	} `json:"entries,omitempty"`
}

// Specify pool and specify member, get the specified member stats.
func (r *PoolStatsResource) GetMemberStats(pool, id string) (*MemberStatsList, error) {
	var list MemberStatsList
	if err := r.c.ReadQuery(BasePath+PoolEndpoint+"/~Common~"+pool+"/members/~Common~"+id+"/stats", &list); err != nil {
		return nil, err
	}
	return &list, nil
}

/*
   Get stats on all members under pool, not include pool itself.
   Data source URL example: https://url-to-bigip/mgmt/tm/ltm/pool/test_pool/members/stats
   @Author zhangfeng
   @Email  980252055@qq.com
*/
type PoolAllMemberStatsList struct {
	Entries  map[string]PoolAllMemberStatsEntries `json:"entries,omitempty"`
	Kind     string                               `json:"kind,omitempty" pretty:",expanded"`
	SelfLink string                               `json:"selflink,omitempty" pretty:",expanded"`
}

type PoolAllMemberStatsEntries struct {
	NestedPoolAllMemberStats MemberStats `json:"nestedStats,omitempty"`
}

// Get stats on all members under pool, not include pool itself.
func (r *PoolStatsResource) GetPoolAllMemberStats(pool string) (*PoolAllMemberStatsList, error) {
	var list PoolAllMemberStatsList
	if err := r.c.ReadQuery(BasePath+PoolEndpoint+"/"+pool+"/members/stats", &list); err != nil {
		return nil, err
	}
	return &list, nil
}
<|endoftext|>


<commit_before>// Copyright (c) 2019 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

package zanzibar

import (
	"net/http"
	"strconv"
	"time"

	"github.com/uber-go/tally"
	"go.uber.org/zap"
)

// HTTPClient defines a http client.
type HTTPClient struct {
	Client         *http.Client
	BaseURL        string
	DefaultHeaders map[string]string
	loggers        map[string]*zap.Logger
	contextMetrics ContextMetrics
}

// UnexpectedHTTPError defines an error for HTTP
type UnexpectedHTTPError struct {
	StatusCode int
	RawBody    []byte
}

func (rawErr *UnexpectedHTTPError) Error() string {
	return "Unexpected http client response (" +
		strconv.Itoa(rawErr.StatusCode) + ")"
}

// NewHTTPClient is deprecated, use NewHTTPClientContext instead
func NewHTTPClient(
	logger *zap.Logger,
	scope tally.Scope,
	clientID string,
	methodToTargetEndpoint map[string]string,
	baseURL string,
	defaultHeaders map[string]string,
	timeout time.Duration,
) *HTTPClient {
	return NewHTTPClientContext(
		logger,
		NewContextMetrics(scope),
		clientID,
		methodToTargetEndpoint,
		baseURL,
		defaultHeaders,
		timeout,
	)
}

// NewHTTPClientContext will allocate a http client.
func NewHTTPClientContext(
	logger *zap.Logger,
	ContextMetrics ContextMetrics,
	clientID string,
	methodToTargetEndpoint map[string]string,
	baseURL string,
	defaultHeaders map[string]string,
	timeout time.Duration,
) *HTTPClient {
	loggers := make(map[string]*zap.Logger, len(methodToTargetEndpoint))

	for methodName, targetEndpointName := range methodToTargetEndpoint {
		loggers[methodName] = logger.With(
			zap.String("clientID", clientID),
			zap.String("clientMethod", methodName),
			zap.String("targetEndpoint", targetEndpointName),
		)
	}
	return &HTTPClient{
		Client: &http.Client{
			Transport: &http.Transport{
				DisableKeepAlives:   false,
				MaxIdleConns:        500,
				MaxIdleConnsPerHost: 500,
			},
			Timeout: timeout,
		},
		BaseURL:        baseURL,
		DefaultHeaders: defaultHeaders,
		loggers:        loggers,
		contextMetrics: ContextMetrics,
	}
}
<commit_msg>Update runtime/http_client.go<commit_after>// Copyright (c) 2019 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

package zanzibar

import (
	"net/http"
	"strconv"
	"time"

	"github.com/uber-go/tally"
	"go.uber.org/zap"
)

// HTTPClient defines a http client.
type HTTPClient struct {
	Client         *http.Client
	BaseURL        string
	DefaultHeaders map[string]string
	loggers        map[string]*zap.Logger
	contextMetrics ContextMetrics
}

// UnexpectedHTTPError defines an error for HTTP
type UnexpectedHTTPError struct {
	StatusCode int
	RawBody    []byte
}

func (rawErr *UnexpectedHTTPError) Error() string {
	return "Unexpected http client response (" +
		strconv.Itoa(rawErr.StatusCode) + ")"
}

// NewHTTPClient is deprecated, use NewHTTPClientContext instead
func NewHTTPClient(
	logger *zap.Logger,
	scope tally.Scope,
	clientID string,
	methodToTargetEndpoint map[string]string,
	baseURL string,
	defaultHeaders map[string]string,
	timeout time.Duration,
) *HTTPClient {
	return NewHTTPClientContext(
		logger,
		NewContextMetrics(scope),
		clientID,
		methodToTargetEndpoint,
		baseURL,
		defaultHeaders,
		timeout,
	)
}

// NewHTTPClientContext will allocate a http client.
func NewHTTPClientContext(
	logger *zap.Logger,
	ContextMetrics ContextMetrics,
	clientID string,
	methodToTargetEndpoint map[string]string,
	baseURL string,
	defaultHeaders map[string]string,
	timeout time.Duration,
) *HTTPClient {
	loggers := make(map[string]*zap.Logger, len(methodToTargetEndpoint))

	for methodName, targetEndpointName := range methodToTargetEndpoint {
		loggers[methodName] = logger.With(
			zap.String("clientID", clientID),
			zap.String(logFieldClientMethod, methodName),
			zap.String("targetEndpoint", targetEndpointName),
		)
	}
	return &HTTPClient{
		Client: &http.Client{
			Transport: &http.Transport{
				DisableKeepAlives:   false,
				MaxIdleConns:        500,
				MaxIdleConnsPerHost: 500,
			},
			Timeout: timeout,
		},
		BaseURL:        baseURL,
		DefaultHeaders: defaultHeaders,
		loggers:        loggers,
		contextMetrics: ContextMetrics,
	}
}
<|endoftext|>


<commit_before>package indicsoundex

import "testing"

func TestCalculate(t *testing.T) {
	inArray := []string{"vasudeva", "kamath", "ವಾಸುದೇವ", "वासुदॆव"}
	outArray := []string{"vA2C3D1A", "kA5A3000", "ವASCKDR0", "वASCKDR0"}

	for index, value := range inArray {
		if x, output := Calculate(value, 8), outArray[index]; x != output {
			t.Errorf("Calculate(%s) = %s was expecting %s", value, x, output)
		}
	}
}
<commit_msg>Fixed test cases<commit_after>package indicsoundex

import "testing"

func TestCalculate(t *testing.T) {
	// inArray := []string {"vasudeva", "kamath", "ವಾಸುದೇವ", "वासुदॆव"}
	inArray := []string{`ವಾಸುದೇವ`, `वासुदॆव`}
	// outArray := []string {"vA2C3D1A", "kA5A3000", "ವASCKDR0", "वASCKDR0" }
	outArray := []string{`ವASCKDR0`, `वASCKDR0`}

	for index, value := range inArray {
		if x, output := Calculate(value, 8), outArray[index]; x != output {
			t.Errorf("Calculate(%s) = %s was expecting %s", value, x, output)
		}
	}
}
<|endoftext|>


<commit_before>// Copyright 2019 Google LLC
//
// 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 main

import (
	"database/sql"
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"os"

	_ "github.com/go-sql-driver/mysql"
	"google.golang.org/appengine"
)

var (
	db          *sql.DB
	queryString = "SELECT probability_of_answer, probability_of_downvote, minutes FROM questions WHERE tag = ? AND first_word = ? AND ends_question = ? AND weekday_utc = ? AND account_creation_year = ? AND question_length = ? AND hour_utc = ?"
	port        = ""
)

func main() {
	var err error
	db, err = getDatabase()
	if err != nil {
		log.Printf("Failed to establish connection to Database. Server terminated")
		return
	}

	port = os.Getenv("PORT")
	if port == "" {
		port = "8080"
	}

	http.Handle("/", http.FileServer(http.Dir("./dist")))
	http.Handle("/experiment/bqml-stackoverflow/", http.FileServer(http.Dir("./dist")))

	http.HandleFunc("/experiment/bqml-stackoverflow/api/", func(w http.ResponseWriter, r *http.Request) {
		rows, err := attemptQuery(5, r)
		if err != nil {
			log.Printf("db.Query: %v", err)

			w.Write(getEmptyAnswer())
			return
		}
		defer rows.Close()

		if rows.Next() {
			var probabilityOfAnswer float32
			var probablityOfDownvote float32
			var minutes float32

			if err := rows.Scan(&probabilityOfAnswer, &probablityOfDownvote, &minutes); err != nil {
				log.Printf("rows.Scan: %v", err)
			}

			var answer = &Answer{
				Minutes:   minutes,
				PDownvote: probablityOfDownvote,
				PAnswer:   probabilityOfAnswer,
			}

			json, _ := json.Marshal(answer)

			w.Write(json)
		} else {
			w.Write(getEmptyAnswer())
		}

	})

	// fs := wrapHandler(http.FileServer(http.Dir("./dist")))
	// http.HandleFunc("/experiment/bqml-stackoverflow/", fs)
	// http.Handle("/", http.FileServer(http.Dir("./static")))

	fmt.Printf("Starting server on port %s\n", port)
	if err := http.ListenAndServe(":"+port, nil); err != nil {
		log.Fatal(err)
	}

}

func getEmptyAnswer() []byte {
	json, _ := json.Marshal(&Answer{
		Minutes:   0,
		PAnswer:   0,
		PDownvote: 0,
	})

	return json
}

func getDatabase() (*sql.DB, error) {
	user := os.Getenv("DB_USER")
	password := os.Getenv("DB_PASSWORD")
	database := os.Getenv("DB_DATABASE")
	instance := os.Getenv("DB_INSTANCE")

	if appengine.IsDevAppServer() {
		return sql.Open("mysql", fmt.Sprintf("%s:%s@tcp([localhost]:3306)/%s", user, password, database))
	}
	log.Printf("Database call to (%s)/%s", instance, database)
	return sql.Open("mysql", fmt.Sprintf("%s:%s@unix(/cloudsql/%s)/%s", user, password, instance, database))
}

func attemptQuery(attempts int, r *http.Request) (*sql.Rows, error) {
	var rows *sql.Rows
	var err error

	query := r.URL.Query()

	for i := 0; i < attempts; i++ {
		rows, err = db.Query(
			queryString,
			query.Get("tag"),
			query.Get("first_word"),
			query.Get("ends_question"),
			query.Get("weekday_utc"),
			query.Get("account_creation_year"),
			query.Get("question_length"),
			query.Get("hour_utc"),
		)

		if err == nil {
			break
		} else {
			fmt.Printf("error getting using the database %v\n", err)
			db, err = getDatabase()
			if err != nil {
				fmt.Printf("error getting database %v\n", err)
			}
		}
	}

	return rows, err
}

// Answer respresents the api query response json.
type Answer struct {
	Minutes   float32 `json:"minutes"`
	PAnswer   float32 `json:"probability_of_answer"`
	PDownvote float32 `json:"probabiliy_of_downvote"`
}

type NotFoundRedirectRespWr struct {
	http.ResponseWriter // We embed http.ResponseWriter
	status              int
}

func (w *NotFoundRedirectRespWr) WriteHeader(status int) {
	w.status = status // Store the status for our own use
	if status != http.StatusNotFound {
		w.ResponseWriter.WriteHeader(status)
	}
}

func (w *NotFoundRedirectRespWr) Write(p []byte) (int, error) {
	if w.status != http.StatusNotFound {
		return w.ResponseWriter.Write(p)
	}
	return len(p), nil // Lie that we successfully written it
}

func wrapHandler(h http.Handler) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		nfrw := &NotFoundRedirectRespWr{ResponseWriter: w}
		h.ServeHTTP(nfrw, r)
		if nfrw.status == 404 {
			http.Redirect(w, r, "/experiment/bqml-stackoverflow/index.html", http.StatusFound)
		}
	}
}
<commit_msg>Update main.go<commit_after>// Copyright 2019 Google LLC
//
// 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 main

import (
	"database/sql"
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"os"

	_ "github.com/go-sql-driver/mysql"
	"google.golang.org/appengine"
)

var (
	db          *sql.DB
	queryString = "SELECT probability_of_answer, probability_of_downvote, minutes FROM questions WHERE tag = ? AND first_word = ? AND ends_question = ? AND weekday_utc = ? AND account_creation_year = ? AND question_length = ? AND hour_utc = ?"
	port        = ""
)

func main() {
	var err error
	db, err = getDatabase()
	if err != nil {
		log.Printf("Failed to establish connection to Database. Server terminated")
		return
	}

	port = os.Getenv("PORT")
	if port == "" {
		port = "8080"
	}

	// http.Handle("/", http.FileServer(http.Dir("./dist")))
	http.Handle("/experiment/bqml-stackoverflow/", http.FileServer(http.Dir("./dist")))

	http.HandleFunc("/experiment/bqml-stackoverflow/api/", func(w http.ResponseWriter, r *http.Request) {
		rows, err := attemptQuery(5, r)
		if err != nil {
			log.Printf("db.Query: %v", err)

			w.Write(getEmptyAnswer())
			return
		}
		defer rows.Close()

		if rows.Next() {
			var probabilityOfAnswer float32
			var probablityOfDownvote float32
			var minutes float32

			if err := rows.Scan(&probabilityOfAnswer, &probablityOfDownvote, &minutes); err != nil {
				log.Printf("rows.Scan: %v", err)
			}

			var answer = &Answer{
				Minutes:   minutes,
				PDownvote: probablityOfDownvote,
				PAnswer:   probabilityOfAnswer,
			}

			json, _ := json.Marshal(answer)

			w.Write(json)
		} else {
			w.Write(getEmptyAnswer())
		}

	})

	// fs := wrapHandler(http.FileServer(http.Dir("./dist")))
	// http.HandleFunc("/experiment/bqml-stackoverflow/", fs)
	// http.Handle("/", http.FileServer(http.Dir("./static")))

	fmt.Printf("Starting server on port %s\n", port)
	if err := http.ListenAndServe(":"+port, nil); err != nil {
		log.Fatal(err)
	}

}

func getEmptyAnswer() []byte {
	json, _ := json.Marshal(&Answer{
		Minutes:   0,
		PAnswer:   0,
		PDownvote: 0,
	})

	return json
}

func getDatabase() (*sql.DB, error) {
	user := os.Getenv("DB_USER")
	password := os.Getenv("DB_PASSWORD")
	database := os.Getenv("DB_DATABASE")
	instance := os.Getenv("DB_INSTANCE")

	if appengine.IsDevAppServer() {
		return sql.Open("mysql", fmt.Sprintf("%s:%s@tcp([localhost]:3306)/%s", user, password, database))
	}
	log.Printf("Database call to (%s)/%s", instance, database)
	return sql.Open("mysql", fmt.Sprintf("%s:%s@unix(/cloudsql/%s)/%s", user, password, instance, database))
}

func attemptQuery(attempts int, r *http.Request) (*sql.Rows, error) {
	var rows *sql.Rows
	var err error

	query := r.URL.Query()

	for i := 0; i < attempts; i++ {
		rows, err = db.Query(
			queryString,
			query.Get("tag"),
			query.Get("first_word"),
			query.Get("ends_question"),
			query.Get("weekday_utc"),
			query.Get("account_creation_year"),
			query.Get("question_length"),
			query.Get("hour_utc"),
		)

		if err == nil {
			break
		} else {
			fmt.Printf("error getting using the database %v\n", err)
			db, err = getDatabase()
			if err != nil {
				fmt.Printf("error getting database %v\n", err)
			}
		}
	}

	return rows, err
}

// Answer respresents the api query response json.
type Answer struct {
	Minutes   float32 `json:"minutes"`
	PAnswer   float32 `json:"probability_of_answer"`
	PDownvote float32 `json:"probabiliy_of_downvote"`
}

type NotFoundRedirectRespWr struct {
	http.ResponseWriter // We embed http.ResponseWriter
	status              int
}

func (w *NotFoundRedirectRespWr) WriteHeader(status int) {
	w.status = status // Store the status for our own use
	if status != http.StatusNotFound {
		w.ResponseWriter.WriteHeader(status)
	}
}

func (w *NotFoundRedirectRespWr) Write(p []byte) (int, error) {
	if w.status != http.StatusNotFound {
		return w.ResponseWriter.Write(p)
	}
	return len(p), nil // Lie that we successfully written it
}

func wrapHandler(h http.Handler) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		nfrw := &NotFoundRedirectRespWr{ResponseWriter: w}
		h.ServeHTTP(nfrw, r)
		if nfrw.status == 404 {
			http.Redirect(w, r, "/experiment/bqml-stackoverflow/index.html", http.StatusFound)
		}
	}
}
<|endoftext|>


<commit_before>/*
Copyright 2015 The Kubernetes Authors All rights reserved.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package main

import (
	"fmt"
	"net/http"
	"net/http/pprof"
	"os"
	"os/signal"
	"syscall"
	"time"

	"github.com/golang/glog"
	"github.com/spf13/pflag"

	"k8s.io/contrib/ingress/controllers/nginx/nginx"

	"k8s.io/kubernetes/pkg/api"
	"k8s.io/kubernetes/pkg/client/unversioned"
	"k8s.io/kubernetes/pkg/healthz"
	kubectl_util "k8s.io/kubernetes/pkg/kubectl/cmd/util"
)

const (
	healthPort = 10249
)

var (
	// value overwritten during build. This can be used to resolve issues.
	version = "0.5"
	gitRepo = "https://github.com/kubernetes/contrib"

	flags = pflag.NewFlagSet("", pflag.ExitOnError)

	defaultSvc = flags.String("default-backend-service", "",
		`Service used to serve a 404 page for the default backend. Takes the form
    namespace/name. The controller uses the first node port of this Service for
    the default backend.`)

	nxgConfigMap = flags.String("nginx-configmap", "",
		`Name of the ConfigMap that containes the custom nginx configuration to use`)

	inCluster = flags.Bool("running-in-cluster", true,
		`Optional, if this controller is running in a kubernetes cluster, use the
		 pod secrets for creating a Kubernetes client.`)

	tcpConfigMapName = flags.String("tcp-services-configmap", "",
		`Name of the ConfigMap that containes the definition of the TCP services to expose.
		The key in the map indicates the external port to be used. The value is the name of the
		service with the format namespace/serviceName and the port of the service could be a number of the
		name of the port.
		The ports 80 and 443 are not allowed as external ports. This ports are reserved for nginx`)

	udpConfigMapName = flags.String("udp-services-configmap", "",
		`Name of the ConfigMap that containes the definition of the UDP services to expose.
		The key in the map indicates the external port to be used. The value is the name of the
		service with the format namespace/serviceName and the port of the service could be a number of the
		name of the port.`)

	resyncPeriod = flags.Duration("sync-period", 30*time.Second,
		`Relist and confirm cloud resources this often.`)

	watchNamespace = flags.String("watch-namespace", api.NamespaceAll,
		`Namespace to watch for Ingress. Default is to watch all namespaces`)

	healthzPort = flags.Int("healthz-port", healthPort, "port for healthz endpoint.")

	buildCfg = flags.Bool("dump-nginx—configuration", false, `Returns a ConfigMap with the default nginx conguration.
		This can be used as a guide to create a custom configuration.`)

	profiling = flags.Bool("profiling", true, `Enable profiling via web interface host:port/debug/pprof/`)
)

func main() {
	var kubeClient *unversioned.Client
	flags.Parse(os.Args)
	clientConfig := kubectl_util.DefaultClientConfig(flags)

	glog.Infof("Using build: %v - %v", gitRepo, version)

	if *buildCfg {
		fmt.Printf("Example of ConfigMap to customize NGINX configuration:\n%v", nginx.ConfigMapAsString())
		os.Exit(0)
	}

	if *defaultSvc == "" {
		glog.Fatalf("Please specify --default-backend-service")
	}

	var err error
	if *inCluster {
		kubeClient, err = unversioned.NewInCluster()
	} else {
		config, connErr := clientConfig.ClientConfig()
		if connErr != nil {
			glog.Fatalf("error connecting to the client: %v", err)
		}
		kubeClient, err = unversioned.New(config)
	}
	if err != nil {
		glog.Fatalf("failed to create client: %v", err)
	}

	runtimePodInfo := &podInfo{NodeIP: "127.0.0.1"}
	if *inCluster {
		runtimePodInfo, err = getPodDetails(kubeClient)
		if err != nil {
			glog.Fatalf("unexpected error getting runtime information: %v", err)
		}
	}
	if err := isValidService(kubeClient, *defaultSvc); err != nil {
		glog.Fatalf("no service with name %v found: %v", *defaultSvc, err)
	}
	glog.Infof("Validated %v as the default backend", *defaultSvc)

	lbc, err := newLoadBalancerController(kubeClient, *resyncPeriod, *defaultSvc, *watchNamespace, *nxgConfigMap, *tcpConfigMapName, *udpConfigMapName, runtimePodInfo)
	if err != nil {
		glog.Fatalf("%v", err)
	}

	go registerHandlers(lbc)
	go handleSigterm(lbc)

	lbc.Run()

	for {
		glog.Infof("Handled quit, awaiting pod deletion")
		time.Sleep(30 * time.Second)
	}
}

// podInfo contains runtime information about the pod
type podInfo struct {
	PodName      string
	PodNamespace string
	NodeIP       string
}

func registerHandlers(lbc *loadBalancerController) {
	mux := http.NewServeMux()
	healthz.InstallHandler(mux, lbc.nginx)

	http.HandleFunc("/build", func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(http.StatusOK)
		fmt.Fprint(w, "build: %v - %v", gitRepo, version)
	})

	http.HandleFunc("/stop", func(w http.ResponseWriter, r *http.Request) {
		lbc.Stop()
	})

	if *profiling {
		mux.HandleFunc("/debug/pprof/", pprof.Index)
		mux.HandleFunc("/debug/pprof/profile", pprof.Profile)
		mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
	}

	server := &http.Server{
		Addr:    fmt.Sprintf(":%v", *healthzPort),
		Handler: mux,
	}
	glog.Fatal(server.ListenAndServe())
}

func handleSigterm(lbc *loadBalancerController) {
	signalChan := make(chan os.Signal, 1)
	signal.Notify(signalChan, syscall.SIGTERM)
	<-signalChan
	glog.Infof("Received SIGTERM, shutting down")

	exitCode := 0
	if err := lbc.Stop(); err != nil {
		glog.Infof("Error during shutdown %v", err)
		exitCode = 1
	}
	glog.Infof("Exiting with %v", exitCode)
	os.Exit(exitCode)
}
<commit_msg>Parse cmdline flags so we get --v<commit_after>/*
Copyright 2015 The Kubernetes Authors All rights reserved.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package main

import (
	"flag"
	"fmt"
	"net/http"
	"net/http/pprof"
	"os"
	"os/signal"
	"syscall"
	"time"

	"github.com/golang/glog"
	"github.com/spf13/pflag"

	"k8s.io/contrib/ingress/controllers/nginx/nginx"

	"k8s.io/kubernetes/pkg/api"
	"k8s.io/kubernetes/pkg/client/unversioned"
	"k8s.io/kubernetes/pkg/healthz"
	kubectl_util "k8s.io/kubernetes/pkg/kubectl/cmd/util"
)

const (
	healthPort = 10249
)

var (
	// value overwritten during build. This can be used to resolve issues.
	version = "0.5"
	gitRepo = "https://github.com/kubernetes/contrib"

	flags = pflag.NewFlagSet("", pflag.ExitOnError)

	defaultSvc = flags.String("default-backend-service", "",
		`Service used to serve a 404 page for the default backend. Takes the form
    namespace/name. The controller uses the first node port of this Service for
    the default backend.`)

	nxgConfigMap = flags.String("nginx-configmap", "",
		`Name of the ConfigMap that containes the custom nginx configuration to use`)

	inCluster = flags.Bool("running-in-cluster", true,
		`Optional, if this controller is running in a kubernetes cluster, use the
		 pod secrets for creating a Kubernetes client.`)

	tcpConfigMapName = flags.String("tcp-services-configmap", "",
		`Name of the ConfigMap that containes the definition of the TCP services to expose.
		The key in the map indicates the external port to be used. The value is the name of the
		service with the format namespace/serviceName and the port of the service could be a number of the
		name of the port.
		The ports 80 and 443 are not allowed as external ports. This ports are reserved for nginx`)

	udpConfigMapName = flags.String("udp-services-configmap", "",
		`Name of the ConfigMap that containes the definition of the UDP services to expose.
		The key in the map indicates the external port to be used. The value is the name of the
		service with the format namespace/serviceName and the port of the service could be a number of the
		name of the port.`)

	resyncPeriod = flags.Duration("sync-period", 30*time.Second,
		`Relist and confirm cloud resources this often.`)

	watchNamespace = flags.String("watch-namespace", api.NamespaceAll,
		`Namespace to watch for Ingress. Default is to watch all namespaces`)

	healthzPort = flags.Int("healthz-port", healthPort, "port for healthz endpoint.")

	buildCfg = flags.Bool("dump-nginx—configuration", false, `Returns a ConfigMap with the default nginx conguration.
		This can be used as a guide to create a custom configuration.`)

	profiling = flags.Bool("profiling", true, `Enable profiling via web interface host:port/debug/pprof/`)
)

func main() {
	var kubeClient *unversioned.Client
	flags.AddGoFlagSet(flag.CommandLine)
	flags.Parse(os.Args)
	clientConfig := kubectl_util.DefaultClientConfig(flags)

	glog.Infof("Using build: %v - %v", gitRepo, version)

	if *buildCfg {
		fmt.Printf("Example of ConfigMap to customize NGINX configuration:\n%v", nginx.ConfigMapAsString())
		os.Exit(0)
	}

	if *defaultSvc == "" {
		glog.Fatalf("Please specify --default-backend-service")
	}

	var err error
	if *inCluster {
		kubeClient, err = unversioned.NewInCluster()
	} else {
		config, connErr := clientConfig.ClientConfig()
		if connErr != nil {
			glog.Fatalf("error connecting to the client: %v", err)
		}
		kubeClient, err = unversioned.New(config)
	}
	if err != nil {
		glog.Fatalf("failed to create client: %v", err)
	}

	runtimePodInfo := &podInfo{NodeIP: "127.0.0.1"}
	if *inCluster {
		runtimePodInfo, err = getPodDetails(kubeClient)
		if err != nil {
			glog.Fatalf("unexpected error getting runtime information: %v", err)
		}
	}
	if err := isValidService(kubeClient, *defaultSvc); err != nil {
		glog.Fatalf("no service with name %v found: %v", *defaultSvc, err)
	}
	glog.Infof("Validated %v as the default backend", *defaultSvc)

	lbc, err := newLoadBalancerController(kubeClient, *resyncPeriod, *defaultSvc, *watchNamespace, *nxgConfigMap, *tcpConfigMapName, *udpConfigMapName, runtimePodInfo)
	if err != nil {
		glog.Fatalf("%v", err)
	}

	go registerHandlers(lbc)
	go handleSigterm(lbc)

	lbc.Run()

	for {
		glog.Infof("Handled quit, awaiting pod deletion")
		time.Sleep(30 * time.Second)
	}
}

// podInfo contains runtime information about the pod
type podInfo struct {
	PodName      string
	PodNamespace string
	NodeIP       string
}

func registerHandlers(lbc *loadBalancerController) {
	mux := http.NewServeMux()
	healthz.InstallHandler(mux, lbc.nginx)

	http.HandleFunc("/build", func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(http.StatusOK)
		fmt.Fprint(w, "build: %v - %v", gitRepo, version)
	})

	http.HandleFunc("/stop", func(w http.ResponseWriter, r *http.Request) {
		lbc.Stop()
	})

	if *profiling {
		mux.HandleFunc("/debug/pprof/", pprof.Index)
		mux.HandleFunc("/debug/pprof/profile", pprof.Profile)
		mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
	}

	server := &http.Server{
		Addr:    fmt.Sprintf(":%v", *healthzPort),
		Handler: mux,
	}
	glog.Fatal(server.ListenAndServe())
}

func handleSigterm(lbc *loadBalancerController) {
	signalChan := make(chan os.Signal, 1)
	signal.Notify(signalChan, syscall.SIGTERM)
	<-signalChan
	glog.Infof("Received SIGTERM, shutting down")

	exitCode := 0
	if err := lbc.Stop(); err != nil {
		glog.Infof("Error during shutdown %v", err)
		exitCode = 1
	}
	glog.Infof("Exiting with %v", exitCode)
	os.Exit(exitCode)
}
<|endoftext|>


<commit_before>// Copyright 2012 Aaron Jacobs. All Rights Reserved.
// Author: aaronjjacobs@gmail.com (Aaron Jacobs)
//
// 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 auth

import (
	"bytes"
	"crypto/hmac"
	"crypto/sha1"
	"encoding/base64"
	"errors"
	"github.com/jacobsa/aws"
	"github.com/jacobsa/aws/s3/http"
	. "github.com/jacobsa/oglematchers"
	. "github.com/jacobsa/ogletest"
	"testing"
)

func TestSigner(t *testing.T) { RunTests(t) }

////////////////////////////////////////////////////////////////////////
// Helpers
////////////////////////////////////////////////////////////////////////

type SignerTest struct {}

func init() { RegisterTestSuite(&SignerTest{}) }

////////////////////////////////////////////////////////////////////////
// Tests
////////////////////////////////////////////////////////////////////////

func (t *SignerTest) CallsFunction() {
	// Function
	var stsArg *http.Request
	sts := func(r *http.Request)(string, error) { stsArg = r; return "", nil }

	// Signer
	signer, err := newSigner(sts, &aws.AccessKey{})
	AssertEq(nil, err)

	// Call
	req := &http.Request{}
	signer.Sign(req)

	ExpectEq(req, stsArg)
}

func (t *SignerTest) FunctionReturnsError() {
	// Function
	sts := func(r *http.Request)(string, error) { return "", errors.New("taco") }

	// Signer
	signer, err := newSigner(sts, &aws.AccessKey{})
	AssertEq(nil, err)

	// Call
	err = signer.Sign(&http.Request{})

	ExpectThat(err, Error(HasSubstr("Sign")))
	ExpectThat(err, Error(HasSubstr("taco")))
}

func (t *SignerTest) FunctionReturnsString() {
	// Function
	sts := func(r *http.Request)(string, error) { return "taco", nil }

	// Signer
	key := &aws.AccessKey{Id: "queso", Secret: "burrito"}
	signer, err := newSigner(sts, key)
	AssertEq(nil, err)

	// Expected output
	h := hmac.New(sha1.New, []byte("burrito"))
	_, err = h.Write([]byte("taco"))
	AssertEq(nil, err)

	buf := new(bytes.Buffer)
	encoder := base64.NewEncoder(base64.StdEncoding, buf)
	_, err = encoder.Write(h.Sum(nil))
	AssertEq(nil, err)
	AssertEq(nil, encoder.Close())

	expected := "AWS queso:" + buf.String()

	// Call
	req := &http.Request{
		Headers: map[string]string {
			"foo": "bar",
		},
	}

	err = signer.Sign(req)
	AssertEq(nil, err)

	ExpectEq("bar", req.Headers["foo"])
	ExpectEq(expected, req.Headers["Authorization"])
}

func (t *SignerTest) GoldenTests() {
	type testCase struct {
		stringToSign string
		expectedHeaderValue string
	}

	// Golden tests taken from Amazon doc examples.
	key := &aws.AccessKey{
		Id: "AKIAIOSFODNN7EXAMPLE",
		Secret: "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
	}

	cases := []testCase{
		testCase{
			"GET\n\n\nTue, 27 Mar 2007 19:36:42 +0000\n/johnsmith/photos/puppy.jpg",
			"AWS AKIAIOSFODNN7EXAMPLE:bWq2s1WEIj+Ydj0vQ697zp+IXMU=",
		},
		testCase{
			"PUT\n\nimage/jpeg\nTue, 27 Mar 2007 21:15:45 +0000\n/johnsmith/photos/puppy.jpg",
			"AWS AKIAIOSFODNN7EXAMPLE:MyyxeRY7whkBe+bq8fHCL/2kKUg=",
		},
		testCase{
			"GET\n\n\nTue, 27 Mar 2007 19:42:41 +0000\n/johnsmith/",
			"AWS AKIAIOSFODNN7EXAMPLE:htDYFYduRNen8P9ZfE/s9SuKy0U=",
		},
		testCase{
			"GET\n\n\nTue, 27 Mar 2007 19:44:46 +0000\n/johnsmith/?acl",
			"AWS AKIAIOSFODNN7EXAMPLE:c2WLPFtWHVgbEmeEG93a4cG37dM=",
		},
		testCase{
			"DELETE\n\n\nx-amz-date:Tue, 27 Mar 2007 21:20:26 +0000\n/johnsmith/photos/puppy.jpg",
			"AWS AKIAIOSFODNN7EXAMPLE:9b2sXq0KfxsxHtdZkzx/9Ngqyh8=",
		},
	}

	for i, c := range cases {
		// Function
		sts := func(r *http.Request)(string, error) { return c.stringToSign, nil }

		// Signer
		signer, err := newSigner(sts, key)
		AssertEq(nil, err)

		// Call
		req := &http.Request{}
		err = signer.Sign(req)
		AssertEq(nil, err)

		ExpectEq(c.expectedHeaderValue, req.Headers["Authorization"], "Case %d: %v", i, c)
	}
}
<commit_msg>Fixed test bugs.<commit_after>// Copyright 2012 Aaron Jacobs. All Rights Reserved.
// Author: aaronjjacobs@gmail.com (Aaron Jacobs)
//
// 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 auth

import (
	"bytes"
	"crypto/hmac"
	"crypto/sha1"
	"encoding/base64"
	"errors"
	"github.com/jacobsa/aws"
	"github.com/jacobsa/aws/s3/http"
	. "github.com/jacobsa/oglematchers"
	. "github.com/jacobsa/ogletest"
	"testing"
)

func TestSigner(t *testing.T) { RunTests(t) }

////////////////////////////////////////////////////////////////////////
// Helpers
////////////////////////////////////////////////////////////////////////

type SignerTest struct {}

func init() { RegisterTestSuite(&SignerTest{}) }

////////////////////////////////////////////////////////////////////////
// Tests
////////////////////////////////////////////////////////////////////////

func (t *SignerTest) CallsFunction() {
	// Function
	var stsArg *http.Request
	sts := func(r *http.Request)(string, error) { stsArg = r; return "", nil }

	// Signer
	signer, err := newSigner(sts, &aws.AccessKey{})
	AssertEq(nil, err)

	// Call
	req := &http.Request{Headers: make(map[string]string)}
	signer.Sign(req)

	ExpectEq(req, stsArg)
}

func (t *SignerTest) FunctionReturnsError() {
	// Function
	sts := func(r *http.Request)(string, error) { return "", errors.New("taco") }

	// Signer
	signer, err := newSigner(sts, &aws.AccessKey{})
	AssertEq(nil, err)

	// Call
	err = signer.Sign(&http.Request{Headers: make(map[string]string)})

	ExpectThat(err, Error(HasSubstr("Sign")))
	ExpectThat(err, Error(HasSubstr("taco")))
}

func (t *SignerTest) FunctionReturnsString() {
	// Function
	sts := func(r *http.Request)(string, error) { return "taco", nil }

	// Signer
	key := &aws.AccessKey{Id: "queso", Secret: "burrito"}
	signer, err := newSigner(sts, key)
	AssertEq(nil, err)

	// Expected output
	h := hmac.New(sha1.New, []byte("burrito"))
	_, err = h.Write([]byte("taco"))
	AssertEq(nil, err)

	buf := new(bytes.Buffer)
	encoder := base64.NewEncoder(base64.StdEncoding, buf)
	_, err = encoder.Write(h.Sum(nil))
	AssertEq(nil, err)
	AssertEq(nil, encoder.Close())

	expected := "AWS queso:" + buf.String()

	// Call
	req := &http.Request{
		Headers: map[string]string {
			"foo": "bar",
		},
	}

	err = signer.Sign(req)
	AssertEq(nil, err)

	ExpectEq("bar", req.Headers["foo"])
	ExpectEq(expected, req.Headers["Authorization"])
}

func (t *SignerTest) GoldenTests() {
	type testCase struct {
		stringToSign string
		expectedHeaderValue string
	}

	// Golden tests taken from Amazon doc examples.
	key := &aws.AccessKey{
		Id: "AKIAIOSFODNN7EXAMPLE",
		Secret: "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
	}

	cases := []testCase{
		testCase{
			"GET\n\n\nTue, 27 Mar 2007 19:36:42 +0000\n/johnsmith/photos/puppy.jpg",
			"AWS AKIAIOSFODNN7EXAMPLE:bWq2s1WEIj+Ydj0vQ697zp+IXMU=",
		},
		testCase{
			"PUT\n\nimage/jpeg\nTue, 27 Mar 2007 21:15:45 +0000\n/johnsmith/photos/puppy.jpg",
			"AWS AKIAIOSFODNN7EXAMPLE:MyyxeRY7whkBe+bq8fHCL/2kKUg=",
		},
		testCase{
			"GET\n\n\nTue, 27 Mar 2007 19:42:41 +0000\n/johnsmith/",
			"AWS AKIAIOSFODNN7EXAMPLE:htDYFYduRNen8P9ZfE/s9SuKy0U=",
		},
		testCase{
			"GET\n\n\nTue, 27 Mar 2007 19:44:46 +0000\n/johnsmith/?acl",
			"AWS AKIAIOSFODNN7EXAMPLE:c2WLPFtWHVgbEmeEG93a4cG37dM=",
		},
		testCase{
			"DELETE\n\n\nx-amz-date:Tue, 27 Mar 2007 21:20:26 +0000\n/johnsmith/photos/puppy.jpg",
			"AWS AKIAIOSFODNN7EXAMPLE:9b2sXq0KfxsxHtdZkzx/9Ngqyh8=",
		},
	}

	for i, c := range cases {
		// Function
		sts := func(r *http.Request)(string, error) { return c.stringToSign, nil }

		// Signer
		signer, err := newSigner(sts, key)
		AssertEq(nil, err)

		// Call
		req := &http.Request{Headers: make(map[string]string)}
		err = signer.Sign(req)
		AssertEq(nil, err)

		ExpectEq(c.expectedHeaderValue, req.Headers["Authorization"], "Case %d: %v", i, c)
	}
}
<|endoftext|>


<commit_before>package host

import (
	"errors"

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

// announce creates an announcement transaction and submits it to the network.
func (h *Host) announce(addr modules.NetAddress) error {
	// Generate an unlock hash, if necessary.
	if h.settings.UnlockHash == (types.UnlockHash{}) {
		uc, err := h.wallet.NextAddress()
		if err != nil {
			return err
		}
		h.settings.UnlockHash = uc.UnlockHash()
		err = h.save()
		if err != nil {
			return err
		}
	}

	// Create a transaction with a host announcement.
	txnBuilder := h.wallet.StartTransaction()
	announcement := encoding.Marshal(modules.HostAnnouncement{
		IPAddress: addr,
	})
	_ = txnBuilder.AddArbitraryData(append(modules.PrefixHostAnnouncement[:], announcement...))
	txn, parents := txnBuilder.View()
	txnSet := append(parents, txn)

	// Add the transaction to the transaction pool.
	err := h.tpool.AcceptTransactionSet(txnSet)
	if err == modules.ErrDuplicateTransactionSet {
		return errors.New("you have already announced yourself")
	}
	if err != nil {
		return err
	}
	h.log.Printf("INFO: Successfully announced as %v", addr)

	return nil
}

// Announce creates a host announcement transaction, adding information to the
// arbitrary data, signing the transaction, and submitting it to the
// transaction pool.
func (h *Host) Announce() error {
	// As an exported function that needs resources, Annouce holds the resource
	// lock throughout its operation.
	h.resourceLock.RLock()
	defer h.resourceLock.RUnlock()
	if h.closed {
		return errHostClosed
	}

	// Get the external IP again; it may have changed.
	h.learnHostname()
	h.mu.RLock()
	addr := h.netAddress
	h.mu.RUnlock()

	// Check that the host's ip address is known.
	if addr.IsLoopback() && build.Release != "testing" {
		return errors.New("can't announce without knowing external IP")
	}

	return h.announce(addr)
}

// AnnounceAddress submits a host announcement to the blockchain to announce a
// specific address. No checks for validity are performed on the address.
func (h *Host) AnnounceAddress(addr modules.NetAddress) error {
	// As an exported function that needs resources, AnnouceAddress holds the
	// resource lock throughout its operation.
	h.resourceLock.RLock()
	defer h.resourceLock.RUnlock()
	if h.closed {
		return errHostClosed
	}
	return h.announce(addr)
}
<commit_msg>fix typos<commit_after>package host

import (
	"errors"

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

// announce creates an announcement transaction and submits it to the network.
func (h *Host) announce(addr modules.NetAddress) error {
	// Generate an unlock hash, if necessary.
	if h.settings.UnlockHash == (types.UnlockHash{}) {
		uc, err := h.wallet.NextAddress()
		if err != nil {
			return err
		}
		h.settings.UnlockHash = uc.UnlockHash()
		err = h.save()
		if err != nil {
			return err
		}
	}

	// Create a transaction with a host announcement.
	txnBuilder := h.wallet.StartTransaction()
	announcement := encoding.Marshal(modules.HostAnnouncement{
		IPAddress: addr,
	})
	_ = txnBuilder.AddArbitraryData(append(modules.PrefixHostAnnouncement[:], announcement...))
	txn, parents := txnBuilder.View()
	txnSet := append(parents, txn)

	// Add the transaction to the transaction pool.
	err := h.tpool.AcceptTransactionSet(txnSet)
	if err == modules.ErrDuplicateTransactionSet {
		return errors.New("you have already announced yourself")
	}
	if err != nil {
		return err
	}
	h.log.Printf("INFO: Successfully announced as %v", addr)

	return nil
}

// Announce creates a host announcement transaction, adding information to the
// arbitrary data, signing the transaction, and submitting it to the
// transaction pool.
func (h *Host) Announce() error {
	// As an exported function that needs resources, Announce holds the resource
	// lock throughout its operation.
	h.resourceLock.RLock()
	defer h.resourceLock.RUnlock()
	if h.closed {
		return errHostClosed
	}

	// Get the external IP again; it may have changed.
	h.learnHostname()
	h.mu.RLock()
	addr := h.netAddress
	h.mu.RUnlock()

	// Check that the host's ip address is known.
	if addr.IsLoopback() && build.Release != "testing" {
		return errors.New("can't announce without knowing external IP")
	}

	return h.announce(addr)
}

// AnnounceAddress submits a host announcement to the blockchain to announce a
// specific address. No checks for validity are performed on the address.
func (h *Host) AnnounceAddress(addr modules.NetAddress) error {
	// As an exported function that needs resources, AnnounceAddress holds the
	// resource lock throughout its operation.
	h.resourceLock.RLock()
	defer h.resourceLock.RUnlock()
	if h.closed {
		return errHostClosed
	}
	return h.announce(addr)
}
<|endoftext|>


<commit_before>package wootric

import (
	"fmt"
	"net/url"
	"regexp"
	"strings"

	"github.com/grokify/chathooks/src/config"
	"github.com/grokify/chathooks/src/handlers"
	"github.com/grokify/chathooks/src/models"
	cc "github.com/grokify/commonchat"
	"github.com/grokify/gotilla/fmt/fmtutil"
	"github.com/grokify/gotilla/html/htmlutil"
	"github.com/pkg/errors"
)

const (
	DisplayName      = "Wootric"
	HandlerKey       = "wootric"
	MessageDirection = "out"
	MessageBodyType  = models.URLEncodedRails // application/x-www-form-urlencoded

	WootricFormatVarResponse = "wootricFormatResponse"
	WootricFormatDefault     = `score[NPS Score],text[Why];firstName lastName[User name];email[User email];survey_id[Survey ID]`
)

func NewHandler() handlers.Handler {
	return handlers.Handler{
		Key:             HandlerKey,
		MessageBodyType: MessageBodyType,
		Normalize:       Normalize}
}

func Normalize(cfg config.Configuration, hReq handlers.HandlerRequest) (cc.Message, error) {
	if hReq.QueryParams == nil {
		hReq.QueryParams = url.Values{}
	}
	ccMsg := cc.NewMessage()
	iconURL, err := cfg.GetAppIconURL(HandlerKey)
	if err == nil {
		ccMsg.IconURL = iconURL.String()
	}

	body, err := url.QueryUnescape(string(hReq.Body))
	if err != nil {
		return ccMsg, errors.Wrap(err, "wootric.Normalize")
	}
	src, err := ParseQueryString(body)
	if err != nil {
		return ccMsg, err
	}

	ccMsg.Activity = src.Activity()
	ccMsg.Title = src.Activity()

	fmtutil.PrintJSON(hReq.QueryParams)

	if src.IsResponse() {
		responseFormat := WootricFormatDefault
		tryFormat := strings.TrimSpace(hReq.QueryParams.Get(WootricFormatVarResponse))
		if len(tryFormat) > 0 {
			responseFormat = tryFormat
		}

		attachment := cc.NewAttachment()
		lines := ParseFields(responseFormat)

		scoreInt64, err := src.Response.Score.Int64()
		if err == nil {
			if scoreInt64 >= 9 {
				attachment.Color = htmlutil.Color2GreenHex
			} else if scoreInt64 >= 7 {
				attachment.Color = htmlutil.Color2YellowHex
			} else {
				attachment.Color = htmlutil.Color2RedHex
			}
		}

		for _, line := range lines {
			numFields := len(line.Fields)
			if numFields == 0 {
				continue
			}
			isShort := false
			if numFields > 1 {
				isShort = true
			}

			for _, field := range line.Fields {
				if field.Property == "score" {
					val := strings.TrimSpace(src.Response.Score.String())
					attachment.AddField(cc.Field{
						Title: field.Display, Short: isShort, Value: val})
				} else if field.Property == "text" {
					attachment.AddField(cc.Field{
						Title: field.Display, Short: isShort, Value: src.Response.Text})
				} else if field.Property == "email" {
					attachment.AddField(cc.Field{
						Title: field.Display, Short: isShort, Value: src.Response.Email})
				} else if field.Property == "survey_id" {
					attachment.AddField(cc.Field{
						Title: field.Display, Short: isShort, Value: src.Response.SurveyID})
				} else if field.IsCustom {
					val := ""
					if src.Response.EndUserProperties != nil {
						if try, ok := src.Response.EndUserProperties[field.Property]; ok {
							val = try
						}
					}
					attachment.AddField(cc.Field{
						Title: field.Display,
						Short: isShort,
						Value: val})
				}
			}
		}
		if len(attachment.Fields) > 0 {
			for i, f := range attachment.Fields {
				if len(f.Value) == 0 {
					f.Value = "[empty]"
				}
				attachment.Fields[i] = f
			}
			ccMsg.AddAttachment(attachment)
		}
	}
	return ccMsg, nil
}

type Line struct {
	Fields []Field
}

type Field struct {
	Property  string
	Display   string
	IsCustom  bool
	UseParens bool
}

var (
	rxParens    = regexp.MustCompile(`^\((.*)\)$`)
	rxBrackets  = regexp.MustCompile(`^(.*)\[(.*)\]$`)
	rxCustom    = regexp.MustCompile(`^_(.*)$`)
	rxCustomOld = regexp.MustCompile(`^(.*)__c$`)
)

func ParseFields(fields string) []Line {
	lines := []Line{}
	parts := strings.Split(strings.TrimSpace(fields), ";")
	// Lines
	for _, part := range parts {
		line := Line{Fields: []Field{}}
		lineRaw := strings.TrimSpace(part)
		lineVars := strings.Split(lineRaw, ",")
		// Line Vars
		for _, lineVar := range lineVars {
			lineVar = strings.TrimSpace(lineVar)
			if len(lineVar) == 0 {
				continue
			}
			field := Field{}
			// Use parens
			m1 := rxParens.FindAllStringSubmatch(lineVar, -1)
			if len(m1) > 0 {
				field.UseParens = true
				lineVar = m1[0][1]
			}
			// Brackets
			m2 := rxBrackets.FindAllStringSubmatch(lineVar, -1)
			if len(m2) > 0 {
				field.Display = strings.TrimSpace(m2[0][2])
				propertyNameRaw := strings.TrimSpace(m2[0][1])
				m3 := rxCustom.FindAllStringSubmatch(propertyNameRaw, -1)
				if len(m3) > 0 {
					field.Property = strings.TrimSpace(m3[0][1])
					field.IsCustom = true
				} else {
					field.Property = propertyNameRaw
				}
			}
			if len(field.Property) == 0 {
				fmt.Println(lineVar)
				fmtutil.PrintJSON(field)
				//panic("Z")
			} else {
				line.Fields = append(line.Fields, field)
			}
		}
		lines = append(lines, line)
	}
	return lines
}

//score[Score],text(Why);company_name__c(Company Name),(rcAccountId__c[RC Account ID]);email[User email];directorySize[Number of users];brand[Brand];survey_id[Survey ID]
<commit_msg>update Wootric<commit_after>package wootric

import (
	"fmt"
	"net/url"
	"regexp"
	"strings"

	"github.com/grokify/chathooks/src/config"
	"github.com/grokify/chathooks/src/handlers"
	"github.com/grokify/chathooks/src/models"
	cc "github.com/grokify/commonchat"
	"github.com/grokify/gotilla/fmt/fmtutil"
	"github.com/grokify/gotilla/html/htmlutil"
	"github.com/pkg/errors"
)

const (
	DisplayName      = "Wootric"
	HandlerKey       = "wootric"
	MessageDirection = "out"
	MessageBodyType  = models.URLEncodedRails // application/x-www-form-urlencoded

	WootricFormatVarResponse = "wootricFormatResponse"
	WootricFormatDefault     = `score[NPS Score],text[Why];firstName lastName[User name];email[User email];survey_id[Survey ID]`
)

func NewHandler() handlers.Handler {
	return handlers.Handler{
		Key:             HandlerKey,
		MessageBodyType: MessageBodyType,
		Normalize:       Normalize}
}

func Normalize(cfg config.Configuration, hReq handlers.HandlerRequest) (cc.Message, error) {
	if hReq.QueryParams == nil {
		hReq.QueryParams = url.Values{}
	}
	ccMsg := cc.NewMessage()
	iconURL, err := cfg.GetAppIconURL(HandlerKey)
	if err == nil {
		ccMsg.IconURL = iconURL.String()
	}

	body, err := url.QueryUnescape(string(hReq.Body))
	if err != nil {
		return ccMsg, errors.Wrap(err, "wootric.Normalize")
	}
	src, err := ParseQueryString(body)
	if err != nil {
		return ccMsg, err
	}
	/*
		ccMsg.Activity = src.Activity()
		ccMsg.Title = src.Activity()
	*/
	fmtutil.PrintJSON(hReq.QueryParams)

	if src.IsResponse() {
		responseFormat := WootricFormatDefault
		tryFormat := strings.TrimSpace(hReq.QueryParams.Get(WootricFormatVarResponse))
		if len(tryFormat) > 0 {
			responseFormat = tryFormat
		}

		attachment := cc.NewAttachment()
		lines := ParseFields(responseFormat)

		scoreInt64, err := src.Response.Score.Int64()
		if err == nil {
			if scoreInt64 >= 9 {
				attachment.Color = htmlutil.Color2GreenHex
			} else if scoreInt64 >= 7 {
				attachment.Color = htmlutil.Color2YellowHex
			} else {
				attachment.Color = htmlutil.Color2RedHex
			}
		}

		for _, line := range lines {
			numFields := len(line.Fields)
			if numFields == 0 {
				continue
			}
			isShort := false
			if numFields > 1 {
				isShort = true
			}

			for _, field := range line.Fields {
				if field.Property == "score" {
					val := strings.TrimSpace(src.Response.Score.String())
					if len(val) > 0 {
						attachment.AddField(cc.Field{
							Title: field.Display, Short: isShort, Value: val})
					}
				} else if field.Property == "text" &&
					len(strings.TrimSpace(src.Response.Text)) > 0 {
					attachment.AddField(cc.Field{
						Title: field.Display, Short: isShort, Value: src.Response.Text})
				} else if field.Property == "email" &&
					len(strings.TrimSpace(src.Response.Email)) > 0 {
					attachment.AddField(cc.Field{
						Title: field.Display, Short: isShort, Value: src.Response.Email})
				} else if field.Property == "survey_id" &&
					len(strings.TrimSpace(src.Response.SurveyID)) > 0 {
					attachment.AddField(cc.Field{
						Title: field.Display, Short: isShort, Value: src.Response.SurveyID})
				} else if field.IsCustom {
					val := ""
					if src.Response.EndUserProperties != nil {
						if try, ok := src.Response.EndUserProperties[field.Property]; ok {
							val = try
						}
					}
					if field.Property == "brand" {
						if val == "rc-glip" {
							val = "RingCentral"
						} else if val == "non-rc-glip" {
							val = "Non-RingCentral"
						}
					}
					if len(val) > 0 {
						attachment.AddField(cc.Field{
							Title: field.Display,
							Short: isShort,
							Value: val})
					}
				}
			}
		}
		if 1 == 0 && len(attachment.Fields) > 0 {
			for i, f := range attachment.Fields {
				if len(f.Value) == 0 {
					f.Value = "[empty]"
				}
				attachment.Fields[i] = f
			}
			ccMsg.AddAttachment(attachment)
		}
	}
	return ccMsg, nil
}

type Line struct {
	Fields []Field
}

type Field struct {
	Property  string
	Display   string
	IsCustom  bool
	UseParens bool
}

var (
	rxParens    = regexp.MustCompile(`^\((.*)\)$`)
	rxBrackets  = regexp.MustCompile(`^(.*)\[(.*)\]$`)
	rxCustom    = regexp.MustCompile(`^_(.*)$`)
	rxCustomOld = regexp.MustCompile(`^(.*)__c$`)
)

func ParseFields(fields string) []Line {
	lines := []Line{}
	parts := strings.Split(strings.TrimSpace(fields), ";")
	// Lines
	for _, part := range parts {
		line := Line{Fields: []Field{}}
		lineRaw := strings.TrimSpace(part)
		lineVars := strings.Split(lineRaw, ",")
		// Line Vars
		for _, lineVar := range lineVars {
			lineVar = strings.TrimSpace(lineVar)
			if len(lineVar) == 0 {
				continue
			}
			field := Field{}
			// Use parens
			m1 := rxParens.FindAllStringSubmatch(lineVar, -1)
			if len(m1) > 0 {
				field.UseParens = true
				lineVar = m1[0][1]
			}
			// Brackets
			m2 := rxBrackets.FindAllStringSubmatch(lineVar, -1)
			if len(m2) > 0 {
				field.Display = strings.TrimSpace(m2[0][2])
				propertyNameRaw := strings.TrimSpace(m2[0][1])
				m3 := rxCustom.FindAllStringSubmatch(propertyNameRaw, -1)
				if len(m3) > 0 {
					field.Property = strings.TrimSpace(m3[0][1])
					field.IsCustom = true
				} else {
					field.Property = propertyNameRaw
				}
			}
			if len(field.Property) == 0 {
				fmt.Println(lineVar)
				fmtutil.PrintJSON(field)
				//panic("Z")
			} else {
				line.Fields = append(line.Fields, field)
			}
		}
		lines = append(lines, line)
	}
	return lines
}

//score[Score],text(Why);company_name__c(Company Name),(rcAccountId__c[RC Account ID]);email[User email];directorySize[Number of users];brand[Brand];survey_id[Survey ID]
<|endoftext|>


<commit_before>package wootric

import (
	"fmt"
	"net/url"
	"regexp"
	"strings"

	"github.com/grokify/chathooks/src/config"
	"github.com/grokify/chathooks/src/handlers"
	"github.com/grokify/chathooks/src/models"
	cc "github.com/grokify/commonchat"
	"github.com/grokify/gotilla/fmt/fmtutil"
	"github.com/grokify/gotilla/html/htmlutil"

	log "github.com/sirupsen/logrus"
)

const (
	DisplayName      = "Wootric"
	HandlerKey       = "wootric"
	MessageDirection = "out"
	MessageBodyType  = models.URLEncodedRails // application/x-www-form-urlencoded

	WootricFormatVarResponse = "wootricFormatResponse"
	WootricFormatDefault     = `score[NPS Score],text[Why];email[User email];survey_id[Survey ID]`
)

func NewHandler() handlers.Handler {
	return handlers.Handler{
		Key:             HandlerKey,
		MessageBodyType: MessageBodyType,
		Normalize:       Normalize}
}

func Normalize(cfg config.Configuration, hReq handlers.HandlerRequest) (cc.Message, error) {
	if hReq.Params == nil {
		hReq.Params = url.Values{}
	}
	ccMsg := cc.NewMessage()
	iconURL, err := cfg.GetAppIconURL(HandlerKey)
	if err == nil {
		ccMsg.IconURL = iconURL.String()
	}

	fmtutil.PrintJSON(ccMsg)

	body, err := url.QueryUnescape(string(hReq.Body))

	if err != nil {
		panic("Z")
	}
	//src, err := ParseQueryString(string(hReq.Body))
	src, err := ParseQueryString(body)
	if err != nil {
		return ccMsg, err
	}
	log.Info("WOOTRIC_BODY: " + string(hReq.Body))

	ccMsg.Activity = src.Activity()
	fmtutil.PrintJSON(src)
	fmtutil.PrintJSON(hReq.Params)

	if src.IsResponse() {
		responseFormat := WootricFormatDefault
		tryFormat := strings.TrimSpace(hReq.Params.Get(WootricFormatVarResponse))
		if len(tryFormat) > 0 {
			responseFormat = tryFormat
		}
		/*
			if tryFormat, ok := hReq.Params[WootricFormatVarResponse]; ok {
				tryFormat = strings.TrimSpace(tryFormat)
				if len(tryFormat) > 0 {
					responseFormat = tryFormat
					log.Info("GOOT_LAYOUT")
				}
			}*/
		fmt.Printf("LAYOUT: [%v]\n", responseFormat)
		attachment := cc.NewAttachment()
		lines := ParseFields(responseFormat)
		fmtutil.PrintJSON(lines)

		scoreInt64, err := src.Response.Score.Int64()
		if err == nil {
			if scoreInt64 >= 9 {
				attachment.Color = htmlutil.Color2GreenHex
			} else if scoreInt64 >= 7 {
				attachment.Color = htmlutil.Color2YellowHex
			} else {
				attachment.Color = htmlutil.Color2RedHex
			}
		}

		for _, line := range lines {
			numFields := len(line.Fields)
			if numFields == 0 {
				continue
			}
			isShort := false
			if numFields > 0 {
				isShort = true
			}
			/*
				isShort := true
				if numFields == 1 {
					isShort = false
				}*/

			for _, field := range line.Fields {
				if field.Property == "score" {
					fmtutil.PrintJSON(src.Response)
					val := strings.TrimSpace(src.Response.Score.String())
					attachment.AddField(cc.Field{
						Title: field.Display, Short: isShort, Value: val})
				} else if field.Property == "text" {
					attachment.AddField(cc.Field{
						Title: field.Display, Short: isShort, Value: src.Response.Text})
				} else if field.Property == "email" {
					attachment.AddField(cc.Field{
						Title: field.Display,
						Value: src.Response.Email})
				} else if field.Property == "survey_id" {
					attachment.AddField(cc.Field{
						Title: field.Display,
						Value: src.Response.SurveyID})
				} else if field.IsCustom {
					val := ""
					if src.Response.EndUserProperties != nil {
						if try, ok := src.Response.EndUserProperties[field.Property]; ok {
							val = try
						}
					}
					attachment.AddField(cc.Field{
						Title: field.Display,
						Value: val})
				}
			}
		}
		if len(attachment.Fields) > 0 {
			ccMsg.AddAttachment(attachment)
		}
	}

	return ccMsg, nil
}

type Line struct {
	Fields []Field
}

type Field struct {
	Property  string
	Display   string
	IsCustom  bool
	UseParens bool
}

var (
	rxParens    = regexp.MustCompile(`^\((.*)\)$`)
	rxBrackets  = regexp.MustCompile(`^(.*)\[(.*)\]$`)
	rxCustom    = regexp.MustCompile(`^_(.*)$`)
	rxCustomOld = regexp.MustCompile(`^(.*)__c$`)
)

func ParseFields(fields string) []Line {
	lines := []Line{}
	parts := strings.Split(strings.TrimSpace(fields), ";")
	// Lines
	for _, part := range parts {
		line := Line{Fields: []Field{}}
		lineRaw := strings.TrimSpace(part)
		lineVars := strings.Split(lineRaw, ",")
		// Line Vars
		for _, lineVar := range lineVars {
			lineVar = strings.TrimSpace(lineVar)
			if len(lineVar) == 0 {
				continue
			}
			field := Field{}
			// Use parens
			m1 := rxParens.FindAllStringSubmatch(lineVar, -1)
			if len(m1) > 0 {
				field.UseParens = true
				lineVar = m1[0][1]
			}
			// Brackets
			m2 := rxBrackets.FindAllStringSubmatch(lineVar, -1)
			if len(m2) > 0 {
				field.Display = strings.TrimSpace(m2[0][2])
				propertyNameRaw := strings.TrimSpace(m2[0][1])
				m3 := rxCustom.FindAllStringSubmatch(propertyNameRaw, -1)
				if len(m3) > 0 {
					field.Property = strings.TrimSpace(m3[0][1])
					field.IsCustom = true
				} else {
					field.Property = propertyNameRaw
				}
			}
			if len(field.Property) == 0 {
				fmt.Println(lineVar)
				fmtutil.PrintJSON(field)
				panic("Z")
			}
			line.Fields = append(line.Fields, field)
		}
		lines = append(lines, line)
	}
	return lines
}

//score[Score],text(Why);company_name__c(Company Name),(rcAccountId__c[RC Account ID]);email[User email];directorySize[Number of users];brand[Brand];survey_id[Survey ID]
<commit_msg>rename HookData.CustomParams to HookData.CustomQueryParams<commit_after>package wootric

import (
	"fmt"
	"net/url"
	"regexp"
	"strings"

	"github.com/grokify/chathooks/src/config"
	"github.com/grokify/chathooks/src/handlers"
	"github.com/grokify/chathooks/src/models"
	cc "github.com/grokify/commonchat"
	"github.com/grokify/gotilla/fmt/fmtutil"
	"github.com/grokify/gotilla/html/htmlutil"
	"github.com/pkg/errors"

	log "github.com/sirupsen/logrus"
)

const (
	DisplayName      = "Wootric"
	HandlerKey       = "wootric"
	MessageDirection = "out"
	MessageBodyType  = models.URLEncodedRails // application/x-www-form-urlencoded

	WootricFormatVarResponse = "wootricFormatResponse"
	WootricFormatDefault     = `score[NPS Score],text[Why];firstName lastName[User name];email[User email];survey_id[Survey ID]`
)

func NewHandler() handlers.Handler {
	return handlers.Handler{
		Key:             HandlerKey,
		MessageBodyType: MessageBodyType,
		Normalize:       Normalize}
}

func Normalize(cfg config.Configuration, hReq handlers.HandlerRequest) (cc.Message, error) {
	if hReq.QueryParams == nil {
		hReq.QueryParams = url.Values{}
	}
	ccMsg := cc.NewMessage()
	iconURL, err := cfg.GetAppIconURL(HandlerKey)
	if err == nil {
		ccMsg.IconURL = iconURL.String()
	}

	body, err := url.QueryUnescape(string(hReq.Body))
	if err != nil {
		return ccMsg, errors.Wrap(err, "wootric.Normalize")
	}
	src, err := ParseQueryString(body)
	if err != nil {
		return ccMsg, err
	}
	log.Info("WOOTRIC_BODY: " + string(hReq.Body))

	ccMsg.Activity = src.Activity()

	fmtutil.PrintJSON(hReq.QueryParams)

	if src.IsResponse() {
		responseFormat := WootricFormatDefault
		tryFormat := strings.TrimSpace(hReq.QueryParams.Get(WootricFormatVarResponse))
		if len(tryFormat) > 0 {
			responseFormat = tryFormat
		}
		/*
			if tryFormat, ok := hReq.Params[WootricFormatVarResponse]; ok {
				tryFormat = strings.TrimSpace(tryFormat)
				if len(tryFormat) > 0 {
					responseFormat = tryFormat
					log.Info("GOOT_LAYOUT")
				}
			}*/
		fmt.Printf("LAYOUT: [%v]\n", responseFormat)
		attachment := cc.NewAttachment()
		lines := ParseFields(responseFormat)
		fmtutil.PrintJSON(lines)

		scoreInt64, err := src.Response.Score.Int64()
		if err == nil {
			if scoreInt64 >= 9 {
				attachment.Color = htmlutil.Color2GreenHex
			} else if scoreInt64 >= 7 {
				attachment.Color = htmlutil.Color2YellowHex
			} else {
				attachment.Color = htmlutil.Color2RedHex
			}
		}

		for _, line := range lines {
			numFields := len(line.Fields)
			if numFields == 0 {
				continue
			}
			isShort := false
			if numFields > 0 {
				isShort = true
			}
			/*
				isShort := true
				if numFields == 1 {
					isShort = false
				}*/

			for _, field := range line.Fields {
				if field.Property == "score" {
					fmtutil.PrintJSON(src.Response)
					val := strings.TrimSpace(src.Response.Score.String())
					attachment.AddField(cc.Field{
						Title: field.Display, Short: isShort, Value: val})
				} else if field.Property == "text" {
					attachment.AddField(cc.Field{
						Title: field.Display, Short: isShort, Value: src.Response.Text})
				} else if field.Property == "email" {
					attachment.AddField(cc.Field{
						Title: field.Display,
						Value: src.Response.Email})
				} else if field.Property == "survey_id" {
					attachment.AddField(cc.Field{
						Title: field.Display,
						Value: src.Response.SurveyID})
				} else if field.IsCustom {
					val := ""
					if src.Response.EndUserProperties != nil {
						if try, ok := src.Response.EndUserProperties[field.Property]; ok {
							val = try
						}
					}
					attachment.AddField(cc.Field{
						Title: field.Display,
						Value: val})
				}
			}
		}
		if len(attachment.Fields) > 0 {
			ccMsg.AddAttachment(attachment)
		}
	}

	return ccMsg, nil
}

type Line struct {
	Fields []Field
}

type Field struct {
	Property  string
	Display   string
	IsCustom  bool
	UseParens bool
}

var (
	rxParens    = regexp.MustCompile(`^\((.*)\)$`)
	rxBrackets  = regexp.MustCompile(`^(.*)\[(.*)\]$`)
	rxCustom    = regexp.MustCompile(`^_(.*)$`)
	rxCustomOld = regexp.MustCompile(`^(.*)__c$`)
)

func ParseFields(fields string) []Line {
	lines := []Line{}
	parts := strings.Split(strings.TrimSpace(fields), ";")
	// Lines
	for _, part := range parts {
		line := Line{Fields: []Field{}}
		lineRaw := strings.TrimSpace(part)
		lineVars := strings.Split(lineRaw, ",")
		// Line Vars
		for _, lineVar := range lineVars {
			lineVar = strings.TrimSpace(lineVar)
			if len(lineVar) == 0 {
				continue
			}
			field := Field{}
			// Use parens
			m1 := rxParens.FindAllStringSubmatch(lineVar, -1)
			if len(m1) > 0 {
				field.UseParens = true
				lineVar = m1[0][1]
			}
			// Brackets
			m2 := rxBrackets.FindAllStringSubmatch(lineVar, -1)
			if len(m2) > 0 {
				field.Display = strings.TrimSpace(m2[0][2])
				propertyNameRaw := strings.TrimSpace(m2[0][1])
				m3 := rxCustom.FindAllStringSubmatch(propertyNameRaw, -1)
				if len(m3) > 0 {
					field.Property = strings.TrimSpace(m3[0][1])
					field.IsCustom = true
				} else {
					field.Property = propertyNameRaw
				}
			}
			if len(field.Property) == 0 {
				fmt.Println(lineVar)
				fmtutil.PrintJSON(field)
				panic("Z")
			}
			line.Fields = append(line.Fields, field)
		}
		lines = append(lines, line)
	}
	return lines
}

//score[Score],text(Why);company_name__c(Company Name),(rcAccountId__c[RC Account ID]);email[User email];directorySize[Number of users];brand[Brand];survey_id[Survey ID]
<|endoftext|>