<commit_before>/*
 *
 * k6 - a next-generation load testing tool
 * Copyright (C) 2019 Load Impact
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

package httpext

import (
	"bytes"
	"compress/gzip"
	"compress/zlib"
	"context"
	"fmt"
	"io"
	"io/ioutil"
	"net"
	"net/http"
	"net/http/cookiejar"
	"net/http/httputil"
	"net/url"
	"strconv"
	"strings"
	"time"

	ntlmssp "github.com/Azure/go-ntlmssp"
	digest "github.com/Soontao/goHttpDigestClient"
	"github.com/loadimpact/k6/lib"
	"github.com/loadimpact/k6/stats"
	log "github.com/sirupsen/logrus"
	null "gopkg.in/guregu/null.v3"
)

// HTTPRequestCookie is a representation of a cookie used for request objects
type HTTPRequestCookie struct {
	Name, Value string
	Replace     bool
}

// A URL wraps net.URL, and preserves the template (if any) the URL was constructed from.
type URL struct {
	u    *url.URL
	Name string // http://example.com/thing/${}/
	URL  string // http://example.com/thing/1234/
}

// NewURL returns a new URL for the provided url and name. The error is returned if the url provided
// can't be parsed
func NewURL(urlString, name string) (URL, error) {
	u, err := url.Parse(urlString)
	return URL{u: u, Name: name, URL: urlString}, err
}

// GetURL returns the internal url.URL
func (u URL) GetURL() *url.URL {
	return u.u
}

// Request represent an http request
type Request struct {
	Method  string                          `json:"method"`
	URL     string                          `json:"url"`
	Headers map[string][]string             `json:"headers"`
	Body    string                          `json:"body"`
	Cookies map[string][]*HTTPRequestCookie `json:"cookies"`
}

// ParsedHTTPRequest a represantion of a request after it has been parsed from a user script
type ParsedHTTPRequest struct {
	URL          *URL
	Body         *bytes.Buffer
	Req          *http.Request
	Timeout      time.Duration
	Auth         string
	Throw        bool
	ResponseType ResponseType
	Redirects    null.Int
	ActiveJar    *cookiejar.Jar
	Cookies      map[string]*HTTPRequestCookie
	Tags         map[string]string
}

func stdCookiesToHTTPRequestCookies(cookies []*http.Cookie) map[string][]*HTTPRequestCookie {
	var result = make(map[string][]*HTTPRequestCookie, len(cookies))
	for _, cookie := range cookies {
		result[cookie.Name] = append(result[cookie.Name],
			&HTTPRequestCookie{Name: cookie.Name, Value: cookie.Value})
	}
	return result
}

// MakeRequest makes http request for tor the provided ParsedHTTPRequest
//TODO break this function up
//nolint: gocyclo
func MakeRequest(ctx context.Context, preq *ParsedHTTPRequest) (*Response, error) {
	state := lib.GetState(ctx)

	respReq := &Request{
		Method:  preq.Req.Method,
		URL:     preq.Req.URL.String(),
		Cookies: stdCookiesToHTTPRequestCookies(preq.Req.Cookies()),
		Headers: preq.Req.Header,
	}
	if preq.Body != nil {
		respReq.Body = preq.Body.String()
	}

	tags := state.Options.RunTags.CloneTags()
	for k, v := range preq.Tags {
		tags[k] = v
	}

	if state.Options.SystemTags["method"] {
		tags["method"] = preq.Req.Method
	}
	if state.Options.SystemTags["url"] {
		tags["url"] = preq.URL.URL
	}

	// Only set the name system tag if the user didn't explicitly set it beforehand
	if _, ok := tags["name"]; !ok && state.Options.SystemTags["name"] {
		tags["name"] = preq.URL.Name
	}
	if state.Options.SystemTags["group"] {
		tags["group"] = state.Group.Path
	}
	if state.Options.SystemTags["vu"] {
		tags["vu"] = strconv.FormatInt(state.Vu, 10)
	}
	if state.Options.SystemTags["iter"] {
		tags["iter"] = strconv.FormatInt(state.Iteration, 10)
	}

	// Check rate limit *after* we've prepared a request; no need to wait with that part.
	if rpsLimit := state.RPSLimit; rpsLimit != nil {
		if err := rpsLimit.Wait(ctx); err != nil {
			return nil, err
		}
	}

	tracerTransport := newTransport(state.Transport, state.Samples, &state.Options, tags)
	var transport http.RoundTripper = tracerTransport
	if preq.Auth == "ntlm" {
		transport = ntlmssp.Negotiator{
			RoundTripper: tracerTransport,
		}
	}

	resp := &Response{ctx: ctx, URL: preq.URL.URL, Request: *respReq}
	client := http.Client{
		Transport: transport,
		Timeout:   preq.Timeout,
		CheckRedirect: func(req *http.Request, via []*http.Request) error {
			debugResponse(state, req.Response, "RedirectResponse")

			// Update active jar with cookies found in "Set-Cookie" header(s) of redirect response
			if preq.ActiveJar != nil {
				if respCookies := req.Response.Cookies(); len(respCookies) > 0 {
					preq.ActiveJar.SetCookies(req.URL, respCookies)
				}
				req.Header.Del("Cookie")
				SetRequestCookies(req, preq.ActiveJar, preq.Cookies)
			}

			if l := len(via); int64(l) > preq.Redirects.Int64 {
				if !preq.Redirects.Valid {
					url := req.URL
					if l > 0 {
						url = via[0].URL
					}
					state.Logger.WithFields(log.Fields{"url": url.String()}).Warnf(
						"Stopped after %d redirects and returned the redirection; pass { redirects: n }"+
							" in request params or set global maxRedirects to silence this", l)
				}
				return http.ErrUseLastResponse
			}
			debugRequest(state, req, "RedirectRequest")
			return nil
		},
	}

	// if digest authentication option is passed, make an initial request
	// to get the authentication params to compute the authorization header
	if preq.Auth == "digest" {
		username := preq.URL.u.User.Username()
		password, _ := preq.URL.u.User.Password()

		// removing user from URL to avoid sending the authorization header fo basic auth
		preq.Req.URL.User = nil

		debugRequest(state, preq.Req, "DigestRequest")
		res, err := client.Do(preq.Req.WithContext(ctx))
		debugRequest(state, preq.Req, "DigestResponse")
		resp.Error = tracerTransport.errorMsg
		resp.ErrorCode = int(tracerTransport.errorCode)
		if err != nil {
			// Do *not* log errors about the contex being cancelled.
			select {
			case <-ctx.Done():
			default:
				state.Logger.WithField("error", res).Warn("Digest request failed")
			}

			if preq.Throw || resp.Error == "" {
				return nil, err
			}

			return resp, nil
		}

		if res.StatusCode == http.StatusUnauthorized {
			body := ""
			if b, err := ioutil.ReadAll(res.Body); err == nil {
				body = string(b)
			}

			challenge := digest.GetChallengeFromHeader(&res.Header)
			challenge.ComputeResponse(preq.Req.Method, preq.Req.URL.RequestURI(), body, username, password)
			authorization := challenge.ToAuthorizationStr()
			preq.Req.Header.Set(digest.KEY_AUTHORIZATION, authorization)
		}
	}

	debugRequest(state, preq.Req, "Request")
	res, resErr := client.Do(preq.Req.WithContext(ctx))
	debugResponse(state, res, "Response")
	resp.Error = tracerTransport.errorMsg
	resp.ErrorCode = int(tracerTransport.errorCode)
	if resErr == nil && res != nil {
		switch res.Header.Get("Content-Encoding") {
		case "deflate":
			res.Body, resErr = zlib.NewReader(res.Body)
		case "gzip":
			res.Body, resErr = gzip.NewReader(res.Body)
		}
	}
	if resErr == nil && res != nil {
		if preq.ResponseType == ResponseTypeNone {
			_, err := io.Copy(ioutil.Discard, res.Body)
			if err != nil && err != io.EOF {
				resErr = err
			}
			resp.Body = nil
		} else {
			// Binary or string
			buf := state.BPool.Get()
			buf.Reset()
			defer state.BPool.Put(buf)
			_, err := io.Copy(buf, res.Body)
			if err != nil && err != io.EOF {
				resErr = err
			}

			switch preq.ResponseType {
			case ResponseTypeText:
				resp.Body = buf.String()
			case ResponseTypeBinary:
				resp.Body = buf.Bytes()
			default:
				resErr = fmt.Errorf("unknown responseType %s", preq.ResponseType)
			}
		}
		_ = res.Body.Close()
	}

	trail := tracerTransport.GetTrail()

	if trail.ConnRemoteAddr != nil {
		remoteHost, remotePortStr, _ := net.SplitHostPort(trail.ConnRemoteAddr.String())
		remotePort, _ := strconv.Atoi(remotePortStr)
		resp.RemoteIP = remoteHost
		resp.RemotePort = remotePort
	}
	resp.Timings = ResponseTimings{
		Duration:       stats.D(trail.Duration),
		Blocked:        stats.D(trail.Blocked),
		Connecting:     stats.D(trail.Connecting),
		TLSHandshaking: stats.D(trail.TLSHandshaking),
		Sending:        stats.D(trail.Sending),
		Waiting:        stats.D(trail.Waiting),
		Receiving:      stats.D(trail.Receiving),
	}

	if resErr == nil {
		if preq.ActiveJar != nil {
			if rc := res.Cookies(); len(rc) > 0 {
				preq.ActiveJar.SetCookies(res.Request.URL, rc)
			}
		}

		resp.URL = res.Request.URL.String()
		resp.Status = res.StatusCode
		resp.Proto = res.Proto

		if res.TLS != nil {
			resp.setTLSInfo(res.TLS)
		}

		resp.Headers = make(map[string]string, len(res.Header))
		for k, vs := range res.Header {
			resp.Headers[k] = strings.Join(vs, ", ")
		}

		resCookies := res.Cookies()
		resp.Cookies = make(map[string][]*HTTPCookie, len(resCookies))
		for _, c := range resCookies {
			resp.Cookies[c.Name] = append(resp.Cookies[c.Name], &HTTPCookie{
				Name:     c.Name,
				Value:    c.Value,
				Domain:   c.Domain,
				Path:     c.Path,
				HTTPOnly: c.HttpOnly,
				Secure:   c.Secure,
				MaxAge:   c.MaxAge,
				Expires:  c.Expires.UnixNano() / 1000000,
			})
		}
	}

	if resErr != nil {
		// Do *not* log errors about the contex being cancelled.
		select {
		case <-ctx.Done():
		default:
			state.Logger.WithField("error", resErr).Warn("Request Failed")
		}

		if preq.Throw || resp.Error == "" {
			return nil, resErr
		}
	}

	return resp, nil
}

// SetRequestCookies sets the cookies of the requests getting those cookies both from the jar and
// from the reqCookies map. The Replace field of the HTTPRequestCookie will be taken into account
func SetRequestCookies(req *http.Request, jar *cookiejar.Jar, reqCookies map[string]*HTTPRequestCookie) {
	var replacedCookies = make(map[string]struct{})
	for key, reqCookie := range reqCookies {
		req.AddCookie(&http.Cookie{Name: key, Value: reqCookie.Value})
		if reqCookie.Replace {
			replacedCookies[key] = struct{}{}
		}
	}
	for _, c := range jar.Cookies(req.URL) {
		if _, ok := replacedCookies[c.Name]; !ok {
			req.AddCookie(&http.Cookie{Name: c.Name, Value: c.Value})
		}
	}
}

func debugRequest(state *lib.State, req *http.Request, description string) {
	if state.Options.HttpDebug.String != "" {
		dump, err := httputil.DumpRequestOut(req, state.Options.HttpDebug.String == "full")
		if err != nil {
			log.Fatal(err)
		}
		logDump(description, dump)
	}
}
func logDump(description string, dump []byte) {
	fmt.Printf("%s:\n%s\n", description, dump)
}
<commit_msg>Add comment clarifing how we find out if an error is from the transport<commit_after>/*
 *
 * k6 - a next-generation load testing tool
 * Copyright (C) 2019 Load Impact
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

package httpext

import (
	"bytes"
	"compress/gzip"
	"compress/zlib"
	"context"
	"fmt"
	"io"
	"io/ioutil"
	"net"
	"net/http"
	"net/http/cookiejar"
	"net/http/httputil"
	"net/url"
	"strconv"
	"strings"
	"time"

	ntlmssp "github.com/Azure/go-ntlmssp"
	digest "github.com/Soontao/goHttpDigestClient"
	"github.com/loadimpact/k6/lib"
	"github.com/loadimpact/k6/stats"
	log "github.com/sirupsen/logrus"
	null "gopkg.in/guregu/null.v3"
)

// HTTPRequestCookie is a representation of a cookie used for request objects
type HTTPRequestCookie struct {
	Name, Value string
	Replace     bool
}

// A URL wraps net.URL, and preserves the template (if any) the URL was constructed from.
type URL struct {
	u    *url.URL
	Name string // http://example.com/thing/${}/
	URL  string // http://example.com/thing/1234/
}

// NewURL returns a new URL for the provided url and name. The error is returned if the url provided
// can't be parsed
func NewURL(urlString, name string) (URL, error) {
	u, err := url.Parse(urlString)
	return URL{u: u, Name: name, URL: urlString}, err
}

// GetURL returns the internal url.URL
func (u URL) GetURL() *url.URL {
	return u.u
}

// Request represent an http request
type Request struct {
	Method  string                          `json:"method"`
	URL     string                          `json:"url"`
	Headers map[string][]string             `json:"headers"`
	Body    string                          `json:"body"`
	Cookies map[string][]*HTTPRequestCookie `json:"cookies"`
}

// ParsedHTTPRequest a represantion of a request after it has been parsed from a user script
type ParsedHTTPRequest struct {
	URL          *URL
	Body         *bytes.Buffer
	Req          *http.Request
	Timeout      time.Duration
	Auth         string
	Throw        bool
	ResponseType ResponseType
	Redirects    null.Int
	ActiveJar    *cookiejar.Jar
	Cookies      map[string]*HTTPRequestCookie
	Tags         map[string]string
}

func stdCookiesToHTTPRequestCookies(cookies []*http.Cookie) map[string][]*HTTPRequestCookie {
	var result = make(map[string][]*HTTPRequestCookie, len(cookies))
	for _, cookie := range cookies {
		result[cookie.Name] = append(result[cookie.Name],
			&HTTPRequestCookie{Name: cookie.Name, Value: cookie.Value})
	}
	return result
}

// MakeRequest makes http request for tor the provided ParsedHTTPRequest
//TODO break this function up
//nolint: gocyclo
func MakeRequest(ctx context.Context, preq *ParsedHTTPRequest) (*Response, error) {
	state := lib.GetState(ctx)

	respReq := &Request{
		Method:  preq.Req.Method,
		URL:     preq.Req.URL.String(),
		Cookies: stdCookiesToHTTPRequestCookies(preq.Req.Cookies()),
		Headers: preq.Req.Header,
	}
	if preq.Body != nil {
		respReq.Body = preq.Body.String()
	}

	tags := state.Options.RunTags.CloneTags()
	for k, v := range preq.Tags {
		tags[k] = v
	}

	if state.Options.SystemTags["method"] {
		tags["method"] = preq.Req.Method
	}
	if state.Options.SystemTags["url"] {
		tags["url"] = preq.URL.URL
	}

	// Only set the name system tag if the user didn't explicitly set it beforehand
	if _, ok := tags["name"]; !ok && state.Options.SystemTags["name"] {
		tags["name"] = preq.URL.Name
	}
	if state.Options.SystemTags["group"] {
		tags["group"] = state.Group.Path
	}
	if state.Options.SystemTags["vu"] {
		tags["vu"] = strconv.FormatInt(state.Vu, 10)
	}
	if state.Options.SystemTags["iter"] {
		tags["iter"] = strconv.FormatInt(state.Iteration, 10)
	}

	// Check rate limit *after* we've prepared a request; no need to wait with that part.
	if rpsLimit := state.RPSLimit; rpsLimit != nil {
		if err := rpsLimit.Wait(ctx); err != nil {
			return nil, err
		}
	}

	tracerTransport := newTransport(state.Transport, state.Samples, &state.Options, tags)
	var transport http.RoundTripper = tracerTransport
	if preq.Auth == "ntlm" {
		transport = ntlmssp.Negotiator{
			RoundTripper: tracerTransport,
		}
	}

	resp := &Response{ctx: ctx, URL: preq.URL.URL, Request: *respReq}
	client := http.Client{
		Transport: transport,
		Timeout:   preq.Timeout,
		CheckRedirect: func(req *http.Request, via []*http.Request) error {
			debugResponse(state, req.Response, "RedirectResponse")

			// Update active jar with cookies found in "Set-Cookie" header(s) of redirect response
			if preq.ActiveJar != nil {
				if respCookies := req.Response.Cookies(); len(respCookies) > 0 {
					preq.ActiveJar.SetCookies(req.URL, respCookies)
				}
				req.Header.Del("Cookie")
				SetRequestCookies(req, preq.ActiveJar, preq.Cookies)
			}

			if l := len(via); int64(l) > preq.Redirects.Int64 {
				if !preq.Redirects.Valid {
					url := req.URL
					if l > 0 {
						url = via[0].URL
					}
					state.Logger.WithFields(log.Fields{"url": url.String()}).Warnf(
						"Stopped after %d redirects and returned the redirection; pass { redirects: n }"+
							" in request params or set global maxRedirects to silence this", l)
				}
				return http.ErrUseLastResponse
			}
			debugRequest(state, req, "RedirectRequest")
			return nil
		},
	}

	// if digest authentication option is passed, make an initial request
	// to get the authentication params to compute the authorization header
	if preq.Auth == "digest" {
		username := preq.URL.u.User.Username()
		password, _ := preq.URL.u.User.Password()

		// removing user from URL to avoid sending the authorization header fo basic auth
		preq.Req.URL.User = nil

		debugRequest(state, preq.Req, "DigestRequest")
		res, err := client.Do(preq.Req.WithContext(ctx))
		debugRequest(state, preq.Req, "DigestResponse")
		resp.Error = tracerTransport.errorMsg
		resp.ErrorCode = int(tracerTransport.errorCode)
		if err != nil {
			// Do *not* log errors about the contex being cancelled.
			select {
			case <-ctx.Done():
			default:
				state.Logger.WithField("error", res).Warn("Digest request failed")
			}

			// In case we have an error but resp.Error is not set it means the error is not from
			// the transport. For all such errors currently we just return them as if throw is true
			if preq.Throw || resp.Error == "" {
				return nil, err
			}

			return resp, nil
		}

		if res.StatusCode == http.StatusUnauthorized {
			body := ""
			if b, err := ioutil.ReadAll(res.Body); err == nil {
				body = string(b)
			}

			challenge := digest.GetChallengeFromHeader(&res.Header)
			challenge.ComputeResponse(preq.Req.Method, preq.Req.URL.RequestURI(), body, username, password)
			authorization := challenge.ToAuthorizationStr()
			preq.Req.Header.Set(digest.KEY_AUTHORIZATION, authorization)
		}
	}

	debugRequest(state, preq.Req, "Request")
	res, resErr := client.Do(preq.Req.WithContext(ctx))
	debugResponse(state, res, "Response")
	resp.Error = tracerTransport.errorMsg
	resp.ErrorCode = int(tracerTransport.errorCode)
	if resErr == nil && res != nil {
		switch res.Header.Get("Content-Encoding") {
		case "deflate":
			res.Body, resErr = zlib.NewReader(res.Body)
		case "gzip":
			res.Body, resErr = gzip.NewReader(res.Body)
		}
	}
	if resErr == nil && res != nil {
		if preq.ResponseType == ResponseTypeNone {
			_, err := io.Copy(ioutil.Discard, res.Body)
			if err != nil && err != io.EOF {
				resErr = err
			}
			resp.Body = nil
		} else {
			// Binary or string
			buf := state.BPool.Get()
			buf.Reset()
			defer state.BPool.Put(buf)
			_, err := io.Copy(buf, res.Body)
			if err != nil && err != io.EOF {
				resErr = err
			}

			switch preq.ResponseType {
			case ResponseTypeText:
				resp.Body = buf.String()
			case ResponseTypeBinary:
				resp.Body = buf.Bytes()
			default:
				resErr = fmt.Errorf("unknown responseType %s", preq.ResponseType)
			}
		}
		_ = res.Body.Close()
	}

	trail := tracerTransport.GetTrail()

	if trail.ConnRemoteAddr != nil {
		remoteHost, remotePortStr, _ := net.SplitHostPort(trail.ConnRemoteAddr.String())
		remotePort, _ := strconv.Atoi(remotePortStr)
		resp.RemoteIP = remoteHost
		resp.RemotePort = remotePort
	}
	resp.Timings = ResponseTimings{
		Duration:       stats.D(trail.Duration),
		Blocked:        stats.D(trail.Blocked),
		Connecting:     stats.D(trail.Connecting),
		TLSHandshaking: stats.D(trail.TLSHandshaking),
		Sending:        stats.D(trail.Sending),
		Waiting:        stats.D(trail.Waiting),
		Receiving:      stats.D(trail.Receiving),
	}

	if resErr == nil {
		if preq.ActiveJar != nil {
			if rc := res.Cookies(); len(rc) > 0 {
				preq.ActiveJar.SetCookies(res.Request.URL, rc)
			}
		}

		resp.URL = res.Request.URL.String()
		resp.Status = res.StatusCode
		resp.Proto = res.Proto

		if res.TLS != nil {
			resp.setTLSInfo(res.TLS)
		}

		resp.Headers = make(map[string]string, len(res.Header))
		for k, vs := range res.Header {
			resp.Headers[k] = strings.Join(vs, ", ")
		}

		resCookies := res.Cookies()
		resp.Cookies = make(map[string][]*HTTPCookie, len(resCookies))
		for _, c := range resCookies {
			resp.Cookies[c.Name] = append(resp.Cookies[c.Name], &HTTPCookie{
				Name:     c.Name,
				Value:    c.Value,
				Domain:   c.Domain,
				Path:     c.Path,
				HTTPOnly: c.HttpOnly,
				Secure:   c.Secure,
				MaxAge:   c.MaxAge,
				Expires:  c.Expires.UnixNano() / 1000000,
			})
		}
	}

	if resErr != nil {
		// Do *not* log errors about the contex being cancelled.
		select {
		case <-ctx.Done():
		default:
			state.Logger.WithField("error", resErr).Warn("Request Failed")
		}

		// In case we have an error but resp.Error is not set it means the error is not from
		// the transport. For all such errors currently we just return them as if throw is true
		if preq.Throw || resp.Error == "" {
			return nil, resErr
		}
	}

	return resp, nil
}

// SetRequestCookies sets the cookies of the requests getting those cookies both from the jar and
// from the reqCookies map. The Replace field of the HTTPRequestCookie will be taken into account
func SetRequestCookies(req *http.Request, jar *cookiejar.Jar, reqCookies map[string]*HTTPRequestCookie) {
	var replacedCookies = make(map[string]struct{})
	for key, reqCookie := range reqCookies {
		req.AddCookie(&http.Cookie{Name: key, Value: reqCookie.Value})
		if reqCookie.Replace {
			replacedCookies[key] = struct{}{}
		}
	}
	for _, c := range jar.Cookies(req.URL) {
		if _, ok := replacedCookies[c.Name]; !ok {
			req.AddCookie(&http.Cookie{Name: c.Name, Value: c.Value})
		}
	}
}

func debugRequest(state *lib.State, req *http.Request, description string) {
	if state.Options.HttpDebug.String != "" {
		dump, err := httputil.DumpRequestOut(req, state.Options.HttpDebug.String == "full")
		if err != nil {
			log.Fatal(err)
		}
		logDump(description, dump)
	}
}
func logDump(description string, dump []byte) {
	fmt.Printf("%s:\n%s\n", description, dump)
}
<|endoftext|>


<commit_before>package websocket

import (
	"github.com/Sirupsen/logrus"
	"github.com/gorilla/websocket"
	"github.com/pkg/errors"
	"github.com/verath/archipelago/lib/common"
	"github.com/verath/archipelago/lib/network"
	"net/http"
)

// The websocket.Upgrader used for all upgrades from http -> ws.
var wsUpgrader = websocket.Upgrader{
	// TODO: should we allow all origins?
	CheckOrigin: func(r *http.Request) bool { return true },
}

// The upgradeHandler is an http.Handler that attempts to upgrade
// handled connections to websocket connections. Once upgraded,
// the connection is wrapped in a client and posted to the
// registered ClientConnectListener.
type upgradeHandler struct {
	logEntry    *logrus.Entry
	connHandler network.ConnectionHandler
}

// Creates a new UpgradeHandler, notifying the connectListener for clients
// successfully created.
func NewUpgradeHandler(log *logrus.Logger, connListener network.ConnectionHandler) *upgradeHandler {
	return &upgradeHandler{
		logEntry:    common.ModuleLogEntry(log, "websocket/wsConnHandler"),
		connHandler: connListener,
	}
}

// ServeHTTP is called on each http request that matches our handler.
// We try to upgrade each such request to a websocket connection.
func (h *upgradeHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	wsConn, err := wsUpgrader.Upgrade(w, r, nil)
	if err != nil {
		h.logEntry.Warnf("Failed upgrading to websocket: %+v", err)
		return
	}
	go func() {
		if err := h.handleWSConn(wsConn); err != nil {
			h.logEntry.Errorf("Error handling ws connection: %+v", err)
		}
	}()
}

// handleWSConn wraps the gorilla websocket in our own wsConnection
// and forwards the connection to the connection handler.
func (h *upgradeHandler) handleWSConn(wsConn *websocket.Conn) error {
	conn, err := newWSConnection(wsConn)
	if err != nil {
		return errors.Wrap(err, "Failed creating WSConnection")
	}
	if err := h.connHandler.HandleConnection(conn); err != nil {
		return errors.Wrap(err, "Connection handler did not handle connection")
	}
	return nil
}
<commit_msg>Enables origin validation of ws connections<commit_after>package websocket

import (
	"github.com/Sirupsen/logrus"
	"github.com/gorilla/websocket"
	"github.com/pkg/errors"
	"github.com/verath/archipelago/lib/common"
	"github.com/verath/archipelago/lib/network"
	"net/http"
)

// The websocket.Upgrader used for all upgrades from http -> ws.
var wsUpgrader = websocket.Upgrader{}

// The upgradeHandler is an http.Handler that attempts to upgrade
// handled connections to websocket connections. Once upgraded,
// the connection is wrapped in a client and posted to the
// registered ClientConnectListener.
type upgradeHandler struct {
	logEntry    *logrus.Entry
	connHandler network.ConnectionHandler
}

// Creates a new UpgradeHandler, notifying the connectListener for clients
// successfully created.
func NewUpgradeHandler(log *logrus.Logger, connListener network.ConnectionHandler) *upgradeHandler {
	return &upgradeHandler{
		logEntry:    common.ModuleLogEntry(log, "websocket/wsConnHandler"),
		connHandler: connListener,
	}
}

// ServeHTTP is called on each http request that matches our handler.
// We try to upgrade each such request to a websocket connection.
func (h *upgradeHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	wsConn, err := wsUpgrader.Upgrade(w, r, nil)
	if err != nil {
		h.logEntry.Warnf("Failed upgrading to websocket: %+v", err)
		return
	}
	go func() {
		if err := h.handleWSConn(wsConn); err != nil {
			h.logEntry.Errorf("Error handling ws connection: %+v", err)
		}
	}()
}

// handleWSConn wraps the gorilla websocket in our own wsConnection
// and forwards the connection to the connection handler.
func (h *upgradeHandler) handleWSConn(wsConn *websocket.Conn) error {
	conn, err := newWSConnection(wsConn)
	if err != nil {
		return errors.Wrap(err, "Failed creating WSConnection")
	}
	if err := h.connHandler.HandleConnection(conn); err != nil {
		return errors.Wrap(err, "Connection handler did not handle connection")
	}
	return nil
}
<|endoftext|>


<commit_before>package paging

import "github.com/guregu/null"

// Pager is the paginator interface.
type Pager interface {
	Previous() null.String
	HasPrevious() bool
	Next() null.String
	HasNext() bool
	Page() (*Page, error)
}

// Page contains pagination information.
type Page struct {
	Limit    int64       `json:"limit"`
	Offset   int64       `json:"offset"`
	Count    int64       `json:"total_count"`
	Previous null.String `json:"previous"`
	Next     null.String `json:"next"`
}
<commit_msg>Remove pager.go.<commit_after><|endoftext|>


<commit_before>// Copyright 2010 The draw2d Authors. All rights reserved.
// created: 21/11/2010 by Laurent Le Goff

package draw2d

/*
import (
	"image/draw"
	"image"
	"freetype-go.googlecode.com/hg/freetype/raster"
)*/

