func buildRateLimitZones(input interface{}) []string {
	zones := sets.String{}
	servers, ok := input.([]*ingress.Server)
	if !ok {
		glog.Errorf("expected a '[]*ingress.Server' type but %T was returned", input)
		return zones.List()
	}
	for _, server := range servers {
		for _, loc := range server.Locations {
			if loc.RateLimit.Connections.Limit > 0 {
				zone := fmt.Sprintf("limit_conn_zone $limit_%s zone=%v:%vm;",
					loc.RateLimit.ID,
					loc.RateLimit.Connections.Name,
					loc.RateLimit.Connections.SharedSize)
				if !zones.Has(zone) {
					zones.Insert(zone)
				}
			}
			if loc.RateLimit.RPM.Limit > 0 {
				zone := fmt.Sprintf("limit_req_zone $limit_%s zone=%v:%vm rate=%vr/m;",
					loc.RateLimit.ID,
					loc.RateLimit.RPM.Name,
					loc.RateLimit.RPM.SharedSize,
					loc.RateLimit.RPM.Limit)
				if !zones.Has(zone) {
					zones.Insert(zone)
				}
			}
			if loc.RateLimit.RPS.Limit > 0 {
				zone := fmt.Sprintf("limit_req_zone $limit_%s zone=%v:%vm rate=%vr/s;",
					loc.RateLimit.ID,
					loc.RateLimit.RPS.Name,
					loc.RateLimit.RPS.SharedSize,
					loc.RateLimit.RPS.Limit)
				if !zones.Has(zone) {
					zones.Insert(zone)
				}
			}
		}
	}
	return zones.List()
}
TODO: Needs Unit Tests
buildRateLimitZones produces an array of limit_conn_zone in order to allow
rate limiting of request. Each Ingress rule could have up to three zones, one
for connection limit by IP address, one for limiting requests per minute, and
one for limiting requests per second.
package template
import (
	"bytes"
	"encoding/base64"
	"encoding/json"
	"fmt"
	"math/rand"
	"net"
	"net/url"
	"os"
	"os/exec"
	"strconv"
	"strings"
	text_template "text/template"
	"time"
	"github.com/golang/glog"
	"github.com/pkg/errors"
	"github.com/NCCloud/fluid/internal/file"
	"github.com/NCCloud/fluid/internal/ingress"
	"github.com/NCCloud/fluid/internal/ingress/annotations/ratelimit"
	"github.com/NCCloud/fluid/internal/ingress/controller/config"
	ing_net "github.com/NCCloud/fluid/internal/net"
	extensions "k8s.io/api/extensions/v1beta1"
	"k8s.io/apimachinery/pkg/util/sets"
)
const (
	slash         = "/"
	nonIdempotent = "non_idempotent"
	defBufferSize = 65535
)
type Template struct {
	tmpl *text_template.Template
	bp *BufferPool
}
func NewTemplate(file string, fs file.Filesystem) (*Template, error) {
	data, err := fs.ReadFile(file)
	if err != nil {
		return nil, errors.Wrapf(err, "unexpected error reading template %v", file)
	}
	tmpl, err := text_template.New("nginx.tmpl").Funcs(funcMap).Parse(string(data))
	if err != nil {
		return nil, err
	}
	return &Template{
		tmpl: tmpl,
		bp:   NewBufferPool(defBufferSize),
	}, nil
}
func (t *Template) Write(conf config.TemplateConfig) ([]byte, error) {
	tmplBuf := t.bp.Get()
	defer t.bp.Put(tmplBuf)
	outCmdBuf := t.bp.Get()
	defer t.bp.Put(outCmdBuf)
	if glog.V(3) {
		b, err := json.Marshal(conf)
		if err != nil {
			glog.Errorf("unexpected error: %v", err)
		}
		glog.Infof("NGINX configuration: %v", string(b))
	}
	err := t.tmpl.Execute(tmplBuf, conf)
	if err != nil {
		return nil, err
	}
	cmd := exec.Command("/ingress-controller/clean-nginx-conf.sh")
	cmd.Stdin = tmplBuf
	cmd.Stdout = outCmdBuf
	if err := cmd.Run(); err != nil {
		glog.Warningf("unexpected error cleaning template: %v", err)
		return tmplBuf.Bytes(), nil
	}
	return outCmdBuf.Bytes(), nil
}
var (
	funcMap = text_template.FuncMap{
		"empty": func(input interface{}) bool {
			check, ok := input.(string)
			if ok {
				return len(check) == 0
			}
			return true
		},
		"buildLocation":            buildLocation,
		"buildAuthLocation":        buildAuthLocation,
		"buildAuthResponseHeaders": buildAuthResponseHeaders,
		"filterRateLimits":         filterRateLimits,
		"buildRateLimitZones":      buildRateLimitZones,
		"buildRateLimit":           buildRateLimit,
		"buildResolvers":           buildResolvers,
		"buildUpstreamName":        buildUpstreamName,
		"isLocationInLocationList": isLocationInLocationList,
		"isLocationAllowed":        isLocationAllowed,
		"buildLogFormatUpstream":   buildLogFormatUpstream,
		"buildDenyVariable":        buildDenyVariable,
		"getenv":                   os.Getenv,
		"contains":                 strings.Contains,
		"hasPrefix":                strings.HasPrefix,
		"hasSuffix":                strings.HasSuffix,
		"toUpper":                  strings.ToUpper,
		"toLower":                  strings.ToLower,
		"formatIP":                 formatIP,
		"buildNextUpstream":        buildNextUpstream,
		"getIngressInformation":    getIngressInformation,
		"serverConfig": func(all config.TemplateConfig, server *ingress.Server) interface{} {
			return struct{ First, Second interface{} }{all, server}
		},
		"isValidClientBodyBufferSize": isValidClientBodyBufferSize,
		"buildForwardedFor":           buildForwardedFor,
		"buildAuthSignURL":            buildAuthSignURL,
		"buildOpentracingLoad":        buildOpentracingLoad,
		"buildOpentracing":            buildOpentracing,
	}
)
func formatIP(input string) string {
	ip := net.ParseIP(input)
	if ip == nil {
		return input
	}
	if v4 := ip.To4(); v4 != nil {
		return input
	}
	return fmt.Sprintf("[%s]", input)
}
func buildResolvers(res interface{}, disableIpv6 interface{}) string {
	nss, ok := res.([]net.IP)
	if !ok {
		glog.Errorf("expected a '[]net.IP' type but %T was returned", res)
		return ""
	}
	no6, ok := disableIpv6.(bool)
	if !ok {
		glog.Errorf("expected a 'bool' type but %T was returned", disableIpv6)
		return ""
	}
	if len(nss) == 0 {
		return ""
	}
	r := []string{"resolver"}
	for _, ns := range nss {
		if ing_net.IsIPV6(ns) {
			if no6 {
				continue
			}
			r = append(r, fmt.Sprintf("[%v]", ns))
		} else {
			r = append(r, fmt.Sprintf("%v", ns))
		}
	}
	r = append(r, "valid=30s")
	if no6 {
		r = append(r, "ipv6=off")
	}
	return strings.Join(r, " ") + ";"
}
func buildLocation(input interface{}) string {
	location, ok := input.(*ingress.Location)
	if !ok {
		glog.Errorf("expected an '*ingress.Location' type but %T was returned", input)
		return slash
	}
	path := location.Path
	if len(location.Rewrite.Target) > 0 && location.Rewrite.Target != path {
		if path == slash {
			return fmt.Sprintf("~* %s", path)
		}
		baseuri := `(?<baseuri>.*)`
		if !strings.HasSuffix(path, slash) {
			baseuri = fmt.Sprintf(`\/?%s`, baseuri)
		}
		return fmt.Sprintf(`~* ^%s%s`, path, baseuri)
	}
	return path
}
func buildAuthLocation(input interface{}) string {
	location, ok := input.(*ingress.Location)
	if !ok {
		glog.Errorf("expected an '*ingress.Location' type but %T was returned", input)
		return ""
	}
	if location.ExternalAuth.URL == "" {
		return ""
	}
	str := base64.URLEncoding.EncodeToString([]byte(location.Path))
	str = strings.Replace(str, "=", "", -1)
	return fmt.Sprintf("/_external-auth-%v", str)
}
func buildAuthResponseHeaders(input interface{}) []string {
	location, ok := input.(*ingress.Location)
	res := []string{}
	if !ok {
		glog.Errorf("expected an '*ingress.Location' type but %T was returned", input)
		return res
	}
	if len(location.ExternalAuth.ResponseHeaders) == 0 {
		return res
	}
	for i, h := range location.ExternalAuth.ResponseHeaders {
		hvar := strings.ToLower(h)
		hvar = strings.NewReplacer("-", "_").Replace(hvar)
		res = append(res, fmt.Sprintf("auth_request_set $authHeader%v $upstream_http_%v;", i, hvar))
		res = append(res, fmt.Sprintf("proxy_set_header '%v' $authHeader%v;", h, i))
	}
	return res
}
func buildLogFormatUpstream(input interface{}) string {
	cfg, ok := input.(config.Configuration)
	if !ok {
		glog.Errorf("expected a 'config.Configuration' type but %T was returned", input)
		return ""
	}
	return cfg.BuildLogFormatUpstream()
}
func filterRateLimits(input interface{}) []ratelimit.Config {
	ratelimits := []ratelimit.Config{}
	found := sets.String{}
	servers, ok := input.([]*ingress.Server)
	if !ok {
		glog.Errorf("expected a '[]ratelimit.RateLimit' type but %T was returned", input)
		return ratelimits
	}
	for _, server := range servers {
		for _, loc := range server.Locations {
			if loc.RateLimit.ID != "" && !found.Has(loc.RateLimit.ID) {
				found.Insert(loc.RateLimit.ID)
				ratelimits = append(ratelimits, loc.RateLimit)
			}
		}
	}
	return ratelimits
}