const M = 1<<16 - 1

/*
type NRGBAPainter struct {
	// The image to compose onto.
	Image *image.NRGBA
	// The Porter-Duff composition operator.
	Op draw.Op
	// The 16-bit color to paint the spans.
	cr, cg, cb, ca uint32
}

// Paint satisfies the Painter interface by painting ss onto an image.RGBA.
func (r *NRGBAPainter) Paint(ss []raster.Span, done bool) {
	b := r.Image.Bounds()
	for _, s := range ss {
		if s.Y < b.Min.Y {
			continue
		}
		if s.Y >= b.Max.Y {
			return
		}
		if s.X0 < b.Min.X {
			s.X0 = b.Min.X
		}
		if s.X1 > b.Max.X {
			s.X1 = b.Max.X
		}
		if s.X0 >= s.X1 {
			continue
		}
		base := s.Y * r.Image.Stride
		p := r.Image.Pix[base+s.X0 : base+s.X1]
		// This code is duplicated from drawGlyphOver in $GOROOT/src/pkg/image/draw/draw.go.
		// TODO(nigeltao): Factor out common code into a utility function, once the compiler
		// can inline such function calls.
		ma := s.A >> 16
		if r.Op == draw.Over {
			for i, nrgba := range p {
				dr, dg, db, da := nrgba.
				a := M - (r.ca*ma)/M
				da = (da*a + r.ca*ma) / M
				if da != 0 {
					dr = minUint32(M, (dr*a+r.cr*ma)/da)
					dg = minUint32(M, (dg*a+r.cg*ma)/da)
					db = minUint32(M, (db*a+r.cb*ma)/da)
				} else {
					dr, dg, db = 0, 0, 0
				}
				p[i] = image.NRGBAColor{uint8(dr >> 8), uint8(dg >> 8), uint8(db >> 8), uint8(da >> 8)}
			}
		} else {
			for i, nrgba := range p {
				dr, dg, db, da := nrgba.RGBA()
				a := M - ma
				da = (da*a + r.ca*ma) / M
				if da != 0 {
					dr = minUint32(M, (dr*a+r.cr*ma)/da)
					dg = minUint32(M, (dg*a+r.cg*ma)/da)
					db = minUint32(M, (db*a+r.cb*ma)/da)
				} else {
					dr, dg, db = 0, 0, 0
				}
				p[i] = image.NRGBAColor{uint8(dr >> 8), uint8(dg >> 8), uint8(db >> 8), uint8(da >> 8)}
			}
		}
	}

}

// SetColor sets the color to paint the spans.
func (r *NRGBAPainter) SetColor(c image.Color) {
	r.cr, r.cg, r.cb, r.ca = c.RGBA()
}

// NewRGBAPainter creates a new RGBAPainter for the given image.
func NewNRGBAPainter(m *image.NRGBA) *NRGBAPainter {
	return &NRGBAPainter{Image: m}
}
*/
<commit_msg>remove unused file<commit_after><|endoftext|>


<commit_before>package main

import (
	"fmt"
	"html/template"
	"strings"
	"time"

	"github.com/sirupsen/logrus"

	"strconv"
)

type msgType int

const (
	msgPrivmsg msgType = iota + 1
	msgTimeout
)

type Message struct {
	typ msgType

	Time        time.Time         `json:"time"`
	Channel     string            `json:"channel"`
	Username    string            `json:"username"`
	DisplayName string            `json:"displayName"`
	UserType    string            `json:"userType"`
	Color       string            `json:"color"`
	Badges      map[string]int    `json:"badges"`
	Emotes      map[string]*Emote `json:"emotes"`
	Tags        map[string]string `json:"tags"`
	Text        string            `json:"text"`

	TextWithEmotes template.HTML `json:"-"`
	TimeStamp      string        `json:"-"`
}

type Emote struct {
	Name  string `json:"name"`
	ID    string `json:"id"`
	Type  string `json:"type"`
	Count int    `json:"count"`
}

func parseMessage(line string) *Message {
	if strings.Contains(line, "CLEARCHAT") {
		logrus.Debug(line)
	}
	if !strings.HasPrefix(line, "@") {
		return &Message{
			Text: line,
		}
	}
	spl := strings.SplitN(line, " :", 3)
	if len(spl) < 3 {
		return &Message{
			Text: line,
		}
	}
	tags, middle, text := spl[0], spl[1], spl[2]
	if strings.HasPrefix(text, "\u0001ACTION") {
		text = text[7 : len(text)-1]
	}
	msg := &Message{
		Time: time.Now(),
		Text: text,
		Tags: map[string]string{},
	}
	parseMiddle(msg, middle)
	parseTags(msg, tags[1:])
	if msg.typ == msgTimeout {
		msg.Username = "twitch"
		targetUser := msg.Text
		seconds, _ := strconv.Atoi(msg.Tags["ban-duration"])

		msg.Text = fmt.Sprintf("%s was timed out for %s: %s",
			targetUser,
			time.Duration(time.Duration(seconds)*time.Second),
			msg.Tags["ban-reason"])
	}
	return msg
}

func parseMiddle(msg *Message, middle string) {
	for i, c := range middle {
		if c == '!' {
			msg.Username = middle[:i]
			middle = middle[i:]
		}
	}
	start := -1
	for i, c := range middle {
		if c == ' ' {
			if start == -1 {
				start = i + 1
			} else {
				typ := middle[start:i]
				switch typ {
				case "PRIVMSG":
					msg.typ = msgPrivmsg
				case "CLEARCHAT":
					msg.typ = msgTimeout
				}
				middle = middle[i:]
			}
		}
	}
	for i, c := range middle {
		if c == '#' {
			msg.Channel = middle[i+1:]
		}
	}
}

/* @badges=broadcaster/1;color=#0B8E70;display-name=nuuls;emotes=;
id=0cabc0b9-bb6e-448c-9688-1b8ea654dc27;mod=1;room-id=100229878;
subscriber=0;tmi-sent-ts=1482614970705;turbo=0;user-id=100229878;
user-type=mod :nuuls!nuuls@nuuls.tmi.twitch.tv PRIVMSG #nuuls :NaM
*/

func parseTags(msg *Message, tagsRaw string) {
	tags := strings.Split(tagsRaw, ";")
	for _, tag := range tags {
		spl := strings.SplitN(tag, "=", 2)
		if len(spl) < 2 {
			return
		}
		value := strings.Replace(spl[1], "\\:", ";", -1)
		value = strings.Replace(value, "\\s", " ", -1)
		value = strings.Replace(value, "\\\\", "\\", -1)
		switch spl[0] {
		case "badges":
			msg.Badges = parseBadges(value)
		case "color":
			msg.Color = value
		case "display-name":
			msg.DisplayName = value
		case "emotes":
			msg.Emotes = parseTwitchEmotes(value, msg.Text)
		case "user-type":
			msg.UserType = value
		default:
			msg.Tags[spl[0]] = value
		}
	}
}

func parseBadges(badges string) map[string]int {
	m := map[string]int{}
	spl := strings.Split(badges, ",")
	for _, badge := range spl {
		s := strings.SplitN(badge, "/", 2)
		if len(s) < 2 {
			continue
		}
		n, _ := strconv.Atoi(s[1])
		m[s[0]] = n
	}
	return m
}

// 25:0-4,6-10,12-16/1902:18-22/88:24-31,33-40

func parseTwitchEmotes(emoteTag, text string) map[string]*Emote {
	emotes := map[string]*Emote{}

	if emoteTag == "" {
		return emotes
	}

	runes := []rune(text)

	emoteSlice := strings.Split(emoteTag, "/")
	for i := range emoteSlice {
		spl := strings.Split(emoteSlice[i], ":")
		pos := strings.Split(spl[1], ",")
		sp := strings.Split(pos[0], "-")
		start, _ := strconv.Atoi(sp[0])
		end, _ := strconv.Atoi(sp[1])
		id := spl[0]
		e := &Emote{
			Type:  "twitch",
			ID:    id,
			Count: strings.Count(emoteSlice[i], "-"),
			Name:  string(runes[start : end+1]),
		}

		emotes[e.Name] = e
	}
	return emotes
}
<commit_msg>fixed /me<commit_after>package main

import (
	"fmt"
	"html/template"
	"strings"
	"time"

	"github.com/sirupsen/logrus"

	"strconv"
)

type msgType int

const (
	msgPrivmsg msgType = iota + 1
	msgTimeout
)

type Message struct {
	typ msgType

	Time        time.Time         `json:"time"`
	Channel     string            `json:"channel"`
	Username    string            `json:"username"`
	DisplayName string            `json:"displayName"`
	UserType    string            `json:"userType"`
	Color       string            `json:"color"`
	Badges      map[string]int    `json:"badges"`
	Emotes      map[string]*Emote `json:"emotes"`
	Tags        map[string]string `json:"tags"`
	Text        string            `json:"text"`

	TextWithEmotes template.HTML `json:"-"`
	TimeStamp      string        `json:"-"`
}

type Emote struct {
	Name  string `json:"name"`
	ID    string `json:"id"`
	Type  string `json:"type"`
	Count int    `json:"count"`
}

func parseMessage(line string) *Message {
	if strings.Contains(line, "CLEARCHAT") {
		logrus.Debug(line)
	}
	if !strings.HasPrefix(line, "@") {
		return &Message{
			Text: line,
		}
	}
	spl := strings.SplitN(line, " :", 3)
	if len(spl) < 3 {
		return &Message{
			Text: line,
		}
	}
	tags, middle, text := spl[0], spl[1], spl[2]
	if strings.HasPrefix(text, "\u0001ACTION") {
		text = text[8 : len(text)-1]
	}
	msg := &Message{
		Time: time.Now(),
		Text: text,
		Tags: map[string]string{},
	}
	parseMiddle(msg, middle)
	parseTags(msg, tags[1:])
	if msg.typ == msgTimeout {
		msg.Username = "twitch"
		targetUser := msg.Text
		seconds, _ := strconv.Atoi(msg.Tags["ban-duration"])

		msg.Text = fmt.Sprintf("%s was timed out for %s: %s",
			targetUser,
			time.Duration(time.Duration(seconds)*time.Second),
			msg.Tags["ban-reason"])
	}
	return msg
}

func parseMiddle(msg *Message, middle string) {
	for i, c := range middle {
		if c == '!' {
			msg.Username = middle[:i]
			middle = middle[i:]
		}
	}
	start := -1
	for i, c := range middle {
		if c == ' ' {
			if start == -1 {
				start = i + 1
			} else {
				typ := middle[start:i]
				switch typ {
				case "PRIVMSG":
					msg.typ = msgPrivmsg
				case "CLEARCHAT":
					msg.typ = msgTimeout
				}
				middle = middle[i:]
			}
		}
	}
	for i, c := range middle {
		if c == '#' {
			msg.Channel = middle[i+1:]
		}
	}
}

/* @badges=broadcaster/1;color=#0B8E70;display-name=nuuls;emotes=;
id=0cabc0b9-bb6e-448c-9688-1b8ea654dc27;mod=1;room-id=100229878;
subscriber=0;tmi-sent-ts=1482614970705;turbo=0;user-id=100229878;
user-type=mod :nuuls!nuuls@nuuls.tmi.twitch.tv PRIVMSG #nuuls :NaM
*/

func parseTags(msg *Message, tagsRaw string) {
	tags := strings.Split(tagsRaw, ";")
	for _, tag := range tags {
		spl := strings.SplitN(tag, "=", 2)
		if len(spl) < 2 {
			return
		}
		value := strings.Replace(spl[1], "\\:", ";", -1)
		value = strings.Replace(value, "\\s", " ", -1)
		value = strings.Replace(value, "\\\\", "\\", -1)
		switch spl[0] {
		case "badges":
			msg.Badges = parseBadges(value)
		case "color":
			msg.Color = value
		case "display-name":
			msg.DisplayName = value
		case "emotes":
			msg.Emotes = parseTwitchEmotes(value, msg.Text)
		case "user-type":
			msg.UserType = value
		default:
			msg.Tags[spl[0]] = value
		}
	}
}

func parseBadges(badges string) map[string]int {
	m := map[string]int{}
	spl := strings.Split(badges, ",")
	for _, badge := range spl {
		s := strings.SplitN(badge, "/", 2)
		if len(s) < 2 {
			continue
		}
		n, _ := strconv.Atoi(s[1])
		m[s[0]] = n
	}
	return m
}

// 25:0-4,6-10,12-16/1902:18-22/88:24-31,33-40

func parseTwitchEmotes(emoteTag, text string) map[string]*Emote {
	emotes := map[string]*Emote{}

	if emoteTag == "" {
		return emotes
	}

	runes := []rune(text)

	emoteSlice := strings.Split(emoteTag, "/")
	for i := range emoteSlice {
		spl := strings.Split(emoteSlice[i], ":")
		pos := strings.Split(spl[1], ",")
		sp := strings.Split(pos[0], "-")
		start, _ := strconv.Atoi(sp[0])
		end, _ := strconv.Atoi(sp[1])
		id := spl[0]
		e := &Emote{
			Type:  "twitch",
			ID:    id,
			Count: strings.Count(emoteSlice[i], "-"),
			Name:  string(runes[start : end+1]),
		}

		emotes[e.Name] = e
	}
	return emotes
}
<|endoftext|>


<commit_before>// Copyright (C) 2017 Damon Revoe. All rights reserved.
// Use of this source code is governed by the MIT
// license, which can be found in the LICENSE file.

package main

import (
	"fmt"
	"strings"
)

type templateParams map[string]interface{}

type fileParams struct {
	filename string
	params   templateParams
}

type array struct {
	paramName    string
	paramValues  []string
	continuation verbatim
}

type verbatim struct {
	text string
	next *array
}

// Subst updates the 'verbatim' receiver by replacing all instances
// of paramName surrounded by braces with paramValue, which can be
// either a string or a slice of strings. In the latter case, the
// text in the receiver structure gets truncated by the substitution
// and the receiver structure gets extended by a new array structure.
func (v *verbatim) subst(paramName string, paramValue interface{}) {
	textValue, ok := paramValue.(string)

	if ok {
		v.text = strings.Replace(v.text, "{"+paramName+"}", textValue, -1)
	} else {
		arrayValue, ok := paramValue.([]string)

		if ok {
			pos := strings.Index(v.text, "{"+paramName+"}")

			if pos >= 0 {
				v.next = &array{paramName, arrayValue,
					verbatim{v.text[len(paramName)+2:], v.next}}
				v.text = v.text[:pos]
			}
		} else {
			v.text = strings.Replace(v.text, "{"+paramName+"}",
				fmt.Sprint(paramValue), -1)
		}
	}
}

// ExpandPathnameTemplate takes a pathname template and subsitutes
// template parameter names with their values. Parameter values can be
// either strings or slices of strings. Each template value that is a
// slice of strings multiplies the number of output strings by the number
// of strings in the slice.
func expandPathnameTemplate(pathname string, params templateParams) []fileParams {
	root := verbatim{pathname, nil}

	for paramName, paramValue := range params {
		root.subst(paramName, paramValue)

		for node := root.next; node != nil; node = node.continuation.next {
			node.continuation.subst(paramName, paramValue)
		}

		fmt.Println(paramName, paramValue)
		strings.Index(pathname, "{"+paramName+"}")
	}

	if root.next == nil {
		return []fileParams{{root.text, params}}
	}

	fmt.Println(root)

	return []fileParams{{pathname, params}}
}
<commit_msg>Fix a bug in verbatim.subst<commit_after>// Copyright (C) 2017 Damon Revoe. All rights reserved.
// Use of this source code is governed by the MIT
// license, which can be found in the LICENSE file.

package main

import (
	"fmt"
	"strings"
)

type templateParams map[string]interface{}

type fileParams struct {
	filename string
	params   templateParams
}

type array struct {
	paramName    string
	paramValues  []string
	continuation verbatim
}

type verbatim struct {
	text string
	next *array
}

// Subst updates the 'verbatim' receiver by replacing all instances
// of paramName surrounded by braces with paramValue, which can be
// either a string or a slice of strings. In the latter case, the
// text in the receiver structure gets truncated by the substitution
// and the receiver structure gets extended by a new array structure.
func (v *verbatim) subst(paramName string, paramValue interface{}) {
	textValue, ok := paramValue.(string)

	if ok {
		v.text = strings.Replace(v.text, "{"+paramName+"}", textValue, -1)
	} else {
		arrayValue, ok := paramValue.([]string)

		if ok {
			pos := strings.Index(v.text, "{"+paramName+"}")

			if pos >= 0 {
				v.next = &array{paramName, arrayValue,
					verbatim{v.text[pos+len(paramName)+2:], v.next}}
				v.text = v.text[:pos]
			}
		} else {
			v.text = strings.Replace(v.text, "{"+paramName+"}",
				fmt.Sprint(paramValue), -1)
		}
	}
}

// ExpandPathnameTemplate takes a pathname template and subsitutes
// template parameter names with their values. Parameter values can be
// either strings or slices of strings. Each template value that is a
// slice of strings multiplies the number of output strings by the number
// of strings in the slice.
func expandPathnameTemplate(pathname string, params templateParams) []fileParams {
	root := verbatim{pathname, nil}

	for paramName, paramValue := range params {
		root.subst(paramName, paramValue)

		for node := root.next; node != nil; node = node.continuation.next {
			node.continuation.subst(paramName, paramValue)
		}

		fmt.Println(paramName, paramValue)
		strings.Index(pathname, "{"+paramName+"}")
	}

	if root.next == nil {
		return []fileParams{{root.text, params}}
	}

	fmt.Println(root)

	return []fileParams{{pathname, params}}
}
<|endoftext|>


<commit_before>/*
Copyright 2014 Huawei Technologies Co., Ltd. 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 models

import (
	"github.com/jinzhu/gorm"
)

type ComponentType string

const (
	ComponentTypeKubernetes ComponentType = "Kubernetes"
	ComponentTypeMesos      ComponentType = "Mesos"
	ComponentTypeSwarm      ComponentType = "Swarm"
)

var ComponentTypes = []ComponentType{ComponentTypeKubernetes, ComponentTypeMesos, ComponentTypeSwarm}

//Component is customized container(docker or rkt) for executing DevOps tasks.
type Component struct {
	BaseIDField
	Name        string `sql:"not null;type:varchar(100);unique_index:uix_component_1"` //Component name for query.
	Version     string `sql:"not null;type:varchar(30);unique_index:uix_component_1"`  // component version for display
	Type        int    `sql:"not null;default:0"`                                      //Container type: docker or rkt.
	ImageName   string `sql:"not null;varchar(100);index:idx_component_1"`
	ImageTag    string `sql:"varchar(30)";index:idx_component_1`
	Timeout     int    `` //
	DataFrom    string
	UseAdvanced bool   `sql:"not null;default:false"`
	KubeSetting string `sql:"null;type:text"` //Kubernetes execute script.
	Input       string `sql:"null;type:text"` //component input
	Output      string `sql:"null;type:text"` //component output
	Environment string `sql:"null;type:text"` //Environment parameters.
	//Manifest    string `json:"manifest" sql:"null;type:longtext"` //
	BaseModel2
}

//TableName is return the table name of Component in MySQL database.
func (c *Component) TableName() string {
	return "component"
}

func (c *Component) GetComponent() *gorm.DB {
	return db.Model(&Component{})
}

func (component *Component) Create() error {
	return db.Create(component).Error
}

func (condition *Component) SelectComponent() (component *Component, err error) {
	var result Component
	err = db.Where(condition).First(&result).Error
	component = &result
	return
}

func SelectComponents(name, version string, fuzzy bool, pageNum, versionNum, offset int) (components []Component, err error) {
	var offsetCond, cond string
	values := make([]interface{}, 0)
	if name != "" {
		if fuzzy {
			cond = " where name like ?"
		} else {
			cond = " where name = ?"
		}
		values = append(values, name)
	}
	if version != "" {
		if cond == "" {
			cond = " where version = ?"
		} else {
			cond = cond + " version = ?"
		}
		values = append(values, version)
	}
	var max int
	if name != "" && !fuzzy {
		offsetCond = " where version_num > ? and version_num < ?"
		max = offset + versionNum
	} else {
		offsetCond = " where page_num > ? and page_num < ?"
		max = offset + pageNum
	}
	values = append(values, max)

	components = make([]Component, 0)
	err = db.Raw("select id, name, version," +
		"(case when @name != name then @page_num := @page_num + 1 else @page_num end) as page_num," +
		"(case when @name != name then @version_num := 1 else @version_num := @version_num + 1 end) as version_num " +
		"from (select id, name, version from component " +
		cond +
		"order by name, version)" +
		offsetCond, values...).Find(&components).Error
	return
}

func (component *Component) Save() error {
	return db.Save(component).Error
}

func (component *Component) Delete() error {
	return db.Delete(component).Error
}
<commit_msg>fix sql params<commit_after>/*
Copyright 2014 Huawei Technologies Co., Ltd. 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 models

import (
	"github.com/jinzhu/gorm"
)

type ComponentType string

const (
	ComponentTypeKubernetes ComponentType = "Kubernetes"
	ComponentTypeMesos      ComponentType = "Mesos"
	ComponentTypeSwarm      ComponentType = "Swarm"
)

var ComponentTypes = []ComponentType{ComponentTypeKubernetes, ComponentTypeMesos, ComponentTypeSwarm}

//Component is customized container(docker or rkt) for executing DevOps tasks.
type Component struct {
	BaseIDField
	Name        string `sql:"not null;type:varchar(100);unique_index:uix_component_1"` //Component name for query.
	Version     string `sql:"not null;type:varchar(30);unique_index:uix_component_1"`  // component version for display
	Type        int    `sql:"not null;default:0"`                                      //Container type: docker or rkt.
	ImageName   string `sql:"not null;varchar(100);index:idx_component_1"`
	ImageTag    string `sql:"varchar(30)";index:idx_component_1`
	Timeout     int    `` //
	DataFrom    string
	UseAdvanced bool   `sql:"not null;default:false"`
	KubeSetting string `sql:"null;type:text"` //Kubernetes execute script.
	Input       string `sql:"null;type:text"` //component input
	Output      string `sql:"null;type:text"` //component output
	Environment string `sql:"null;type:text"` //Environment parameters.
	//Manifest    string `json:"manifest" sql:"null;type:longtext"` //
	BaseModel2
}

//TableName is return the table name of Component in MySQL database.
func (c *Component) TableName() string {
	return "component"
}

func (c *Component) GetComponent() *gorm.DB {
	return db.Model(&Component{})
}

func (component *Component) Create() error {
	return db.Create(component).Error
}

func (condition *Component) SelectComponent() (component *Component, err error) {
	var result Component
	err = db.Where(condition).First(&result).Error
	component = &result
	return
}

func SelectComponents(name, version string, fuzzy bool, pageNum, versionNum, offset int) (components []Component, err error) {
	var offsetCond, cond string
	values := make([]interface{}, 0)
	if name != "" {
		if fuzzy {
			cond = " where name like ?"
			values = append(values, name + "%")
		} else {
			cond = " where name = ?"
			values = append(values, name)
		}
	}
	if version != "" {
		if cond == "" {
			cond = " where version = ?"
		} else {
			cond = cond + " version = ?"
		}
		values = append(values, version)
	}
	var max int
	if name != "" && !fuzzy {
		offsetCond = " where version_num > ? and version_num < ?"
		max = offset + versionNum
		values = append(values, offset, max)
	} else {
		offsetCond = " where page_num > ? and page_num < ? and version_num < ?"
		max = offset + pageNum
		values = append(values, offset, max, versionNum)
	}

	components = make([]Component, 0)
	err = db.Raw("select id, name, version," +
		"(case when @name != name then @page_num := @page_num + 1 else @page_num end) as page_num," +
		"(case when @name != name then @version_num := 1 else @version_num := @version_num + 1 end) as version_num " +
		"from (select id, name, version from component " +
		cond +
		"order by name, version)" +
		offsetCond, values...).Find(&components).Error
	return
}

func (component *Component) Save() error {
	return db.Save(component).Error
}

func (component *Component) Delete() error {
	return db.Delete(component).Error
}
<|endoftext|>


<commit_before>// Copyright (C) 2017 Damon Revoe. All rights reserved.
// Use of this source code is governed by the MIT
// license, which can be found in the LICENSE file.

package main

import (
	"fmt"
	"strings"
)

type templateParams map[string]interface{}

type fileParams struct {
	filename string
	params   templateParams
}

type array struct {
	paramName    string
	paramValues  []string
	continuation pathnameTemplateText
}

type pathnameTemplateText struct {
	text string
	next *array
}

// Subst updates the 'pathnameTemplateText' receiver by replacing all
// instances of 'name' surrounded by braces with 'value', which can
// be either a string or a slice of strings. In the latter case, the
// text in the receiver structure gets truncated by the substitution
// and the receiver structure gets extended by a new array structure.
// Subst returns the number of substitution values.
func (t *pathnameTemplateText) subst(name string, value interface{}) int {
	if textValue, ok := value.(string); ok {
		t.text = strings.Replace(t.text, "{"+name+"}",
			textValue, -1)
	} else if arrayValue, ok := value.([]string); !ok {
		t.text = strings.Replace(t.text, "{"+name+"}",
			fmt.Sprint(value), -1)
	} else if pos := strings.Index(t.text, "{"+name+"}"); pos >= 0 {
		t.next = &array{name, arrayValue,
			pathnameTemplateText{t.text[pos+len(name)+2:], t.next}}
		t.text = t.text[:pos]
		return len(arrayValue)
	}

	return 1
}

// ExpandPathnameTemplate takes a pathname template and subsitutes
// template parameter names with their values. Parameter values can be
// either strings or slices of strings. Each template value that is a
// slice of strings multiplies the number of output strings by the number
// of strings in the slice.
func expandPathnameTemplate(pathname string,
	params templateParams) []fileParams {
	root := pathnameTemplateText{pathname, nil}

	resultSize := 1

	for name, value := range params {
		resultSize *= root.subst(name, value)

		for n := root.next; n != nil; n = n.continuation.next {
			resultSize *= n.continuation.subst(name, value)
		}
	}

	result := make([]fileParams, resultSize)

	for i := 0; i < resultSize; i++ {
		result[i].filename = root.text
		copyOfParams := templateParams{}
		for name, value := range params {
			copyOfParams[name] = value
		}
		result[i].params = copyOfParams
	}

	if resultSize == 0 {
		return result
	}

	sliceSize := 1

	for a := root.next; a != nil; a = a.continuation.next {
		numberOfValues := len(a.paramValues)

		verbatimText := a.continuation.text

		for i := 0; i < resultSize; {
			for j := 0; j < numberOfValues; j++ {
				value := a.paramValues[j]
				filenameFragment := value + verbatimText
				for k := 0; k < sliceSize; k++ {
					result[i].filename += filenameFragment
					result[i].params[a.paramName] = value
					i++
				}
			}
		}

		sliceSize *= numberOfValues
	}

	return result
}
<commit_msg>Rename the rest of obscure types in paths.go<commit_after>// Copyright (C) 2017 Damon Revoe. All rights reserved.
// Use of this source code is governed by the MIT
// license, which can be found in the LICENSE file.

package main

import (
	"fmt"
	"strings"
)

type templateParams map[string]interface{}

type fileParams struct {
	filename string
	params   templateParams
}

type pathnameTemplateMultiplier struct {
	paramName    string
	paramValues  []string
	continuation pathnameTemplateText
}

type pathnameTemplateText struct {
	text string
	next *pathnameTemplateMultiplier
}

// Subst updates the 'pathnameTemplateText' receiver by replacing all
// instances of 'name' surrounded by braces with 'value', which can be
// either a string or a slice of strings.  In the latter case, the text
// in the receiver structure gets truncated by the substitution and the
// receiver structure gets extended by a new pathnameTemplateMultiplier
// structure.  Subst returns the number of substitution values.
func (t *pathnameTemplateText) subst(name string, value interface{}) int {
	if textValue, ok := value.(string); ok {
		t.text = strings.Replace(t.text, "{"+name+"}",
			textValue, -1)
	} else if arrayValue, ok := value.([]string); !ok {
		t.text = strings.Replace(t.text, "{"+name+"}",
			fmt.Sprint(value), -1)
	} else if pos := strings.Index(t.text, "{"+name+"}"); pos >= 0 {
		t.next = &pathnameTemplateMultiplier{name, arrayValue,
			pathnameTemplateText{t.text[pos+len(name)+2:], t.next}}
		t.text = t.text[:pos]
		return len(arrayValue)
	}

	return 1
}

// ExpandPathnameTemplate takes a pathname template and subsitutes
// template parameter names with their values. Parameter values can be
// either strings or slices of strings. Each template value that is a
// slice of strings multiplies the number of output strings by the number
// of strings in the slice.
func expandPathnameTemplate(pathname string,
	params templateParams) []fileParams {
	root := pathnameTemplateText{pathname, nil}

	resultSize := 1

	for name, value := range params {
		resultSize *= root.subst(name, value)

		for n := root.next; n != nil; n = n.continuation.next {
			resultSize *= n.continuation.subst(name, value)
		}
	}

	result := make([]fileParams, resultSize)

	for i := 0; i < resultSize; i++ {
		result[i].filename = root.text
		copyOfParams := templateParams{}
		for name, value := range params {
			copyOfParams[name] = value
		}
		result[i].params = copyOfParams
	}

	if resultSize == 0 {
		return result
	}

	sliceSize := 1

	for a := root.next; a != nil; a = a.continuation.next {
		numberOfValues := len(a.paramValues)

		continuationText := a.continuation.text

		for i := 0; i < resultSize; {
			for j := 0; j < numberOfValues; j++ {
				value := a.paramValues[j]
				filenameFragment := value + continuationText
				for k := 0; k < sliceSize; k++ {
					result[i].filename += filenameFragment
					result[i].params[a.paramName] = value
					i++
				}
			}
		}

		sliceSize *= numberOfValues
	}

	return result
}
<|endoftext|>


<commit_before>/*
Copyright 2017 The Kubernetes Authors.

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

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

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

package controller

import (
	"github.com/golang/glog"
	"github.com/jcmoraisjr/haproxy-ingress/pkg/common/ingress"
	"github.com/jcmoraisjr/haproxy-ingress/pkg/common/ingress/controller"
	"github.com/jcmoraisjr/haproxy-ingress/pkg/common/ingress/defaults"
	"github.com/jcmoraisjr/haproxy-ingress/pkg/controller/dynconfig"
	"github.com/jcmoraisjr/haproxy-ingress/pkg/types"
	"github.com/jcmoraisjr/haproxy-ingress/pkg/version"
	"github.com/spf13/pflag"
	"io/ioutil"
	api "k8s.io/api/core/v1"
	extensions "k8s.io/api/extensions/v1beta1"
	"net/http"
	"os"
	"os/exec"
	"sort"
	"strings"
	"time"
)

// HAProxyController has internal data of a HAProxyController instance
type HAProxyController struct {
	controller        *controller.GenericController
	configMap         *api.ConfigMap
	storeLister       *ingress.StoreLister
	command           string
	reloadStrategy    *string
	configDir         string
	configFilePrefix  string
	configFileSuffix  string
	maxOldConfigFiles *int
	haproxyTemplate   *template
	modsecConfigFile  string
	modsecTemplate    *template
	currentConfig     *types.ControllerConfig
}

// NewHAProxyController constructor
func NewHAProxyController() *HAProxyController {
	return &HAProxyController{}
}

// Info provides controller name and repository infos
func (haproxy *HAProxyController) Info() *ingress.BackendInfo {
	return &ingress.BackendInfo{
		Name:       "HAProxy",
		Release:    version.RELEASE,
		Build:      version.COMMIT,
		Repository: version.REPO,
	}
}

// Start starts the controller
func (haproxy *HAProxyController) Start() {
	haproxy.controller = controller.NewIngressController(haproxy)
	if *haproxy.reloadStrategy == "multibinder" {
		glog.Warningf("multibinder is deprecated, using reusesocket strategy instead. update your deployment configuration")
	}
	haproxy.controller.Start()
}

// Stop shutdown the controller process
func (haproxy *HAProxyController) Stop() error {
	err := haproxy.controller.Stop()
	return err
}

// Name provides the complete name of the controller
func (haproxy *HAProxyController) Name() string {
	return "HAProxy Ingress Controller"
}

// DefaultIngressClass returns the ingress class name
func (haproxy *HAProxyController) DefaultIngressClass() string {
	return "haproxy"
}

// Check health check implementation
func (haproxy *HAProxyController) Check(_ *http.Request) error {
	return nil
}

// SetListers give access to the store listers
func (haproxy *HAProxyController) SetListers(lister *ingress.StoreLister) {
	haproxy.storeLister = lister
}

// UpdateIngressStatus custom callback used to update the status in an Ingress rule
// If the function returns nil the standard functions will be executed.
func (haproxy *HAProxyController) UpdateIngressStatus(*extensions.Ingress) []api.LoadBalancerIngress {
	return nil
}

// ConfigureFlags allow to configure more flags before the parsing of
// command line arguments
func (haproxy *HAProxyController) ConfigureFlags(flags *pflag.FlagSet) {
	haproxy.reloadStrategy = flags.String("reload-strategy", "native",
		`Name of the reload strategy. Options are: native (default) or reusesocket`)
	haproxy.maxOldConfigFiles = flags.Int("max-old-config-files", 0,
		`Maximum old haproxy timestamped config files to allow before being cleaned up. A value <= 0 indicates a single non-timestamped config file will be used`)
	ingressClass := flags.Lookup("ingress-class")
	if ingressClass != nil {
		ingressClass.Value.Set("haproxy")
		ingressClass.DefValue = "haproxy"
	}
}

// OverrideFlags allows controller to override command line parameter flags
func (haproxy *HAProxyController) OverrideFlags(flags *pflag.FlagSet) {
	haproxy.configDir = "/etc/haproxy"
	haproxy.configFilePrefix = "haproxy"
	haproxy.configFileSuffix = ".cfg"
	haproxy.haproxyTemplate = newTemplate("haproxy.tmpl", "/etc/haproxy/template/haproxy.tmpl", 16384)
	haproxy.modsecConfigFile = "/etc/haproxy/spoe-modsecurity.conf"
	haproxy.modsecTemplate = newTemplate("spoe-modsecurity.tmpl", "/etc/haproxy/modsecurity/spoe-modsecurity.tmpl", 1024)
	haproxy.command = "/haproxy-reload.sh"

	if !(*haproxy.reloadStrategy == "native" || *haproxy.reloadStrategy == "reusesocket" || *haproxy.reloadStrategy == "multibinder") {
		glog.Fatalf("Unsupported reload strategy: %v", *haproxy.reloadStrategy)
	}
}

// SetConfig receives the ConfigMap the user has configured
func (haproxy *HAProxyController) SetConfig(configMap *api.ConfigMap) {
	haproxy.configMap = configMap
}

// BackendDefaults defines default values to the ingress core
func (haproxy *HAProxyController) BackendDefaults() defaults.Backend {
	return newHAProxyConfig(haproxy).Backend
}

// DefaultEndpoint returns the Endpoint to use as default when the
// referenced service does not exists
func (haproxy *HAProxyController) DefaultEndpoint() ingress.Endpoint {
	return ingress.Endpoint{
		Address:  "127.0.0.1",
		Port:     "8181",
		Draining: false,
		Target:   &api.ObjectReference{},
	}
}

// DrainSupport indicates whether or not this controller supports a "drain" mode where
// unavailable and terminating pods are included in the list of returned pods and used to
// direct certain traffic (e.g., traffic using persistence) to terminating/unavailable pods.
func (haproxy *HAProxyController) DrainSupport() (drainSupport bool) {
	if haproxy.currentConfig != nil {
		drainSupport = haproxy.currentConfig.Cfg.DrainSupport
	}
	return
}

// OnUpdate regenerate the configuration file of the backend
func (haproxy *HAProxyController) OnUpdate(cfg ingress.Configuration) error {
	updatedConfig, err := newControllerConfig(&cfg, haproxy)
	if err != nil {
		return err
	}

	reloadRequired := !dynconfig.ConfigBackends(haproxy.currentConfig, updatedConfig)
	haproxy.currentConfig = updatedConfig

	data, err := haproxy.haproxyTemplate.execute(updatedConfig)
	if err != nil {
		return err
	}

	configFile, err := haproxy.rewriteConfigFiles(data)
	if err != nil {
		return err
	}

	modSecConf, err := haproxy.modsecTemplate.execute(updatedConfig)
	if err != nil {
		return err
	}

	if err := haproxy.writeModSecConfigFile(modSecConf); err != nil {
		return err
	}

	if !reloadRequired {
		glog.Infoln("HAProxy updated without needing to reload")
		return nil
	}

	reloadCmd := exec.Command(haproxy.command, *haproxy.reloadStrategy, configFile)
	out, err := reloadCmd.CombinedOutput()
	if len(out) > 0 {
		glog.Infof("HAProxy[pid=%v] output:\n%v", reloadCmd.Process.Pid, string(out))
	}
	return err
}

func (haproxy *HAProxyController) writeModSecConfigFile(data []byte) error {
	if err := ioutil.WriteFile(haproxy.modsecConfigFile, data, 644); err != nil {
		glog.Warningf("Error writing modsecurity config file: %v", err)
		return err
	}
	return nil
}

// RewriteConfigFiles safely replaces configuration files with new contents after validation
func (haproxy *HAProxyController) rewriteConfigFiles(data []byte) (string, error) {
	// Include timestamp in config file name to aid troubleshooting. When using a single, ever-changing config file it
	// was difficult to know what config was loaded by any given haproxy process
	timestamp := ""
	if *haproxy.maxOldConfigFiles > 0 {
		timestamp = time.Now().Format("-20060102-150405.000")
	}
	configFile := haproxy.configDir + "/" + haproxy.configFilePrefix + timestamp + haproxy.configFileSuffix

	// Write directly to configFile
	if err := ioutil.WriteFile(configFile, data, 644); err != nil {
		glog.Warningf("Error writing haproxy config file: %v", err)
		return "", err
	}

	// Validate haproxy config
	if err := checkValidity(configFile); err != nil {
		return "", err
	}

	if *haproxy.maxOldConfigFiles > 0 {
		if err := haproxy.removeOldConfigFiles(); err != nil {
			glog.Warningf("Problem removing old config files, but continuing in case it was a fluke. err=%v", err)
		}
	}

	return configFile, nil
}

func (haproxy *HAProxyController) removeOldConfigFiles() error {
	files, err := ioutil.ReadDir(haproxy.configDir)
	if err != nil {
		return err
	}

	// Sort with most recently modified first
	sort.Slice(files, func(i, j int) bool {
		return files[i].ModTime().After(files[j].ModTime())
	})

	matchesFound := 0
	for _, f := range files {
		if !f.IsDir() && strings.HasPrefix(f.Name(), haproxy.configFilePrefix) && strings.HasSuffix(f.Name(), haproxy.configFileSuffix) {
			matchesFound = matchesFound + 1
			if matchesFound > *haproxy.maxOldConfigFiles {
				filePath := haproxy.configDir + "/" + f.Name()
				glog.Infof("Removing old config file (%v). maxOldConfigFiles=%v", filePath, *haproxy.maxOldConfigFiles)
				if err := os.Remove(filePath); err != nil {
					return err
				}
			}
		}
	}
	return nil
}

// checkValidity runs a HAProxy configuration validity check on a file
func checkValidity(configFile string) error {
	out, err := exec.Command("haproxy", "-c", "-f", configFile).CombinedOutput()
	if err != nil {
		glog.Warningf("Error validating config file:\n%v", string(out))
		return err
	}
	return nil
}
<commit_msg>Fix validation of mod security conf<commit_after>/*
Copyright 2017 The Kubernetes Authors.

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

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

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

package controller

import (
	"github.com/golang/glog"
	"github.com/jcmoraisjr/haproxy-ingress/pkg/common/ingress"
	"github.com/jcmoraisjr/haproxy-ingress/pkg/common/ingress/controller"
	"github.com/jcmoraisjr/haproxy-ingress/pkg/common/ingress/defaults"
	"github.com/jcmoraisjr/haproxy-ingress/pkg/controller/dynconfig"
	"github.com/jcmoraisjr/haproxy-ingress/pkg/types"
	"github.com/jcmoraisjr/haproxy-ingress/pkg/version"
	"github.com/spf13/pflag"
	"io/ioutil"
	api "k8s.io/api/core/v1"
	extensions "k8s.io/api/extensions/v1beta1"
	"net/http"
	"os"
	"os/exec"
	"sort"
	"strings"
	"time"
)

// HAProxyController has internal data of a HAProxyController instance
type HAProxyController struct {
	controller        *controller.GenericController
	configMap         *api.ConfigMap
	storeLister       *ingress.StoreLister
	command           string
	reloadStrategy    *string
	configDir         string
	configFilePrefix  string
	configFileSuffix  string
	maxOldConfigFiles *int
	haproxyTemplate   *template
	modsecConfigFile  string
	modsecTemplate    *template
	currentConfig     *types.ControllerConfig
}

// NewHAProxyController constructor
func NewHAProxyController() *HAProxyController {
	return &HAProxyController{}
}

// Info provides controller name and repository infos
func (haproxy *HAProxyController) Info() *ingress.BackendInfo {
	return &ingress.BackendInfo{
		Name:       "HAProxy",
		Release:    version.RELEASE,
		Build:      version.COMMIT,
		Repository: version.REPO,
	}
}

// Start starts the controller
func (haproxy *HAProxyController) Start() {
	haproxy.controller = controller.NewIngressController(haproxy)
	if *haproxy.reloadStrategy == "multibinder" {
		glog.Warningf("multibinder is deprecated, using reusesocket strategy instead. update your deployment configuration")
	}
	haproxy.controller.Start()
}

// Stop shutdown the controller process
func (haproxy *HAProxyController) Stop() error {
	err := haproxy.controller.Stop()
	return err
}

// Name provides the complete name of the controller
func (haproxy *HAProxyController) Name() string {
	return "HAProxy Ingress Controller"
}

// DefaultIngressClass returns the ingress class name
func (haproxy *HAProxyController) DefaultIngressClass() string {
	return "haproxy"
}

// Check health check implementation
func (haproxy *HAProxyController) Check(_ *http.Request) error {
	return nil
}

// SetListers give access to the store listers
func (haproxy *HAProxyController) SetListers(lister *ingress.StoreLister) {
	haproxy.storeLister = lister
}

// UpdateIngressStatus custom callback used to update the status in an Ingress rule
// If the function returns nil the standard functions will be executed.
func (haproxy *HAProxyController) UpdateIngressStatus(*extensions.Ingress) []api.LoadBalancerIngress {
	return nil
}

// ConfigureFlags allow to configure more flags before the parsing of
// command line arguments
func (haproxy *HAProxyController) ConfigureFlags(flags *pflag.FlagSet) {
	haproxy.reloadStrategy = flags.String("reload-strategy", "native",
		`Name of the reload strategy. Options are: native (default) or reusesocket`)
	haproxy.maxOldConfigFiles = flags.Int("max-old-config-files", 0,
		`Maximum old haproxy timestamped config files to allow before being cleaned up. A value <= 0 indicates a single non-timestamped config file will be used`)
	ingressClass := flags.Lookup("ingress-class")
	if ingressClass != nil {
		ingressClass.Value.Set("haproxy")
		ingressClass.DefValue = "haproxy"
	}
}

// OverrideFlags allows controller to override command line parameter flags
func (haproxy *HAProxyController) OverrideFlags(flags *pflag.FlagSet) {
	haproxy.configDir = "/etc/haproxy"
	haproxy.configFilePrefix = "haproxy"
	haproxy.configFileSuffix = ".cfg"
	haproxy.haproxyTemplate = newTemplate("haproxy.tmpl", "/etc/haproxy/template/haproxy.tmpl", 16384)
	haproxy.modsecConfigFile = "/etc/haproxy/spoe-modsecurity.conf"
	haproxy.modsecTemplate = newTemplate("spoe-modsecurity.tmpl", "/etc/haproxy/modsecurity/spoe-modsecurity.tmpl", 1024)
	haproxy.command = "/haproxy-reload.sh"

	if !(*haproxy.reloadStrategy == "native" || *haproxy.reloadStrategy == "reusesocket" || *haproxy.reloadStrategy == "multibinder") {
		glog.Fatalf("Unsupported reload strategy: %v", *haproxy.reloadStrategy)
	}
}

// SetConfig receives the ConfigMap the user has configured
func (haproxy *HAProxyController) SetConfig(configMap *api.ConfigMap) {
	haproxy.configMap = configMap
}

// BackendDefaults defines default values to the ingress core
func (haproxy *HAProxyController) BackendDefaults() defaults.Backend {
	return newHAProxyConfig(haproxy).Backend
}

// DefaultEndpoint returns the Endpoint to use as default when the
// referenced service does not exists
func (haproxy *HAProxyController) DefaultEndpoint() ingress.Endpoint {
	return ingress.Endpoint{
		Address:  "127.0.0.1",
		Port:     "8181",
		Draining: false,
		Target:   &api.ObjectReference{},
	}
}

// DrainSupport indicates whether or not this controller supports a "drain" mode where
// unavailable and terminating pods are included in the list of returned pods and used to
// direct certain traffic (e.g., traffic using persistence) to terminating/unavailable pods.
func (haproxy *HAProxyController) DrainSupport() (drainSupport bool) {
	if haproxy.currentConfig != nil {
		drainSupport = haproxy.currentConfig.Cfg.DrainSupport
	}
	return
}

// OnUpdate regenerate the configuration file of the backend
func (haproxy *HAProxyController) OnUpdate(cfg ingress.Configuration) error {
	updatedConfig, err := newControllerConfig(&cfg, haproxy)
	if err != nil {
		return err
	}

	reloadRequired := !dynconfig.ConfigBackends(haproxy.currentConfig, updatedConfig)
	haproxy.currentConfig = updatedConfig

	modSecConf, err := haproxy.modsecTemplate.execute(updatedConfig)
	if err != nil {
		return err
	}

	if err := haproxy.writeModSecConfigFile(modSecConf); err != nil {
		return err
	}

	data, err := haproxy.haproxyTemplate.execute(updatedConfig)
	if err != nil {
		return err
	}

	configFile, err := haproxy.rewriteConfigFiles(data)
	if err != nil {
		return err
	}

	if !reloadRequired {
		glog.Infoln("HAProxy updated without needing to reload")
		return nil
	}

	reloadCmd := exec.Command(haproxy.command, *haproxy.reloadStrategy, configFile)
	out, err := reloadCmd.CombinedOutput()
	if len(out) > 0 {
		glog.Infof("HAProxy[pid=%v] output:\n%v", reloadCmd.Process.Pid, string(out))
	}
	return err
}

func (haproxy *HAProxyController) writeModSecConfigFile(data []byte) error {
	if err := ioutil.WriteFile(haproxy.modsecConfigFile, data, 644); err != nil {
		glog.Warningf("Error writing modsecurity config file: %v", err)
		return err
	}
	return nil
}

// RewriteConfigFiles safely replaces configuration files with new contents after validation
func (haproxy *HAProxyController) rewriteConfigFiles(data []byte) (string, error) {
	// Include timestamp in config file name to aid troubleshooting. When using a single, ever-changing config file it
	// was difficult to know what config was loaded by any given haproxy process
	timestamp := ""
	if *haproxy.maxOldConfigFiles > 0 {
		timestamp = time.Now().Format("-20060102-150405.000")
	}
	configFile := haproxy.configDir + "/" + haproxy.configFilePrefix + timestamp + haproxy.configFileSuffix

	// Write directly to configFile
	if err := ioutil.WriteFile(configFile, data, 644); err != nil {
		glog.Warningf("Error writing haproxy config file: %v", err)
		return "", err
	}

	// Validate haproxy config
	if err := checkValidity(configFile); err != nil {
		return "", err
	}

	if *haproxy.maxOldConfigFiles > 0 {
		if err := haproxy.removeOldConfigFiles(); err != nil {
			glog.Warningf("Problem removing old config files, but continuing in case it was a fluke. err=%v", err)
		}
	}

	return configFile, nil
}

func (haproxy *HAProxyController) removeOldConfigFiles() error {
	files, err := ioutil.ReadDir(haproxy.configDir)
	if err != nil {
		return err
	}

	// Sort with most recently modified first
	sort.Slice(files, func(i, j int) bool {
		return files[i].ModTime().After(files[j].ModTime())
	})

	matchesFound := 0
	for _, f := range files {
		if !f.IsDir() && strings.HasPrefix(f.Name(), haproxy.configFilePrefix) && strings.HasSuffix(f.Name(), haproxy.configFileSuffix) {
			matchesFound = matchesFound + 1
			if matchesFound > *haproxy.maxOldConfigFiles {
				filePath := haproxy.configDir + "/" + f.Name()
				glog.Infof("Removing old config file (%v). maxOldConfigFiles=%v", filePath, *haproxy.maxOldConfigFiles)
				if err := os.Remove(filePath); err != nil {
					return err
				}
			}
		}
	}
	return nil
}

// checkValidity runs a HAProxy configuration validity check on a file
func checkValidity(configFile string) error {
	out, err := exec.Command("haproxy", "-c", "-f", configFile).CombinedOutput()
	if err != nil {
		glog.Warningf("Error validating config file:\n%v", string(out))
		return err
	}
	return nil
}
<|endoftext|>


<commit_before>/*
Copyright AppsCode Inc. and Contributors

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 controller

import (
	"time"

	api "kubedb.dev/apimachinery/apis/kubedb/v1alpha1"
	cs "kubedb.dev/apimachinery/client/clientset/versioned"
	kubedbinformers "kubedb.dev/apimachinery/client/informers/externalversions"

	"github.com/appscode/go/log/golog"
	cm "github.com/jetstack/cert-manager/pkg/client/clientset/versioned"
	cmInformers "github.com/jetstack/cert-manager/pkg/client/informers/externalversions"
	crd_cs "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
	externalInformers "k8s.io/apiextensions-apiserver/pkg/client/informers/externalversions"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/client-go/dynamic"
	"k8s.io/client-go/informers"
	"k8s.io/client-go/kubernetes"
	corelisters "k8s.io/client-go/listers/core/v1"
	"k8s.io/client-go/rest"
	"k8s.io/client-go/tools/cache"
	core_util "kmodules.xyz/client-go/core/v1"
	"kmodules.xyz/client-go/tools/queue"
	appcat_cs "kmodules.xyz/custom-resources/client/clientset/versioned"
	appcat_in "kmodules.xyz/custom-resources/client/informers/externalversions"
	scs "stash.appscode.dev/apimachinery/client/clientset/versioned"
	stashInformers "stash.appscode.dev/apimachinery/client/informers/externalversions"
)

type Controller struct {
	ClientConfig *rest.Config
	// Kubernetes client
	Client kubernetes.Interface
	// CRD Client
	CRDClient crd_cs.Interface
	// ThirdPartyExtension client
	ExtClient cs.Interface //#TODO: rename to DBClient
	// Dynamic client
	DynamicClient dynamic.Interface
	// AppCatalog client
	AppCatalogClient appcat_cs.Interface
	// StashClient for stash
	StashClient scs.Interface
	//CertManagerClient for cert-manger
	CertManagerClient cm.Interface
	// Cluster topology when the operator started
	ClusterTopology *core_util.Topology
}

type Config struct {
	// Informer factory
	KubeInformerFactory        informers.SharedInformerFactory
	KubedbInformerFactory      kubedbinformers.SharedInformerFactory
	StashInformerFactory       stashInformers.SharedInformerFactory
	AppCatInformerFactory      appcat_in.SharedInformerFactory
	ExternalInformerFactory    externalInformers.SharedInformerFactory
	CertManagerInformerFactory cmInformers.SharedInformerFactory

	// restoreSession queue
	RSQueue    *queue.Worker
	RSInformer cache.SharedIndexInformer

	// Secret
	SecretInformer cache.SharedIndexInformer
	SecretLister   corelisters.SecretLister

	OperatorNamespace       string
	GoverningService        string
	ResyncPeriod            time.Duration
	MaxNumRequeues          int
	NumThreads              int
	LoggerOptions           golog.Options
	EnableAnalytics         bool
	AnalyticsClientID       string
	WatchNamespace          string
	EnableValidatingWebhook bool
	EnableMutatingWebhook   bool
}

type DBHelper interface {
	GetDatabase(metav1.ObjectMeta) (runtime.Object, error)
	SetDatabaseStatus(metav1.ObjectMeta, api.DatabasePhase, string) error
	UpsertDatabaseAnnotation(metav1.ObjectMeta, map[string]string) error
}
<commit_msg>Remove CertManagerClient client<commit_after>/*
Copyright AppsCode Inc. and Contributors

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 controller

import (
	"time"

	api "kubedb.dev/apimachinery/apis/kubedb/v1alpha1"
	cs "kubedb.dev/apimachinery/client/clientset/versioned"
	kubedbinformers "kubedb.dev/apimachinery/client/informers/externalversions"

	"github.com/appscode/go/log/golog"
	cmInformers "github.com/jetstack/cert-manager/pkg/client/informers/externalversions"
	crd_cs "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
	externalInformers "k8s.io/apiextensions-apiserver/pkg/client/informers/externalversions"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/client-go/dynamic"
	"k8s.io/client-go/informers"
	"k8s.io/client-go/kubernetes"
	corelisters "k8s.io/client-go/listers/core/v1"
	"k8s.io/client-go/rest"
	"k8s.io/client-go/tools/cache"
	core_util "kmodules.xyz/client-go/core/v1"
	"kmodules.xyz/client-go/tools/queue"
	appcat_cs "kmodules.xyz/custom-resources/client/clientset/versioned"
	appcat_in "kmodules.xyz/custom-resources/client/informers/externalversions"
	scs "stash.appscode.dev/apimachinery/client/clientset/versioned"
	stashInformers "stash.appscode.dev/apimachinery/client/informers/externalversions"
)

type Controller struct {
	ClientConfig *rest.Config
	// Kubernetes client
	Client kubernetes.Interface
	// CRD Client
	CRDClient crd_cs.Interface
	// ThirdPartyExtension client
	ExtClient cs.Interface //#TODO: rename to DBClient
	// Dynamic client
	DynamicClient dynamic.Interface
	// AppCatalog client
	AppCatalogClient appcat_cs.Interface
	// StashClient for stash
	StashClient scs.Interface
	// Cluster topology when the operator started
	ClusterTopology *core_util.Topology
}

type Config struct {
	// Informer factory
	KubeInformerFactory        informers.SharedInformerFactory
	KubedbInformerFactory      kubedbinformers.SharedInformerFactory
	StashInformerFactory       stashInformers.SharedInformerFactory
	AppCatInformerFactory      appcat_in.SharedInformerFactory
	ExternalInformerFactory    externalInformers.SharedInformerFactory
	CertManagerInformerFactory cmInformers.SharedInformerFactory

	// restoreSession queue
	RSQueue    *queue.Worker
	RSInformer cache.SharedIndexInformer

	// Secret
	SecretInformer cache.SharedIndexInformer
	SecretLister   corelisters.SecretLister

	OperatorNamespace       string
	GoverningService        string
	ResyncPeriod            time.Duration
	MaxNumRequeues          int
	NumThreads              int
	LoggerOptions           golog.Options
	EnableAnalytics         bool
	AnalyticsClientID       string
	WatchNamespace          string
	EnableValidatingWebhook bool
	EnableMutatingWebhook   bool
}

type DBHelper interface {
	GetDatabase(metav1.ObjectMeta) (runtime.Object, error)
	SetDatabaseStatus(metav1.ObjectMeta, api.DatabasePhase, string) error
	UpsertDatabaseAnnotation(metav1.ObjectMeta, map[string]string) error
}
<|endoftext|>


<commit_before>/*
Copyright 2015 The Kubernetes Authors.

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

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

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

package controller

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
	"strings"
	"syscall"
	"text/template"
	"time"

	"github.com/golang/glog"

	"k8s.io/kubernetes/pkg/util/iptables"
	k8sexec "k8s.io/utils/exec"
)

const (
	iptablesChain   = "KUBE-KEEPALIVED-VIP"
	keepalivedCfg   = "/etc/keepalived/keepalived.conf"
	haproxyCfg      = "/etc/haproxy/haproxy.cfg"
	keepalivedPid   = "/var/run/keepalived.pid"
	keepalivedState = "/var/run/keepalived.state"
	vrrpPid         = "/var/run/vrrp.pid"
)

var (
	keepalivedTmpl = "keepalived.tmpl"
	haproxyTmpl    = "haproxy.tmpl"
)

type keepalived struct {
	iface          string
	ip             string
	netmask        int
	priority       int
	nodes          []string
	neighbors      []string
	useUnicast     bool
	started        bool
	vips           []string
	keepalivedTmpl *template.Template
	haproxyTmpl    *template.Template
	cmd            *exec.Cmd
	ipt            iptables.Interface
	vrid           int
	proxyMode      bool
	notify         string
	releaseVips    bool
}

// WriteCfg creates a new keepalived configuration file.
// In case of an error with the generation it returns the error
func (k *keepalived) WriteCfg(svcs []vip) error {
	w, err := os.Create(keepalivedCfg)
	if err != nil {
		return err
	}
	defer w.Close()

	k.vips = getVIPs(svcs)

	conf := make(map[string]interface{})
	conf["iptablesChain"] = iptablesChain
	conf["iface"] = k.iface
	conf["myIP"] = k.ip
	conf["netmask"] = k.netmask
	conf["svcs"] = svcs
	conf["vips"] = k.vips
	conf["nodes"] = k.neighbors
	conf["priority"] = k.priority
	conf["useUnicast"] = k.useUnicast
	conf["vrid"] = k.vrid
	conf["iface"] = k.iface
	conf["proxyMode"] = k.proxyMode
	conf["vipIsEmpty"] = len(k.vips) == 0
	conf["notify"] = k.notify

	if glog.V(2) {
		b, _ := json.Marshal(conf)
		glog.Infof("%v", string(b))
	}

	err = k.keepalivedTmpl.Execute(w, conf)
	if err != nil {
		return fmt.Errorf("unexpected error creating keepalived.cfg: %v", err)
	}

	if k.proxyMode {
		w, err := os.Create(haproxyCfg)
		if err != nil {
			return err
		}
		defer w.Close()
		err = k.haproxyTmpl.Execute(w, conf)
		if err != nil {
			return fmt.Errorf("unexpected error creating haproxy.cfg: %v", err)
		}
	}

	return nil
}

// getVIPs returns a list of the virtual IP addresses to be used in keepalived
// without duplicates (a service can use more than one port)
func getVIPs(svcs []vip) []string {
	result := []string{}
	for _, svc := range svcs {
		result = appendIfMissing(result, svc.IP)
	}

	return result
}

// Start starts a keepalived process in foreground.
// In case of any error it will terminate the execution with a fatal error
func (k *keepalived) Start() {
	ae, err := k.ipt.EnsureChain(iptables.TableFilter, iptables.Chain(iptablesChain))
	if err != nil {
		glog.Fatalf("unexpected error: %v", err)
	}
	if ae {
		glog.V(2).Infof("chain %v already existed", iptablesChain)
	}

	args := []string{"--dont-fork", "--log-console", "--log-detail"}
	if k.releaseVips {
		args = append(args, "--release-vips")
	}

	k.cmd = exec.Command("keepalived", args...)

	k.cmd.Stdout = os.Stdout
	k.cmd.Stderr = os.Stderr

	k.cmd.SysProcAttr = &syscall.SysProcAttr{
		Setpgid: true,
		Pgid:    0,
	}

	k.started = true

	if err := k.cmd.Run(); err != nil {
		glog.Fatalf("Error starting keepalived: %v", err)
	}
}

// Reload sends SIGHUP to keepalived to reload the configuration.
func (k *keepalived) Reload() error {
	glog.Info("Waiting for keepalived to start")
	for !k.IsRunning() {
		time.Sleep(time.Second)
	}

	k.Cleanup()
	glog.Info("reloading keepalived")
	err := syscall.Kill(k.cmd.Process.Pid, syscall.SIGHUP)
	if err != nil {
		return fmt.Errorf("error reloading keepalived: %v", err)
	}

	return nil
}

// Whether keepalived process is currently running
func (k *keepalived) IsRunning() bool {
	if !k.started {
		glog.Error("keepalived not started")
		return false
	}

	if _, err := os.Stat(keepalivedPid); os.IsNotExist(err) {
		glog.Error("Missing keepalived.pid")
		return false
	}

	return true
}

// Whether keepalived child process is currently running and VIPs are assigned
func (k *keepalived) Healthy() error {
	if !k.IsRunning() {
		return fmt.Errorf("keepalived is not running")
	}

	if _, err := os.Stat(vrrpPid); os.IsNotExist(err) {
		return fmt.Errorf("VRRP child process not running")
	}

	b, err := ioutil.ReadFile(keepalivedState)
	if err != nil {
		return err
	}

	master := false
	state := strings.TrimSpace(string(b))
	if strings.Contains(state, "MASTER") {
		master = true
	}

	var out bytes.Buffer
	cmd := exec.Command("ip", "-brief", "address", "show", k.iface, "up")
	cmd.Stderr = os.Stderr
	cmd.Stdout = &out
	cmd.SysProcAttr = &syscall.SysProcAttr{
		Setpgid: true,
		Pgid:    0,
	}

	err = cmd.Run()
	if err != nil {
		return err
	}

	ips := out.String()
	glog.V(3).Infof("Status of %s interface: %s", state, ips)

	for _, vip := range k.vips {
		containsVip := strings.Contains(ips, fmt.Sprintf(" %s/32 ", vip))

		if master && !containsVip {
			return fmt.Errorf("Missing VIP %s on %s", vip, state)
		} else if !master && containsVip {
			return fmt.Errorf("%s should not contain VIP %s", state, vip)
		}
	}

	// All checks successful
	return nil
}

func (k *keepalived) Cleanup() {
	glog.Infof("Cleanup: %s", k.vips)
	for _, vip := range k.vips {
		k.removeVIP(vip)
	}

	err := k.ipt.FlushChain(iptables.TableFilter, iptables.Chain(iptablesChain))
	if err != nil {
		glog.V(2).Infof("unexpected error flushing iptables chain %v: %v", err, iptablesChain)
	}
}

// Stop stop keepalived process
func (k *keepalived) Stop() {
	k.Cleanup()

	err := syscall.Kill(k.cmd.Process.Pid, syscall.SIGTERM)
	if err != nil {
		glog.Errorf("error stopping keepalived: %v", err)
	}
}

func (k *keepalived) removeVIP(vip string) {
	glog.Infof("removing configured VIP %v", vip)
	out, err := k8sexec.New().Command("ip", "addr", "del", vip+"/32", "dev", k.iface).CombinedOutput()
	if err != nil {
		glog.V(2).Infof("Error removing VIP %s: %v\n%s", vip, err, out)
	}
}

func (k *keepalived) loadTemplates() error {
	tmpl, err := template.ParseFiles(keepalivedTmpl)
	if err != nil {
		return err
	}
	k.keepalivedTmpl = tmpl

	tmpl, err = template.ParseFiles(haproxyTmpl)
	if err != nil {
		return err
	}
	k.haproxyTmpl = tmpl

	return nil
}
<commit_msg>Don't cleanup VIPs on reload<commit_after>/*
Copyright 2015 The Kubernetes Authors.

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

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

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

package controller

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
	"strings"
	"syscall"
	"text/template"
	"time"

	"github.com/golang/glog"

	"k8s.io/kubernetes/pkg/util/iptables"
	k8sexec "k8s.io/utils/exec"
)

const (
	iptablesChain   = "KUBE-KEEPALIVED-VIP"
	keepalivedCfg   = "/etc/keepalived/keepalived.conf"
	haproxyCfg      = "/etc/haproxy/haproxy.cfg"
	keepalivedPid   = "/var/run/keepalived.pid"
	keepalivedState = "/var/run/keepalived.state"
	vrrpPid         = "/var/run/vrrp.pid"
)

var (
	keepalivedTmpl = "keepalived.tmpl"
	haproxyTmpl    = "haproxy.tmpl"
)

type keepalived struct {
	iface          string
	ip             string
	netmask        int
	priority       int
	nodes          []string
	neighbors      []string
	useUnicast     bool
	started        bool
	vips           []string
	keepalivedTmpl *template.Template
	haproxyTmpl    *template.Template
	cmd            *exec.Cmd
	ipt            iptables.Interface
	vrid           int
	proxyMode      bool
	notify         string
	releaseVips    bool
}

// WriteCfg creates a new keepalived configuration file.
// In case of an error with the generation it returns the error
func (k *keepalived) WriteCfg(svcs []vip) error {
	w, err := os.Create(keepalivedCfg)
	if err != nil {
		return err
	}
	defer w.Close()

	k.vips = getVIPs(svcs)

	conf := make(map[string]interface{})
	conf["iptablesChain"] = iptablesChain
	conf["iface"] = k.iface
	conf["myIP"] = k.ip
	conf["netmask"] = k.netmask
	conf["svcs"] = svcs
	conf["vips"] = k.vips
	conf["nodes"] = k.neighbors
	conf["priority"] = k.priority
	conf["useUnicast"] = k.useUnicast
	conf["vrid"] = k.vrid
	conf["iface"] = k.iface
	conf["proxyMode"] = k.proxyMode
	conf["vipIsEmpty"] = len(k.vips) == 0
	conf["notify"] = k.notify

	if glog.V(2) {
		b, _ := json.Marshal(conf)
		glog.Infof("%v", string(b))
	}

	err = k.keepalivedTmpl.Execute(w, conf)
	if err != nil {
		return fmt.Errorf("unexpected error creating keepalived.cfg: %v", err)
	}

	if k.proxyMode {
		w, err := os.Create(haproxyCfg)
		if err != nil {
			return err
		}
		defer w.Close()
		err = k.haproxyTmpl.Execute(w, conf)
		if err != nil {
			return fmt.Errorf("unexpected error creating haproxy.cfg: %v", err)
		}
	}

	return nil
}

// getVIPs returns a list of the virtual IP addresses to be used in keepalived
// without duplicates (a service can use more than one port)
func getVIPs(svcs []vip) []string {
	result := []string{}
	for _, svc := range svcs {
		result = appendIfMissing(result, svc.IP)
	}

	return result
}

// Start starts a keepalived process in foreground.
// In case of any error it will terminate the execution with a fatal error
func (k *keepalived) Start() {
	ae, err := k.ipt.EnsureChain(iptables.TableFilter, iptables.Chain(iptablesChain))
	if err != nil {
		glog.Fatalf("unexpected error: %v", err)
	}
	if ae {
		glog.V(2).Infof("chain %v already existed", iptablesChain)
	}

	args := []string{"--dont-fork", "--log-console", "--log-detail"}
	if k.releaseVips {
		args = append(args, "--release-vips")
	}

	k.cmd = exec.Command("keepalived", args...)

	k.cmd.Stdout = os.Stdout
	k.cmd.Stderr = os.Stderr

	k.cmd.SysProcAttr = &syscall.SysProcAttr{
		Setpgid: true,
		Pgid:    0,
	}

	k.started = true

	if err := k.cmd.Run(); err != nil {
		glog.Fatalf("Error starting keepalived: %v", err)
	}
}

// Reload sends SIGHUP to keepalived to reload the configuration.
func (k *keepalived) Reload() error {
	glog.Info("Waiting for keepalived to start")
	for !k.IsRunning() {
		time.Sleep(time.Second)
	}

	glog.Info("reloading keepalived")
	err := syscall.Kill(k.cmd.Process.Pid, syscall.SIGHUP)
	if err != nil {
		return fmt.Errorf("error reloading keepalived: %v", err)
	}

	return nil
}

// Whether keepalived process is currently running
func (k *keepalived) IsRunning() bool {
	if !k.started {
		glog.Error("keepalived not started")
		return false
	}

	if _, err := os.Stat(keepalivedPid); os.IsNotExist(err) {
		glog.Error("Missing keepalived.pid")
		return false
	}

	return true
}

// Whether keepalived child process is currently running and VIPs are assigned
func (k *keepalived) Healthy() error {
	if !k.IsRunning() {
		return fmt.Errorf("keepalived is not running")
	}

	if _, err := os.Stat(vrrpPid); os.IsNotExist(err) {
		return fmt.Errorf("VRRP child process not running")
	}

	b, err := ioutil.ReadFile(keepalivedState)
	if err != nil {
		return err
	}

	master := false
	state := strings.TrimSpace(string(b))
	if strings.Contains(state, "MASTER") {
		master = true
	}

	var out bytes.Buffer
	cmd := exec.Command("ip", "-brief", "address", "show", k.iface, "up")
	cmd.Stderr = os.Stderr
	cmd.Stdout = &out
	cmd.SysProcAttr = &syscall.SysProcAttr{
		Setpgid: true,
		Pgid:    0,
	}

	err = cmd.Run()
	if err != nil {
		return err
	}

	ips := out.String()
	glog.V(3).Infof("Status of %s interface: %s", state, ips)

	for _, vip := range k.vips {
		containsVip := strings.Contains(ips, fmt.Sprintf(" %s/32 ", vip))

		if master && !containsVip {
			return fmt.Errorf("Missing VIP %s on %s", vip, state)
		} else if !master && containsVip {
			return fmt.Errorf("%s should not contain VIP %s", state, vip)
		}
	}

	// All checks successful
	return nil
}

func (k *keepalived) Cleanup() {
	glog.Infof("Cleanup: %s", k.vips)
	for _, vip := range k.vips {
		k.removeVIP(vip)
	}

	err := k.ipt.FlushChain(iptables.TableFilter, iptables.Chain(iptablesChain))
	if err != nil {
		glog.V(2).Infof("unexpected error flushing iptables chain %v: %v", err, iptablesChain)
	}
}

// Stop stop keepalived process
func (k *keepalived) Stop() {
	k.Cleanup()

	err := syscall.Kill(k.cmd.Process.Pid, syscall.SIGTERM)
	if err != nil {
		glog.Errorf("error stopping keepalived: %v", err)
	}
}

func (k *keepalived) removeVIP(vip string) {
	glog.Infof("removing configured VIP %v", vip)
	out, err := k8sexec.New().Command("ip", "addr", "del", vip+"/32", "dev", k.iface).CombinedOutput()
	if err != nil {
		glog.V(2).Infof("Error removing VIP %s: %v\n%s", vip, err, out)
	}
}

func (k *keepalived) loadTemplates() error {
	tmpl, err := template.ParseFiles(keepalivedTmpl)
	if err != nil {
		return err
	}
	k.keepalivedTmpl = tmpl

	tmpl, err = template.ParseFiles(haproxyTmpl)
	if err != nil {
		return err
	}
	k.haproxyTmpl = tmpl

	return nil
}
<|endoftext|>