<commit_before>package sessions

import (
	"encoding/json"
	"errors"
	"net/http"
	"time"

	"github.com/cozy/cozy-stack/pkg/config"
	"github.com/cozy/cozy-stack/pkg/consts"
	"github.com/cozy/cozy-stack/pkg/couchdb"
	"github.com/cozy/cozy-stack/pkg/crypto"
	"github.com/cozy/cozy-stack/pkg/instance"
	"github.com/cozy/cozy-stack/pkg/utils"
	"github.com/labstack/echo"
)

// SessionCookieName: name of the cookie created by cozy
const SessionCookieName = "cozysessid"

const (
	// SessionMaxAge is the maximum duration of the session in seconds
	SessionMaxAge = 7 * 24 * 60 * 60

	// AppCookieMaxAge is the maximum duration of the application cookie in
	// seconds
	AppCookieMaxAge = 24 * 60 * 60
)

var (
	// ErrNoCookie is returned by GetSession if there is no cookie
	ErrNoCookie = errors.New("No session cookie")
	// ErrExpired is returned when the session has expired
	ErrExpired = errors.New("Session expired")
	// ErrInvalidID is returned by GetSession if the cookie contains wrong ID
	ErrInvalidID = errors.New("Session cookie has wrong ID")
)

// A Session is an instance opened in a browser
type Session struct {
	Instance *instance.Instance `json:"-"`
	DocID    string             `json:"_id,omitempty"`
	DocRev   string             `json:"_rev,omitempty"`
	LastSeen time.Time          `json:"last_seen,omitempty"`
}

// DocType implements couchdb.Doc
func (s *Session) DocType() string { return consts.Sessions }

// ID implements couchdb.Doc
func (s *Session) ID() string { return s.DocID }

// SetID implements couchdb.Doc
func (s *Session) SetID(v string) { s.DocID = v }

// Rev implements couchdb.Doc
func (s *Session) Rev() string { return s.DocRev }

// SetRev implements couchdb.Doc
func (s *Session) SetRev(v string) { s.DocRev = v }

// Clone implements couchdb.Doc
func (s *Session) Clone() couchdb.Doc {
	cloned := *s
	if cloned.Instance != nil {
		tmp := *s.Instance
		cloned.Instance = &tmp
	}
	return &cloned
}

// ensure Session implements couchdb.Doc
var _ couchdb.Doc = (*Session)(nil)

// OlderThan checks if a session last seen is older than t from now
func (s *Session) OlderThan(t time.Duration) bool {
	return time.Now().After(s.LastSeen.Add(t))
}

// New creates a session in couchdb for the given instance
func New(i *instance.Instance) (*Session, error) {
	var s = &Session{
		Instance: i,
		LastSeen: time.Now(),
	}
	if err := couchdb.CreateDoc(i, s); err != nil {
		return nil, err
	}
	getCache().Set(i.Domain, s.DocID, s)
	return s, nil
}

// Get fetches the session
func Get(i *instance.Instance, sessionID string) (*Session, error) {
	updateCache := false

	s := getCache().Get(i.Domain, sessionID)
	if s == nil {
		s = &Session{}
		err := couchdb.GetDoc(i, consts.Sessions, sessionID, s)
		if couchdb.IsNotFoundError(err) {
			return nil, ErrInvalidID
		}
		if err != nil {
			return nil, err
		}
		updateCache = true
	}
	s.Instance = i

	// If the session is older than the session max age, it has expired and
	// should be deleted.
	if s.OlderThan(SessionMaxAge * time.Second) {
		err := couchdb.DeleteDoc(i, s)
		if err != nil {
			i.Logger().Warn("[session] Failed to delete expired session:", err)
		}
		getCache().Revoke(i.Domain, s.DocID)
		return nil, ErrExpired
	}

	// If the session is older than half its half-life, update the LastSeen date.
	if s.OlderThan((SessionMaxAge * time.Second) / 2) {
		lastSeen := s.LastSeen
		s.LastSeen = time.Now()
		err := couchdb.UpdateDoc(i, s)
		if err != nil {
			i.Logger().Warn("[session] Failed to update session last seen:", err)
			s.LastSeen = lastSeen
			updateCache = false
		} else {
			updateCache = true
		}
	}

	if updateCache {
		getCache().Set(i.Domain, s.DocID, s)
	}

	return s, nil
}

// FromCookie retrieves the session from a echo.Context cookies.
func FromCookie(c echo.Context, i *instance.Instance) (*Session, error) {
	cookie, err := c.Cookie(SessionCookieName)
	if err != nil || cookie.Value == "" {
		return nil, ErrNoCookie
	}

	sessionID, err := crypto.DecodeAuthMessage(cookieMACConfig(i, SessionMaxAge),
		[]byte(cookie.Value), nil)
	if err != nil {
		return nil, err
	}

	return Get(i, string(sessionID))
}

// FromAppCookie retrives the session from an application submain cookie.
func FromAppCookie(c echo.Context, i *instance.Instance, slug string) (*Session, error) {
	if config.GetConfig().Subdomains == config.FlatSubdomains {
		cookie, err := c.Cookie(SessionCookieName)
		if err != nil || cookie.Value == "" {
			return nil, ErrNoCookie
		}

		sessionID, err := crypto.DecodeAuthMessage(cookieMACConfig(i, AppCookieMaxAge),
			[]byte(cookie.Value), []byte(slug))
		if err != nil {
			return nil, err
		}

		return Get(i, string(sessionID))
	}
	return FromCookie(c, i)
}

// GetAll returns all the active sessions
func GetAll(inst *instance.Instance) ([]*Session, error) {
	var sessions []*Session
	if err := couchdb.GetAllDocs(inst, consts.Sessions, nil, &sessions); err != nil {
		return nil, err
	}
	return sessions, nil
}

// Delete is a function to delete the session in couchdb,
// and returns a cookie with a negative MaxAge to clear it
func (s *Session) Delete(i *instance.Instance) *http.Cookie {
	err := couchdb.DeleteDoc(i, s)
	getCache().Revoke(i.Domain, s.DocID)
	if err != nil {
		i.Logger().Error("[session] Failed to delete session:", err)
	}
	return &http.Cookie{
		Name:   SessionCookieName,
		Value:  "",
		MaxAge: -1,
		Path:   "/",
		Domain: utils.StripPort("." + i.Domain),
	}
}

// ToCookie returns an http.Cookie for this Session
func (s *Session) ToCookie() (*http.Cookie, error) {
	encoded, err := crypto.EncodeAuthMessage(cookieMACConfig(s.Instance, SessionMaxAge), []byte(s.ID()), nil)
	if err != nil {
		return nil, err
	}

	return &http.Cookie{
		Name:     SessionCookieName,
		Value:    string(encoded),
		MaxAge:   SessionMaxAge,
		Path:     "/",
		Domain:   utils.StripPort("." + s.Instance.Domain),
		Secure:   !s.Instance.Dev,
		HttpOnly: true,
	}, nil
}

// ToAppCookie returns an http.Cookie for this Session on an app subdomain
func (s *Session) ToAppCookie(domain, slug string) (*http.Cookie, error) {
	encoded, err := crypto.EncodeAuthMessage(cookieMACConfig(s.Instance, AppCookieMaxAge), []byte(s.ID()), []byte(slug))
	if err != nil {
		return nil, err
	}

	return &http.Cookie{
		Name:     SessionCookieName,
		Value:    string(encoded),
		MaxAge:   AppCookieMaxAge,
		Path:     "/",
		Domain:   utils.StripPort(domain),
		Secure:   !s.Instance.Dev,
		HttpOnly: true,
	}, nil
}

// DeleteOthers will remove all sessions except the one given in parameter.
func DeleteOthers(i *instance.Instance, selfSessionID string) error {
	var sessions []*Session
	err := couchdb.ForeachDocs(i, consts.Sessions, func(data []byte) error {
		var s Session
		if err := json.Unmarshal(data, &s); err != nil {
			return err
		}
		sessions = append(sessions, &s)
		return nil
	})
	if err != nil {
		return err
	}
	for _, s := range sessions {
		if s.ID() != selfSessionID {
			s.Delete(i)
		}
	}
	return nil
}

// cookieMACConfig returns the options to authenticate the session cookie.
//
// We rely on a MACed cookie value, without additional encryption of the
// message since it should not contain critical private informations and is
// protected by HTTPs (secure cookie).
//
// About MaxLength, for a session of size 100 bytes
//
//       8 bytes time
//   +  32 bytes HMAC-SHA256
//   + 100 bytes session
//   + base64 encoding (4*n/3)
//   < 200 bytes
//
// 256 bytes should be sufficient enough to support any type of session.
//
func cookieMACConfig(i *instance.Instance, maxAge int64) *crypto.MACConfig {
	return &crypto.MACConfig{
		Name:   SessionCookieName,
		Key:    i.SessionSecret,
		MaxAge: maxAge,
		MaxLen: 256,
	}
}
<commit_msg>No maxage on the main session cookie to avoid sync issues<commit_after>package sessions

import (
	"encoding/json"
	"errors"
	"net/http"
	"time"

	"github.com/cozy/cozy-stack/pkg/config"
	"github.com/cozy/cozy-stack/pkg/consts"
	"github.com/cozy/cozy-stack/pkg/couchdb"
	"github.com/cozy/cozy-stack/pkg/crypto"
	"github.com/cozy/cozy-stack/pkg/instance"
	"github.com/cozy/cozy-stack/pkg/utils"
	"github.com/labstack/echo"
)

// SessionCookieName: name of the cookie created by cozy
const SessionCookieName = "cozysessid"

const (
	// SessionMaxAge is the maximum duration of the session in seconds
	SessionMaxAge = 7 * 24 * 60 * 60

	// AppCookieMaxAge is the maximum duration of the application cookie in
	// seconds
	AppCookieMaxAge = 24 * 60 * 60
)

var (
	// ErrNoCookie is returned by GetSession if there is no cookie
	ErrNoCookie = errors.New("No session cookie")
	// ErrExpired is returned when the session has expired
	ErrExpired = errors.New("Session expired")
	// ErrInvalidID is returned by GetSession if the cookie contains wrong ID
	ErrInvalidID = errors.New("Session cookie has wrong ID")
)

// A Session is an instance opened in a browser
type Session struct {
	Instance *instance.Instance `json:"-"`
	DocID    string             `json:"_id,omitempty"`
	DocRev   string             `json:"_rev,omitempty"`
	LastSeen time.Time          `json:"last_seen,omitempty"`
}

// DocType implements couchdb.Doc
func (s *Session) DocType() string { return consts.Sessions }

// ID implements couchdb.Doc
func (s *Session) ID() string { return s.DocID }

// SetID implements couchdb.Doc
func (s *Session) SetID(v string) { s.DocID = v }

// Rev implements couchdb.Doc
func (s *Session) Rev() string { return s.DocRev }

// SetRev implements couchdb.Doc
func (s *Session) SetRev(v string) { s.DocRev = v }

// Clone implements couchdb.Doc
func (s *Session) Clone() couchdb.Doc {
	cloned := *s
	if cloned.Instance != nil {
		tmp := *s.Instance
		cloned.Instance = &tmp
	}
	return &cloned
}

// ensure Session implements couchdb.Doc
var _ couchdb.Doc = (*Session)(nil)

// OlderThan checks if a session last seen is older than t from now
func (s *Session) OlderThan(t time.Duration) bool {
	return time.Now().After(s.LastSeen.Add(t))
}

// New creates a session in couchdb for the given instance
func New(i *instance.Instance) (*Session, error) {
	var s = &Session{
		Instance: i,
		LastSeen: time.Now(),
	}
	if err := couchdb.CreateDoc(i, s); err != nil {
		return nil, err
	}
	getCache().Set(i.Domain, s.DocID, s)
	return s, nil
}

// Get fetches the session
func Get(i *instance.Instance, sessionID string) (*Session, error) {
	updateCache := false

	s := getCache().Get(i.Domain, sessionID)
	if s == nil {
		s = &Session{}
		err := couchdb.GetDoc(i, consts.Sessions, sessionID, s)
		if couchdb.IsNotFoundError(err) {
			return nil, ErrInvalidID
		}
		if err != nil {
			return nil, err
		}
		updateCache = true
	}
	s.Instance = i

	// If the session is older than the session max age, it has expired and
	// should be deleted.
	if s.OlderThan(SessionMaxAge * time.Second) {
		err := couchdb.DeleteDoc(i, s)
		if err != nil {
			i.Logger().Warn("[session] Failed to delete expired session:", err)
		}
		getCache().Revoke(i.Domain, s.DocID)
		return nil, ErrExpired
	}

	// In order to avoid too many updates of the session document, we have an
	// update period of one day for the `last_seen` date, which is a good enough
	// granularity.
	if s.OlderThan(24 * time.Hour) {
		lastSeen := s.LastSeen
		s.LastSeen = time.Now()
		err := couchdb.UpdateDoc(i, s)
		if err != nil {
			i.Logger().Warn("[session] Failed to update session last seen:", err)
			s.LastSeen = lastSeen
			updateCache = false
		} else {
			updateCache = true
		}
	}

	if updateCache {
		getCache().Set(i.Domain, s.DocID, s)
	}

	return s, nil
}

// FromCookie retrieves the session from a echo.Context cookies.
func FromCookie(c echo.Context, i *instance.Instance) (*Session, error) {
	cookie, err := c.Cookie(SessionCookieName)
	if err != nil || cookie.Value == "" {
		return nil, ErrNoCookie
	}

	sessionID, err := crypto.DecodeAuthMessage(cookieMACConfig(i, SessionMaxAge),
		[]byte(cookie.Value), nil)
	if err != nil {
		return nil, err
	}

	return Get(i, string(sessionID))
}

// FromAppCookie retrives the session from an application submain cookie.
func FromAppCookie(c echo.Context, i *instance.Instance, slug string) (*Session, error) {
	if config.GetConfig().Subdomains == config.FlatSubdomains {
		cookie, err := c.Cookie(SessionCookieName)
		if err != nil || cookie.Value == "" {
			return nil, ErrNoCookie
		}

		sessionID, err := crypto.DecodeAuthMessage(cookieMACConfig(i, AppCookieMaxAge),
			[]byte(cookie.Value), []byte(slug))
		if err != nil {
			return nil, err
		}

		return Get(i, string(sessionID))
	}
	return FromCookie(c, i)
}

// GetAll returns all the active sessions
func GetAll(inst *instance.Instance) ([]*Session, error) {
	var sessions []*Session
	if err := couchdb.GetAllDocs(inst, consts.Sessions, nil, &sessions); err != nil {
		return nil, err
	}
	return sessions, nil
}

// Delete is a function to delete the session in couchdb,
// and returns a cookie with a negative MaxAge to clear it
func (s *Session) Delete(i *instance.Instance) *http.Cookie {
	err := couchdb.DeleteDoc(i, s)
	getCache().Revoke(i.Domain, s.DocID)
	if err != nil {
		i.Logger().Error("[session] Failed to delete session:", err)
	}
	return &http.Cookie{
		Name:   SessionCookieName,
		Value:  "",
		MaxAge: -1,
		Path:   "/",
		Domain: utils.StripPort("." + i.Domain),
	}
}

// ToCookie returns an http.Cookie for this Session
func (s *Session) ToCookie() (*http.Cookie, error) {
	encoded, err := crypto.EncodeAuthMessage(cookieMACConfig(s.Instance, SessionMaxAge), []byte(s.ID()), nil)
	if err != nil {
		return nil, err
	}

	return &http.Cookie{
		Name:     SessionCookieName,
		Value:    string(encoded),
		MaxAge:   0, // session cookie without maxage
		Path:     "/",
		Domain:   utils.StripPort("." + s.Instance.Domain),
		Secure:   !s.Instance.Dev,
		HttpOnly: true,
	}, nil
}

// ToAppCookie returns an http.Cookie for this Session on an app subdomain
func (s *Session) ToAppCookie(domain, slug string) (*http.Cookie, error) {
	encoded, err := crypto.EncodeAuthMessage(cookieMACConfig(s.Instance, AppCookieMaxAge), []byte(s.ID()), []byte(slug))
	if err != nil {
		return nil, err
	}

	return &http.Cookie{
		Name:     SessionCookieName,
		Value:    string(encoded),
		MaxAge:   AppCookieMaxAge,
		Path:     "/",
		Domain:   utils.StripPort(domain),
		Secure:   !s.Instance.Dev,
		HttpOnly: true,
	}, nil
}

// DeleteOthers will remove all sessions except the one given in parameter.
func DeleteOthers(i *instance.Instance, selfSessionID string) error {
	var sessions []*Session
	err := couchdb.ForeachDocs(i, consts.Sessions, func(data []byte) error {
		var s Session
		if err := json.Unmarshal(data, &s); err != nil {
			return err
		}
		sessions = append(sessions, &s)
		return nil
	})
	if err != nil {
		return err
	}
	for _, s := range sessions {
		if s.ID() != selfSessionID {
			s.Delete(i)
		}
	}
	return nil
}

// cookieMACConfig returns the options to authenticate the session cookie.
//
// We rely on a MACed cookie value, without additional encryption of the
// message since it should not contain critical private informations and is
// protected by HTTPs (secure cookie).
//
// About MaxLength, for a session of size 100 bytes
//
//       8 bytes time
//   +  32 bytes HMAC-SHA256
//   + 100 bytes session
//   + base64 encoding (4*n/3)
//   < 200 bytes
//
// 256 bytes should be sufficient enough to support any type of session.
//
func cookieMACConfig(i *instance.Instance, maxAge int64) *crypto.MACConfig {
	return &crypto.MACConfig{
		Name:   SessionCookieName,
		Key:    i.SessionSecret,
		MaxAge: maxAge,
		MaxLen: 256,
	}
}
<|endoftext|>


<commit_before>package reset

var (
	resetFns, recoverFns []func()
)

// ClearInternal used by reset package unit tests only, never call ClearInternal outside reset package
func ClearInternal() {
	resetFns = nil
	recoverFns = nil
}

// Register() register two function to cooperate testing reset/recover process.
// Unlike reset.Add(), which register a reset function to undo things for
// current call, after current test function finished. Register() normally
// register at package init() function to be part of testing cycle. Most
// reset.Add() can be replaced by Register().
//
// Register() is better than Add():
//  1. Better performance, when application runs in non-test mode, do not need
//  over and over calling reset.Add().
//  2. Add() not work well with static inited items, such as ert tree. Ert tree
//  need reset itself to give a clean environment for next test. But unit test
//  also depends on some pre-registered items in the ert tree, simply reset the
//  whole ert tree can not work, fine control which ert entry need reset is too
//  complex. Use Register() each package can re-register static ert items in
//  the recover function.
//
// fReset function called in ginkgo AfterEach() stage (actually in
// reset.Disable()), fReset function called in reversed register order. After
// all fReset() function called, fRecover() functions called by register order.
//
// Both fReset and fRecover can be nil.
//
// fRecover function run immediately on Register to do init job.
func Register(fReset func(), fRecover func()) {
	if fReset != nil {
		resetFns = append(resetFns, fReset)
	}
	if fRecover != nil {
		recoverFns = append(recoverFns, fRecover)
	}

	if fRecover != nil {
		fRecover()
	}
}

func execResetRecovers() {
	for i := len(resetFns) - 1; i >= 0; i-- {
		resetFns[i]()
	}

	for _, f := range recoverFns {
		f()
	}
}
<commit_msg>reset.Register() do not register on non-test mode<commit_after>package reset

var (
	resetFns, recoverFns []func()
)

// ClearInternal used by reset package unit tests only, never call ClearInternal outside reset package
func ClearInternal() {
	resetFns = nil
	recoverFns = nil
}

// Register() register two function to cooperate testing reset/recover process.
// Unlike reset.Add(), which register a reset function to undo things for
// current call, after current test function finished. Register() normally
// register at package init() function to be part of testing cycle. Most
// reset.Add() can be replaced by Register().
//
// Register() is better than Add():
//  1. Better performance, when application runs in non-test mode, do not need
//  over and over calling reset.Add().
//  2. Add() not work well with static inited items, such as ert tree. Ert tree
//  need reset itself to give a clean environment for next test. But unit test
//  also depends on some pre-registered items in the ert tree, simply reset the
//  whole ert tree can not work, fine control which ert entry need reset is too
//  complex. Use Register() each package can re-register static ert items in
//  the recover function.
//
// fReset function called in ginkgo AfterEach() stage (actually in
// reset.Disable()), fReset function called in reversed register order. After
// all fReset() function called, fRecover() functions called by register order.
//
// Both fReset and fRecover can be nil.
//
// fRecover function run immediately on Register to do init job.
func Register(fReset func(), fRecover func()) {
	if TestMode() {
		if fReset != nil {
			resetFns = append(resetFns, fReset)
		}
		if fRecover != nil {
			recoverFns = append(recoverFns, fRecover)
		}
	}

	if fRecover != nil {
		fRecover()
	}
}

func execResetRecovers() {
	for i := len(resetFns) - 1; i >= 0; i-- {
		resetFns[i]()
	}

	for _, f := range recoverFns {
		f()
	}
}
<|endoftext|>


<commit_before>package birect

import (
	"encoding/json"
	"errors"
	"io"
	"io/ioutil"
	"log"
	"sync/atomic"

	"github.com/golang/protobuf/proto"
	"github.com/marcuswestin/go-birect/internal/wire"
	"github.com/marcuswestin/go-errs"
	"github.com/marcuswestin/go-ws"
)

// Log lets you control logging output.
var Log = func(conn *Conn, argv ...interface{}) {}

// LogToStdout causes all birect connections to star
// logging to stdout
func LogToStdout() {
	Log = func(conn *Conn, argv ...interface{}) {
		argv = append([]interface{}{"birect", conn.Info}, argv...)
		log.Println(argv...)
	}
}

// Conn represents a persistent bi-directional connection between
// a birect client and a birect server.
type Conn struct {
	Info      Info
	wsConn    *ws.Conn
	lastReqID reqID
	resChans  map[reqID]resChan
	jsonReqHandlerMap
	protoReqHandlerMap
}

// Log logs the given arguments, along with contextual information about the Conn.
func (c *Conn) Log(args ...interface{}) {
	Log(c, args...)
}

// Internal
///////////

type reqID uint32
type resChan chan *wire.Response

func newConn(wsConn *ws.Conn, jsonHandlers jsonReqHandlerMap, protoHandlers protoReqHandlerMap) *Conn {
	return &Conn{newInfo(), wsConn, 0, make(map[reqID]resChan, 1), jsonHandlers, protoHandlers}
}

type request interface {
	// Request sending side
	encode() ([]byte, error)
	// Request handling side
	ParseParams(valPrt interface{})
}
type response interface {
	// Responding side
	dataType() wire.DataType
	encode() ([]byte, error)
	// Response receiving side
}

// Internal - Outgoing wrappers
///////////////////////////////

func (c *Conn) sendRequestAndWaitForResponse(reqID reqID, wireReq *wire.Request, resValPtr interface{}) (err error) {
	c.resChans[reqID] = make(resChan)
	defer delete(c.resChans, reqID)
	defer func() { err = errs.Wrap(err, nil) }()

	c.Log("REQ", wireReq.Name, "ReqID:", reqID, "len:", len(wireReq.Data))
	err = c.sendWrapper(&wire.Wrapper{
		Content: &wire.Wrapper_Request{Request: wireReq},
	})
	if err != nil {
		return
	}

	wireRes := <-c.resChans[reqID]
	c.Log("RCV", wireReq.Name, "ReqID:", reqID, "DataType:", wireRes.Type, "len(Data):", len(wireRes.Data))

	if wireRes.IsError {
		return errors.New(string(wireRes.Data))
	}

	if wireRes.Data == nil {
		return nil
	}

	switch wireRes.Type {
	case wire.DataType_JSON:
		if resValPtr == nil {
			err = errs.New(errs.Info{"data": string(wireRes.Data)}, "Expected struct pointer to deserialize JSON data into")
			return
		}
		return json.Unmarshal(wireRes.Data, resValPtr)
	case wire.DataType_Proto:
		if resValPtr == nil {
			err = errs.New(errs.Info{"len": len(wireRes.Data)}, "Expected struct pointer to deserialize protobuf data into")
			return
		}
		return proto.Unmarshal(wireRes.Data, resValPtr.(proto.Message))
	default:
		return errors.New("Bad response wire type: " + wireRes.Type.String())
	}
}
func (c *Conn) sendResponse(wireReq *wire.Request, response response) {
	wireRes := &wire.Response{ReqId: wireReq.ReqId}
	data, err := response.encode()
	if err != nil {
		panic(errs.Wrap(err, nil, "Unable to encode response"))
	}
	wireRes.Type = response.dataType()
	wireRes.Data = data
	err = c.sendWrapper(&wire.Wrapper{
		Content: &wire.Wrapper_Response{Response: wireRes},
	})
	if err != nil {
		panic(errs.Wrap(err, nil, "Unable to send response"))
	}
}
func (c *Conn) sendErrorResponse(wireReq *wire.Request, err error) {
	var publicMessage string
	if errsErr, ok := err.(errs.Err); ok {
		publicMessage = errsErr.PublicMsg()
	}
	if publicMessage == "" {
		publicMessage = DefaultPublicErrorMessage
	}
	wireRes := &wire.Response{
		ReqId:   wireReq.ReqId,
		IsError: true,
		Type:    wire.DataType_Text,
		Data:    []byte(publicMessage),
	}
	c.Log("Req ERROR", wireReq.ReqId, err)
	c.sendWrapper(&wire.Wrapper{
		Content: &wire.Wrapper_Response{Response: wireRes},
	})
}
func (c *Conn) nextReqID() reqID {
	rawReqID := atomic.AddUint32((*uint32)(&c.lastReqID), 1)
	return reqID(rawReqID)
}
func (c *Conn) sendWrapper(wrapper *wire.Wrapper) (err error) {
	wireData, err := proto.Marshal(wrapper)
	if err != nil {
		return
	}
	c.Log("SND Wrapper len:", len(wireData), wrapper)
	return c.wsConn.SendBinary(wireData)
}

// Internal - incoming wrappers
///////////////////////////////

func (c *Conn) readAndHandleWireWrapperReader(reader io.Reader) {
	data, err := ioutil.ReadAll(reader)
	if err != nil {
		panic(errs.Wrap(err, nil, "Unable to read wrapper"))
	}
	c.readAndHandleWireWrapper(data)
}
func (c *Conn) readAndHandleWireWrapper(data []byte) {
	if len(data) == 0 {
		panic(errs.New(nil, "Empty data"))
	}

	var wireWrapper wire.Wrapper
	err := proto.Unmarshal(data, &wireWrapper)
	if err != nil {
		panic(errs.Wrap(err, nil, "Unable to decode wire wrapper"))
	}

	c.Log("readAndHandleWireWrapper", wireWrapper.Content)
	switch content := wireWrapper.Content.(type) {
	case *wire.Wrapper_Message:
		c.handleMessage(content.Message)
	case *wire.Wrapper_Request:
		c.handleRequest(content.Request)
	case *wire.Wrapper_Response:
		c.handleResponse(content.Response)
	default:
		panic(errs.New(errs.Info{"Wrapper": wireWrapper}, "Unknown wire wrapper content type"))
	}
}
func (c *Conn) handleMessage(msg *wire.Message) {
	panic(errs.New(nil, "TODO: handleMessage"))
}
func (c *Conn) handleRequest(wireReq *wire.Request) {
	c.Log("HANDLE REQ", wireReq)
	switch wireReq.Type {
	case wire.DataType_JSON:
		c.handleJSONWireReq(wireReq)
	case wire.DataType_Proto:
		c.handleProtoWireReq(wireReq)
	default:
		c.sendErrorResponse(wireReq, errs.New(errs.Info{"Type": wireReq.Type}, "Bad wireReq.Type"))
	}
}
func (c *Conn) handleResponse(wireRes *wire.Response) {
	c.Log("HANDLE RES", wireRes)
	c.resChans[reqID(wireRes.ReqId)] <- wireRes
}
<commit_msg>Syncronous handling of requests will cause the connection to freeze up if we are sending multiple nested back-and-forth requests and responses. Make request handlers async.<commit_after>package birect

import (
	"encoding/json"
	"errors"
	"io"
	"io/ioutil"
	"log"
	"sync/atomic"

	"github.com/golang/protobuf/proto"
	"github.com/marcuswestin/go-birect/internal/wire"
	"github.com/marcuswestin/go-errs"
	"github.com/marcuswestin/go-ws"
)

// Log lets you control logging output.
var Log = func(conn *Conn, argv ...interface{}) {}

// LogToStdout causes all birect connections to star
// logging to stdout
func LogToStdout() {
	Log = func(conn *Conn, argv ...interface{}) {
		argv = append([]interface{}{"birect", conn.Info}, argv...)
		log.Println(argv...)
	}
}

// Conn represents a persistent bi-directional connection between
// a birect client and a birect server.
type Conn struct {
	Info      Info
	wsConn    *ws.Conn
	lastReqID reqID
	resChans  map[reqID]resChan
	jsonReqHandlerMap
	protoReqHandlerMap
}

// Log logs the given arguments, along with contextual information about the Conn.
func (c *Conn) Log(args ...interface{}) {
	Log(c, args...)
}

// Internal
///////////

type reqID uint32
type resChan chan *wire.Response

func newConn(wsConn *ws.Conn, jsonHandlers jsonReqHandlerMap, protoHandlers protoReqHandlerMap) *Conn {
	return &Conn{newInfo(), wsConn, 0, make(map[reqID]resChan, 1), jsonHandlers, protoHandlers}
}

type request interface {
	// Request sending side
	encode() ([]byte, error)
	// Request handling side
	ParseParams(valPrt interface{})
}
type response interface {
	// Responding side
	dataType() wire.DataType
	encode() ([]byte, error)
	// Response receiving side
}

// Internal - Outgoing wrappers
///////////////////////////////

func (c *Conn) sendRequestAndWaitForResponse(reqID reqID, wireReq *wire.Request, resValPtr interface{}) (err error) {
	c.resChans[reqID] = make(resChan)
	defer delete(c.resChans, reqID)
	defer func() { err = errs.Wrap(err, nil) }()

	c.Log("REQ", wireReq.Name, "ReqID:", reqID, "len:", len(wireReq.Data))
	err = c.sendWrapper(&wire.Wrapper{
		Content: &wire.Wrapper_Request{Request: wireReq},
	})
	if err != nil {
		return
	}

	wireRes := <-c.resChans[reqID]
	c.Log("RCV", wireReq.Name, "ReqID:", reqID, "DataType:", wireRes.Type, "len(Data):", len(wireRes.Data))

	if wireRes.IsError {
		return errors.New(string(wireRes.Data))
	}

	if wireRes.Data == nil {
		return nil
	}

	switch wireRes.Type {
	case wire.DataType_JSON:
		if resValPtr == nil {
			err = errs.New(errs.Info{"data": string(wireRes.Data)}, "Expected struct pointer to deserialize JSON data into")
			return
		}
		return json.Unmarshal(wireRes.Data, resValPtr)
	case wire.DataType_Proto:
		if resValPtr == nil {
			err = errs.New(errs.Info{"len": len(wireRes.Data)}, "Expected struct pointer to deserialize protobuf data into")
			return
		}
		return proto.Unmarshal(wireRes.Data, resValPtr.(proto.Message))
	default:
		return errors.New("Bad response wire type: " + wireRes.Type.String())
	}
}
func (c *Conn) sendResponse(wireReq *wire.Request, response response) {
	wireRes := &wire.Response{ReqId: wireReq.ReqId}
	data, err := response.encode()
	if err != nil {
		panic(errs.Wrap(err, nil, "Unable to encode response"))
	}
	wireRes.Type = response.dataType()
	wireRes.Data = data
	err = c.sendWrapper(&wire.Wrapper{
		Content: &wire.Wrapper_Response{Response: wireRes},
	})
	if err != nil {
		panic(errs.Wrap(err, nil, "Unable to send response"))
	}
}
func (c *Conn) sendErrorResponse(wireReq *wire.Request, err error) {
	var publicMessage string
	if errsErr, ok := err.(errs.Err); ok {
		publicMessage = errsErr.PublicMsg()
	}
	if publicMessage == "" {
		publicMessage = DefaultPublicErrorMessage
	}
	wireRes := &wire.Response{
		ReqId:   wireReq.ReqId,
		IsError: true,
		Type:    wire.DataType_Text,
		Data:    []byte(publicMessage),
	}
	c.Log("Req ERROR", wireReq.ReqId, err)
	c.sendWrapper(&wire.Wrapper{
		Content: &wire.Wrapper_Response{Response: wireRes},
	})
}
func (c *Conn) nextReqID() reqID {
	rawReqID := atomic.AddUint32((*uint32)(&c.lastReqID), 1)
	return reqID(rawReqID)
}
func (c *Conn) sendWrapper(wrapper *wire.Wrapper) (err error) {
	wireData, err := proto.Marshal(wrapper)
	if err != nil {
		return
	}
	c.Log("SND Wrapper len:", len(wireData), wrapper)
	return c.wsConn.SendBinary(wireData)
}

// Internal - incoming wrappers
///////////////////////////////

func (c *Conn) readAndHandleWireWrapperReader(reader io.Reader) {
	data, err := ioutil.ReadAll(reader)
	if err != nil {
		panic(errs.Wrap(err, nil, "Unable to read wrapper"))
	}
	c.readAndHandleWireWrapper(data)
}
func (c *Conn) readAndHandleWireWrapper(data []byte) {
	if len(data) == 0 {
		panic(errs.New(nil, "Empty data"))
	}

	var wireWrapper wire.Wrapper
	err := proto.Unmarshal(data, &wireWrapper)
	if err != nil {
		panic(errs.Wrap(err, nil, "Unable to decode wire wrapper"))
	}

	c.Log("readAndHandleWireWrapper", wireWrapper.Content)
	switch content := wireWrapper.Content.(type) {
	case *wire.Wrapper_Message:
		c.handleMessage(content.Message)
	case *wire.Wrapper_Request:
		c.handleRequest(content.Request)
	case *wire.Wrapper_Response:
		c.handleResponse(content.Response)
	default:
		panic(errs.New(errs.Info{"Wrapper": wireWrapper}, "Unknown wire wrapper content type"))
	}
}
func (c *Conn) handleMessage(msg *wire.Message) {
	panic(errs.New(nil, "TODO: handleMessage"))
}
func (c *Conn) handleRequest(wireReq *wire.Request) {
	c.Log("HANDLE REQ", wireReq)
	switch wireReq.Type {
	case wire.DataType_JSON:
		go c.handleJSONWireReq(wireReq)
	case wire.DataType_Proto:
		go c.handleProtoWireReq(wireReq)
	default:
		c.sendErrorResponse(wireReq, errs.New(errs.Info{"Type": wireReq.Type}, "Bad wireReq.Type"))
	}
}
func (c *Conn) handleResponse(wireRes *wire.Response) {
	c.Log("HANDLE RES", wireRes)
	c.resChans[reqID(wireRes.ReqId)] <- wireRes
}
<|endoftext|>


<commit_before>/*
 * This file is part of the KubeVirt project
 *
 * 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.
 *
 * Copyright 2017 Red Hat, Inc.
 *
 */

package virtwrap

import (
	"encoding/xml"
	"fmt"

	"github.com/golang/mock/gomock"
	"github.com/libvirt/libvirt-go"
	. "github.com/onsi/ginkgo"
	"github.com/onsi/ginkgo/extensions/table"
	. "github.com/onsi/gomega"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

	"kubevirt.io/kubevirt/pkg/api/v1"
	"kubevirt.io/kubevirt/pkg/log"
	"kubevirt.io/kubevirt/pkg/virt-launcher/virtwrap/api"
	"kubevirt.io/kubevirt/pkg/virt-launcher/virtwrap/cli"
	"kubevirt.io/kubevirt/pkg/virt-launcher/virtwrap/network"
)

var _ = Describe("Manager", func() {
	var mockConn *cli.MockConnection
	var mockDomain *cli.MockVirDomain
	var ctrl *gomock.Controller
	testVmName := "testvmi"
	testNamespace := "testnamespace"
	testDomainName := fmt.Sprintf("%s_%s", testNamespace, testVmName)

	log.Log.SetIOWriter(GinkgoWriter)

	BeforeEach(func() {
		ctrl = gomock.NewController(GinkgoT())
		mockConn = cli.NewMockConnection(ctrl)
		mockDomain = cli.NewMockVirDomain(ctrl)
		// Make sure that we always free the domain after use
		mockDomain.EXPECT().Free()
	})

	expectIsolationDetectionForVMI := func(vmi *v1.VirtualMachineInstance) *api.DomainSpec {
		domain := &api.Domain{}
		c := &api.ConverterContext{
			VirtualMachine: vmi,
			UseEmulation:   true,
		}
		Expect(api.Convert_v1_VirtualMachine_To_api_Domain(vmi, domain, c)).To(Succeed())
		api.SetObjectDefaults_Domain(domain)

		return &domain.Spec
	}

	Context("on successful VirtualMachineInstance sync", func() {
		It("should define and start a new VirtualMachineInstance", func() {
			StubOutNetworkForTest()
			vmi := newVMI(testNamespace, testVmName)
			mockConn.EXPECT().LookupDomainByName(testDomainName).Return(mockDomain, libvirt.Error{Code: libvirt.ERR_NO_DOMAIN})

			domainSpec := expectIsolationDetectionForVMI(vmi)

			xml, err := xml.Marshal(domainSpec)
			Expect(err).To(BeNil())
			mockConn.EXPECT().DomainDefineXML(string(xml)).Return(mockDomain, nil)
			mockDomain.EXPECT().GetState().Return(libvirt.DOMAIN_SHUTDOWN, 1, nil)
			mockDomain.EXPECT().Create().Return(nil)
			mockDomain.EXPECT().GetXMLDesc(libvirt.DomainXMLFlags(0)).Return(string(xml), nil)
			manager, _ := NewLibvirtDomainManager(mockConn, "fake")
			newspec, err := manager.SyncVMI(vmi, true)
			Expect(err).To(BeNil())
			Expect(newspec).ToNot(BeNil())
		})
		It("should leave a defined and started VirtualMachineInstance alone", func() {
			vmi := newVMI(testNamespace, testVmName)
			domainSpec := expectIsolationDetectionForVMI(vmi)
			xml, err := xml.Marshal(domainSpec)

			mockConn.EXPECT().LookupDomainByName(testDomainName).Return(mockDomain, nil)
			mockDomain.EXPECT().GetState().Return(libvirt.DOMAIN_RUNNING, 1, nil)
			mockDomain.EXPECT().GetXMLDesc(libvirt.DomainXMLFlags(0)).Return(string(xml), nil)
			manager, _ := NewLibvirtDomainManager(mockConn, "fake")
			newspec, err := manager.SyncVMI(vmi, true)
			Expect(err).To(BeNil())
			Expect(newspec).ToNot(BeNil())
		})
		table.DescribeTable("should try to start a VirtualMachineInstance in state",
			func(state libvirt.DomainState) {
				vmi := newVMI(testNamespace, testVmName)
				domainSpec := expectIsolationDetectionForVMI(vmi)
				xml, err := xml.Marshal(domainSpec)

				mockConn.EXPECT().LookupDomainByName(testDomainName).Return(mockDomain, nil)
				mockDomain.EXPECT().GetState().Return(state, 1, nil)
				mockConn.EXPECT().DomainDefineXML(string(xml)).Return(mockDomain, nil)
				mockDomain.EXPECT().Create().Return(nil)
				mockDomain.EXPECT().GetXMLDesc(libvirt.DomainXMLFlags(0)).Return(string(xml), nil)
				manager, _ := NewLibvirtDomainManager(mockConn, "fake")
				newspec, err := manager.SyncVMI(vmi, true)
				Expect(err).To(BeNil())
				Expect(newspec).ToNot(BeNil())
			},
			table.Entry("crashed", libvirt.DOMAIN_CRASHED),
			table.Entry("shutdown", libvirt.DOMAIN_SHUTDOWN),
			table.Entry("shutoff", libvirt.DOMAIN_SHUTOFF),
			table.Entry("unknown", libvirt.DOMAIN_NOSTATE),
		)
		It("should resume a paused VirtualMachineInstance", func() {
			vmi := newVMI(testNamespace, testVmName)
			domainSpec := expectIsolationDetectionForVMI(vmi)
			xml, err := xml.Marshal(domainSpec)

			mockConn.EXPECT().LookupDomainByName(testDomainName).Return(mockDomain, nil)
			mockDomain.EXPECT().GetState().Return(libvirt.DOMAIN_PAUSED, 1, nil)
			mockDomain.EXPECT().Resume().Return(nil)
			mockDomain.EXPECT().GetXMLDesc(libvirt.DomainXMLFlags(0)).Return(string(xml), nil)
			manager, _ := NewLibvirtDomainManager(mockConn, "fake")
			newspec, err := manager.SyncVMI(vmi, true)
			Expect(err).To(BeNil())
			Expect(newspec).ToNot(BeNil())
		})
	})
	Context("on successful VirtualMachineInstance kill", func() {
		table.DescribeTable("should try to undefine a VirtualMachineInstance in state",
			func(state libvirt.DomainState) {
				mockConn.EXPECT().LookupDomainByName(testDomainName).Return(mockDomain, nil)
				mockDomain.EXPECT().Undefine().Return(nil)
				manager, _ := NewLibvirtDomainManager(mockConn, "fake")
				err := manager.DeleteVMI(newVMI(testNamespace, testVmName))
				Expect(err).To(BeNil())
			},
			table.Entry("crashed", libvirt.DOMAIN_CRASHED),
			table.Entry("shutoff", libvirt.DOMAIN_SHUTOFF),
		)
		table.DescribeTable("should try to destroy a VirtualMachineInstance in state",
			func(state libvirt.DomainState) {
				mockConn.EXPECT().LookupDomainByName(testDomainName).Return(mockDomain, nil)
				mockDomain.EXPECT().GetState().Return(state, 1, nil)
				mockDomain.EXPECT().DestroyFlags(libvirt.DOMAIN_DESTROY_GRACEFUL).Return(nil)
				manager, _ := NewLibvirtDomainManager(mockConn, "fake")
				err := manager.KillVMI(newVMI(testNamespace, testVmName))
				Expect(err).To(BeNil())
			},
			table.Entry("shuttingDown", libvirt.DOMAIN_SHUTDOWN),
			table.Entry("running", libvirt.DOMAIN_RUNNING),
			table.Entry("paused", libvirt.DOMAIN_PAUSED),
		)
	})

	table.DescribeTable("on successful list all domains",
		func(state libvirt.DomainState, kubevirtState api.LifeCycle, libvirtReason int, kubevirtReason api.StateChangeReason) {

			mockDomain.EXPECT().GetState().Return(state, libvirtReason, nil).AnyTimes()
			mockDomain.EXPECT().GetName().Return("test", nil)
			x, err := xml.Marshal(api.NewMinimalDomainSpec("test"))
			Expect(err).To(BeNil())
			if !cli.IsDown(state) {
				mockDomain.EXPECT().GetXMLDesc(gomock.Eq(libvirt.DOMAIN_XML_MIGRATABLE)).Return(string(x), nil)
			}
			mockDomain.EXPECT().GetXMLDesc(gomock.Eq(libvirt.DOMAIN_XML_INACTIVE)).Return(string(x), nil)
			mockConn.EXPECT().ListAllDomains(gomock.Eq(libvirt.CONNECT_LIST_DOMAINS_ACTIVE|libvirt.CONNECT_LIST_DOMAINS_INACTIVE)).Return([]cli.VirDomain{mockDomain}, nil)

			manager, _ := NewLibvirtDomainManager(mockConn, "fake")
			doms, err := manager.ListAllDomains()

			Expect(len(doms)).To(Equal(1))

			domain := doms[0]
			domain.Spec.XMLName = xml.Name{}

			Expect(&domain.Spec).To(Equal(api.NewMinimalDomainSpec("test")))
			Expect(domain.Status.Status).To(Equal(kubevirtState))
			Expect(domain.Status.Reason).To(Equal(kubevirtReason))
		},
		table.Entry("crashed", libvirt.DOMAIN_CRASHED, api.Crashed, int(libvirt.DOMAIN_CRASHED_UNKNOWN), api.ReasonUnknown),
		table.Entry("shutoff", libvirt.DOMAIN_SHUTOFF, api.Shutoff, int(libvirt.DOMAIN_SHUTOFF_DESTROYED), api.ReasonDestroyed),
		table.Entry("shutdown", libvirt.DOMAIN_SHUTDOWN, api.Shutdown, int(libvirt.DOMAIN_SHUTDOWN_USER), api.ReasonUser),
		table.Entry("unknown", libvirt.DOMAIN_NOSTATE, api.NoState, int(libvirt.DOMAIN_NOSTATE_UNKNOWN), api.ReasonUnknown),
		table.Entry("running", libvirt.DOMAIN_RUNNING, api.Running, int(libvirt.DOMAIN_RUNNING_UNKNOWN), api.ReasonUnknown),
		table.Entry("paused", libvirt.DOMAIN_PAUSED, api.Paused, int(libvirt.DOMAIN_PAUSED_STARTING_UP), api.ReasonPausedStartingUp),
	)

	// TODO: test error reporting on non successful VirtualMachineInstance syncs and kill attempts

	AfterEach(func() {
		ctrl.Finish()
	})
})

func newVMI(namespace string, name string) *v1.VirtualMachineInstance {
	vmi := &v1.VirtualMachineInstance{
		ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: namespace},
		Spec:       v1.VirtualMachineInstanceSpec{Domain: v1.NewMinimalDomainSpec()},
	}
	v1.SetObjectDefaults_VirtualMachineInstance(vmi)
	return vmi
}

func StubOutNetworkForTest() {
	network.SetupPodNetwork = func(vm *v1.VirtualMachineInstance, domain *api.Domain) error { return nil }
}
<commit_msg>Migration unit tests for manager.go<commit_after>/*
 * This file is part of the KubeVirt project
 *
 * 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.
 *
 * Copyright 2017 Red Hat, Inc.
 *
 */

package virtwrap

import (
	"encoding/xml"
	"fmt"

	"github.com/golang/mock/gomock"
	"github.com/libvirt/libvirt-go"
	. "github.com/onsi/ginkgo"
	"github.com/onsi/ginkgo/extensions/table"
	. "github.com/onsi/gomega"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

	"kubevirt.io/kubevirt/pkg/api/v1"
	"kubevirt.io/kubevirt/pkg/log"
	"kubevirt.io/kubevirt/pkg/virt-launcher/virtwrap/api"
	"kubevirt.io/kubevirt/pkg/virt-launcher/virtwrap/cli"
	"kubevirt.io/kubevirt/pkg/virt-launcher/virtwrap/network"
)

var _ = Describe("Manager", func() {
	var mockConn *cli.MockConnection
	var mockDomain *cli.MockVirDomain
	var ctrl *gomock.Controller
	testVmName := "testvmi"
	testNamespace := "testnamespace"
	testDomainName := fmt.Sprintf("%s_%s", testNamespace, testVmName)

	log.Log.SetIOWriter(GinkgoWriter)

	BeforeEach(func() {
		ctrl = gomock.NewController(GinkgoT())
		mockConn = cli.NewMockConnection(ctrl)
		mockDomain = cli.NewMockVirDomain(ctrl)
	})

	expectIsolationDetectionForVMI := func(vmi *v1.VirtualMachineInstance) *api.DomainSpec {
		domain := &api.Domain{}
		c := &api.ConverterContext{
			VirtualMachine: vmi,
			UseEmulation:   true,
		}
		Expect(api.Convert_v1_VirtualMachine_To_api_Domain(vmi, domain, c)).To(Succeed())
		api.SetObjectDefaults_Domain(domain)

		return &domain.Spec
	}

	Context("on successful VirtualMachineInstance sync", func() {
		It("should define and start a new VirtualMachineInstance", func() {
			// Make sure that we always free the domain after use
			mockDomain.EXPECT().Free()
			StubOutNetworkForTest()
			vmi := newVMI(testNamespace, testVmName)
			mockConn.EXPECT().LookupDomainByName(testDomainName).Return(mockDomain, libvirt.Error{Code: libvirt.ERR_NO_DOMAIN})

			domainSpec := expectIsolationDetectionForVMI(vmi)

			xml, err := xml.Marshal(domainSpec)
			Expect(err).To(BeNil())
			mockConn.EXPECT().DomainDefineXML(string(xml)).Return(mockDomain, nil)
			mockDomain.EXPECT().GetState().Return(libvirt.DOMAIN_SHUTDOWN, 1, nil)
			mockDomain.EXPECT().Create().Return(nil)
			mockDomain.EXPECT().GetXMLDesc(libvirt.DomainXMLFlags(0)).Return(string(xml), nil)
			manager, _ := NewLibvirtDomainManager(mockConn, "fake")
			newspec, err := manager.SyncVMI(vmi, true)
			Expect(err).To(BeNil())
			Expect(newspec).ToNot(BeNil())
		})
		It("should leave a defined and started VirtualMachineInstance alone", func() {
			// Make sure that we always free the domain after use
			mockDomain.EXPECT().Free()
			vmi := newVMI(testNamespace, testVmName)
			domainSpec := expectIsolationDetectionForVMI(vmi)
			xml, err := xml.Marshal(domainSpec)

			mockConn.EXPECT().LookupDomainByName(testDomainName).Return(mockDomain, nil)
			mockDomain.EXPECT().GetState().Return(libvirt.DOMAIN_RUNNING, 1, nil)
			mockDomain.EXPECT().GetXMLDesc(libvirt.DomainXMLFlags(0)).Return(string(xml), nil)
			manager, _ := NewLibvirtDomainManager(mockConn, "fake")
			newspec, err := manager.SyncVMI(vmi, true)
			Expect(err).To(BeNil())
			Expect(newspec).ToNot(BeNil())
		})
		table.DescribeTable("should try to start a VirtualMachineInstance in state",
			func(state libvirt.DomainState) {
				// Make sure that we always free the domain after use
				mockDomain.EXPECT().Free()
				vmi := newVMI(testNamespace, testVmName)
				domainSpec := expectIsolationDetectionForVMI(vmi)
				xml, err := xml.Marshal(domainSpec)

				mockConn.EXPECT().LookupDomainByName(testDomainName).Return(mockDomain, nil)
				mockDomain.EXPECT().GetState().Return(state, 1, nil)
				mockConn.EXPECT().DomainDefineXML(string(xml)).Return(mockDomain, nil)
				mockDomain.EXPECT().Create().Return(nil)
				mockDomain.EXPECT().GetXMLDesc(libvirt.DomainXMLFlags(0)).Return(string(xml), nil)
				manager, _ := NewLibvirtDomainManager(mockConn, "fake")
				newspec, err := manager.SyncVMI(vmi, true)
				Expect(err).To(BeNil())
				Expect(newspec).ToNot(BeNil())
			},
			table.Entry("crashed", libvirt.DOMAIN_CRASHED),
			table.Entry("shutdown", libvirt.DOMAIN_SHUTDOWN),
			table.Entry("shutoff", libvirt.DOMAIN_SHUTOFF),
			table.Entry("unknown", libvirt.DOMAIN_NOSTATE),
		)
		It("should resume a paused VirtualMachineInstance", func() {
			// Make sure that we always free the domain after use
			mockDomain.EXPECT().Free()
			vmi := newVMI(testNamespace, testVmName)
			domainSpec := expectIsolationDetectionForVMI(vmi)
			xml, err := xml.Marshal(domainSpec)

			mockConn.EXPECT().LookupDomainByName(testDomainName).Return(mockDomain, nil)
			mockDomain.EXPECT().GetState().Return(libvirt.DOMAIN_PAUSED, 1, nil)
			mockDomain.EXPECT().Resume().Return(nil)
			mockDomain.EXPECT().GetXMLDesc(libvirt.DomainXMLFlags(0)).Return(string(xml), nil)
			manager, _ := NewLibvirtDomainManager(mockConn, "fake")
			newspec, err := manager.SyncVMI(vmi, true)
			Expect(err).To(BeNil())
			Expect(newspec).ToNot(BeNil())
		})
	})

	Context("on successful VirtualMachineInstance migrate", func() {
		It("should prepare the target pod", func() {

			StubOutNetworkForTest()
			vmi := newVMI(testNamespace, testVmName)

			manager, _ := NewLibvirtDomainManager(mockConn, "fake")
			err := manager.PrepareMigrationTarget(vmi, true)
			Expect(err).To(BeNil())
		})

		It("should detect inprogress migration job", func() {
			// Make sure that we always free the domain after use
			mockDomain.EXPECT().Free()

			vmi := newVMI(testNamespace, testVmName)
			vmi.Status.MigrationState = &v1.VirtualMachineInstanceMigrationState{
				MigrationUID: "111222333",
			}

			domainSpec := expectIsolationDetectionForVMI(vmi)
			domainSpec.Metadata.KubeVirt.Migration = &api.MigrationMetadata{

				UID: vmi.Status.MigrationState.MigrationUID,
			}

			manager, _ := NewLibvirtDomainManager(mockConn, "fake")

			mockConn.EXPECT().LookupDomainByName(testDomainName).Return(mockDomain, nil)
			mockDomain.EXPECT().GetState().Return(libvirt.DOMAIN_RUNNING, 1, nil)

			xml, err := xml.Marshal(domainSpec)
			Expect(err).To(BeNil())

			mockDomain.EXPECT().GetXMLDesc(gomock.Eq(libvirt.DOMAIN_XML_MIGRATABLE)).Return(string(xml), nil)
			mockDomain.EXPECT().GetXMLDesc(gomock.Eq(libvirt.DOMAIN_XML_INACTIVE)).Return(string(xml), nil)

			err = manager.MigrateVMI(vmi)
			Expect(err).To(BeNil())
		})
	})

	Context("on successful VirtualMachineInstance kill", func() {
		table.DescribeTable("should try to undefine a VirtualMachineInstance in state",
			func(state libvirt.DomainState) {
				// Make sure that we always free the domain after use
				mockDomain.EXPECT().Free()
				mockConn.EXPECT().LookupDomainByName(testDomainName).Return(mockDomain, nil)
				mockDomain.EXPECT().Undefine().Return(nil)
				manager, _ := NewLibvirtDomainManager(mockConn, "fake")
				err := manager.DeleteVMI(newVMI(testNamespace, testVmName))
				Expect(err).To(BeNil())
			},
			table.Entry("crashed", libvirt.DOMAIN_CRASHED),
			table.Entry("shutoff", libvirt.DOMAIN_SHUTOFF),
		)
		table.DescribeTable("should try to destroy a VirtualMachineInstance in state",
			func(state libvirt.DomainState) {
				// Make sure that we always free the domain after use
				mockDomain.EXPECT().Free()
				mockConn.EXPECT().LookupDomainByName(testDomainName).Return(mockDomain, nil)
				mockDomain.EXPECT().GetState().Return(state, 1, nil)
				mockDomain.EXPECT().DestroyFlags(libvirt.DOMAIN_DESTROY_GRACEFUL).Return(nil)
				manager, _ := NewLibvirtDomainManager(mockConn, "fake")
				err := manager.KillVMI(newVMI(testNamespace, testVmName))
				Expect(err).To(BeNil())
			},
			table.Entry("shuttingDown", libvirt.DOMAIN_SHUTDOWN),
			table.Entry("running", libvirt.DOMAIN_RUNNING),
			table.Entry("paused", libvirt.DOMAIN_PAUSED),
		)
	})

	table.DescribeTable("on successful list all domains",
		func(state libvirt.DomainState, kubevirtState api.LifeCycle, libvirtReason int, kubevirtReason api.StateChangeReason) {

			// Make sure that we always free the domain after use
			mockDomain.EXPECT().Free()
			mockDomain.EXPECT().GetState().Return(state, libvirtReason, nil).AnyTimes()
			mockDomain.EXPECT().GetName().Return("test", nil)
			x, err := xml.Marshal(api.NewMinimalDomainSpec("test"))
			Expect(err).To(BeNil())
			if !cli.IsDown(state) {
				mockDomain.EXPECT().GetXMLDesc(gomock.Eq(libvirt.DOMAIN_XML_MIGRATABLE)).Return(string(x), nil)
			}
			mockDomain.EXPECT().GetXMLDesc(gomock.Eq(libvirt.DOMAIN_XML_INACTIVE)).Return(string(x), nil)
			mockConn.EXPECT().ListAllDomains(gomock.Eq(libvirt.CONNECT_LIST_DOMAINS_ACTIVE|libvirt.CONNECT_LIST_DOMAINS_INACTIVE)).Return([]cli.VirDomain{mockDomain}, nil)

			manager, _ := NewLibvirtDomainManager(mockConn, "fake")
			doms, err := manager.ListAllDomains()

			Expect(len(doms)).To(Equal(1))

			domain := doms[0]
			domain.Spec.XMLName = xml.Name{}

			Expect(&domain.Spec).To(Equal(api.NewMinimalDomainSpec("test")))
			Expect(domain.Status.Status).To(Equal(kubevirtState))
			Expect(domain.Status.Reason).To(Equal(kubevirtReason))
		},
		table.Entry("crashed", libvirt.DOMAIN_CRASHED, api.Crashed, int(libvirt.DOMAIN_CRASHED_UNKNOWN), api.ReasonUnknown),
		table.Entry("shutoff", libvirt.DOMAIN_SHUTOFF, api.Shutoff, int(libvirt.DOMAIN_SHUTOFF_DESTROYED), api.ReasonDestroyed),
		table.Entry("shutdown", libvirt.DOMAIN_SHUTDOWN, api.Shutdown, int(libvirt.DOMAIN_SHUTDOWN_USER), api.ReasonUser),
		table.Entry("unknown", libvirt.DOMAIN_NOSTATE, api.NoState, int(libvirt.DOMAIN_NOSTATE_UNKNOWN), api.ReasonUnknown),
		table.Entry("running", libvirt.DOMAIN_RUNNING, api.Running, int(libvirt.DOMAIN_RUNNING_UNKNOWN), api.ReasonUnknown),
		table.Entry("paused", libvirt.DOMAIN_PAUSED, api.Paused, int(libvirt.DOMAIN_PAUSED_STARTING_UP), api.ReasonPausedStartingUp),
	)

	// TODO: test error reporting on non successful VirtualMachineInstance syncs and kill attempts

	AfterEach(func() {
		ctrl.Finish()
	})
})

func newVMI(namespace string, name string) *v1.VirtualMachineInstance {
	vmi := &v1.VirtualMachineInstance{
		ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: namespace},
		Spec:       v1.VirtualMachineInstanceSpec{Domain: v1.NewMinimalDomainSpec()},
	}
	v1.SetObjectDefaults_VirtualMachineInstance(vmi)
	return vmi
}

func StubOutNetworkForTest() {
	network.SetupPodNetwork = func(vm *v1.VirtualMachineInstance, domain *api.Domain) error { return nil }
}
<|endoftext|>


<commit_before>package settings

import (
	"encoding/json"
	"fmt"
	"os"
	"strconv"
	"strings"

	v32 "github.com/rancher/rancher/pkg/apis/management.cattle.io/v3"
	authsettings "github.com/rancher/rancher/pkg/auth/settings"
	"github.com/sirupsen/logrus"
)

var (
	settings       = map[string]Setting{}
	provider       Provider
	InjectDefaults string

	AgentImage                        = NewSetting("agent-image", "rancher/rancher-agent:master-head")
	AuthImage                         = NewSetting("auth-image", v32.ToolsSystemImages.AuthSystemImages.KubeAPIAuth)
	AuthTokenMaxTTLMinutes            = NewSetting("auth-token-max-ttl-minutes", "1440") // 24 hours
	AuthorizationCacheTTLSeconds      = NewSetting("authorization-cache-ttl-seconds", "10")
	AuthorizationDenyCacheTTLSeconds  = NewSetting("authorization-deny-cache-ttl-seconds", "10")
	CACerts                           = NewSetting("cacerts", "")
	CLIURLDarwin                      = NewSetting("cli-url-darwin", "https://releases.rancher.com/cli/v1.0.0-alpha8/rancher-darwin-amd64-v1.0.0-alpha8.tar.gz")
	CLIURLLinux                       = NewSetting("cli-url-linux", "https://releases.rancher.com/cli/v1.0.0-alpha8/rancher-linux-amd64-v1.0.0-alpha8.tar.gz")
	CLIURLWindows                     = NewSetting("cli-url-windows", "https://releases.rancher.com/cli/v1.0.0-alpha8/rancher-windows-386-v1.0.0-alpha8.zip")
	ClusterControllerStartCount       = NewSetting("cluster-controller-start-count", "50")
	EngineInstallURL                  = NewSetting("engine-install-url", "https://releases.rancher.com/install-docker/19.03.sh")
	EngineISOURL                      = NewSetting("engine-iso-url", "https://releases.rancher.com/os/latest/rancheros-vmware.iso")
	EngineNewestVersion               = NewSetting("engine-newest-version", "v17.12.0")
	EngineSupportedRange              = NewSetting("engine-supported-range", "~v1.11.2 || ~v1.12.0 || ~v1.13.0 || ~v17.03.0 || ~v17.06.0 || ~v17.09.0 || ~v18.06.0 || ~v18.09.0 || ~v19.03.0 ")
	FirstLogin                        = NewSetting("first-login", "true")
	GlobalRegistryEnabled             = NewSetting("global-registry-enabled", "false")
	GithubProxyAPIURL                 = NewSetting("github-proxy-api-url", "https://api.github.com")
	HelmVersion                       = NewSetting("helm-version", "dev")
	HelmMaxHistory                    = NewSetting("helm-max-history", "10")
	IngressIPDomain                   = NewSetting("ingress-ip-domain", "xip.io")
	InstallUUID                       = NewSetting("install-uuid", "")
	JailerTimeout                     = NewSetting("jailer-timeout", "60")
	KubeconfigGenerateToken           = NewSetting("kubeconfig-generate-token", "true")
	KubeconfigTokenTTLMinutes         = NewSetting("kubeconfig-token-ttl-minutes", "960") // 16 hours
	KubernetesVersion                 = NewSetting("k8s-version", "")
	KubernetesVersionToServiceOptions = NewSetting("k8s-version-to-service-options", "")
	KubernetesVersionToSystemImages   = NewSetting("k8s-version-to-images", "")
	KubernetesVersionsCurrent         = NewSetting("k8s-versions-current", "")
	KubernetesVersionsDeprecated      = NewSetting("k8s-versions-deprecated", "")
	MachineVersion                    = NewSetting("machine-version", "dev")
	Namespace                         = NewSetting("namespace", os.Getenv("CATTLE_NAMESPACE"))
	PeerServices                      = NewSetting("peer-service", os.Getenv("CATTLE_PEER_SERVICE"))
	RDNSServerBaseURL                 = NewSetting("rdns-base-url", "https://api.lb.rancher.cloud/v1")
	RkeVersion                        = NewSetting("rke-version", "")
	RkeMetadataConfig                 = NewSetting("rke-metadata-config", getMetadataConfig())
	ServerImage                       = NewSetting("server-image", "rancher/rancher")
	ServerURL                         = NewSetting("server-url", "")
	ServerVersion                     = NewSetting("server-version", "dev")
	SystemDefaultRegistry             = NewSetting("system-default-registry", "")
	SystemNamespaces                  = NewSetting("system-namespaces", "kube-system,kube-public,cattle-system,cattle-alerting,cattle-logging,cattle-pipeline,cattle-prometheus,ingress-nginx,cattle-global-data,cattle-istio,kube-node-lease,cert-manager,cattle-global-nt,security-scan")
	TelemetryOpt                      = NewSetting("telemetry-opt", "")
	TLSMinVersion                     = NewSetting("tls-min-version", "1.2")
	TLSCiphers                        = NewSetting("tls-ciphers", "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305")
	UIBanners                         = NewSetting("ui-banners", "{}")
	UIDefaultLanding                  = NewSetting("ui-default-landing", "")
	UIFeedBackForm                    = NewSetting("ui-feedback-form", "")
	UIIndex                           = NewSetting("ui-index", "https://releases.rancher.com/ui/latest2/index.html")
	UIPath                            = NewSetting("ui-path", "/usr/share/rancher/ui")
	UIDashboardIndex                  = NewSetting("ui-dashboard-index", "https://releases.rancher.com/dashboard/latest/index.html")
	UIDashboardPath                   = NewSetting("ui-dashboard-path", "/usr/share/rancher/ui-dashboard")
	UIPreferred                       = NewSetting("ui-preferred", "ember")
	UIOfflinePreferred                = NewSetting("ui-offline-preferred", "dynamic")
	UIIssues                          = NewSetting("ui-issues", "")
	UIPL                              = NewSetting("ui-pl", "rancher")
	UIKubernetesSupportedVersions     = NewSetting("ui-k8s-supported-versions-range", ">= 1.11.0 <=1.14.x")
	UIKubernetesDefaultVersion        = NewSetting("ui-k8s-default-version-range", "<=1.14.x")
	WhitelistDomain                   = NewSetting("whitelist-domain", "forums.rancher.com")
	WhitelistEnvironmentVars          = NewSetting("whitelist-envvars", "HTTP_PROXY,HTTPS_PROXY,NO_PROXY")
	AuthUserInfoResyncCron            = NewSetting("auth-user-info-resync-cron", "0 0 * * *")
	AuthUserSessionTTLMinutes         = NewSetting("auth-user-session-ttl-minutes", "960")   // 16 hours
	AuthUserInfoMaxAgeSeconds         = NewSetting("auth-user-info-max-age-seconds", "3600") // 1 hour
	APIUIVersion                      = NewSetting("api-ui-version", "1.1.6")                // Please update the CATTLE_API_UI_VERSION in package/Dockerfile when updating the version here.
	RotateCertsIfExpiringInDays       = NewSetting("rotate-certs-if-expiring-in-days", "7")  // 7 days
	ClusterTemplateEnforcement        = NewSetting("cluster-template-enforcement", "false")
	InitialDockerRootDir              = NewSetting("initial-docker-root-dir", "/var/lib/docker")
	SystemCatalog                     = NewSetting("system-catalog", "external") // Options are 'external' or 'bundled'
	ChartDefaultBranch                = NewSetting("chart-default-branch", "dev-v2.5")
	IgnoreNodeName                    = NewSetting("ignore-node-name", "") // nodes to ignore when syncing v1.node to v3.node
)

func init() {
	// setup auth setting
	authsettings.AuthUserInfoResyncCron = AuthUserInfoResyncCron
	authsettings.AuthUserSessionTTLMinutes = AuthUserSessionTTLMinutes
	authsettings.AuthUserInfoMaxAgeSeconds = AuthUserInfoMaxAgeSeconds
	authsettings.FirstLogin = FirstLogin

	if InjectDefaults == "" {
		return
	}
	defaults := map[string]string{}
	if err := json.Unmarshal([]byte(InjectDefaults), &defaults); err != nil {
		return
	}
	for name, defaultValue := range defaults {
		value, ok := settings[name]
		if !ok {
			continue
		}
		value.Default = defaultValue
		settings[name] = value
	}
}

type Provider interface {
	Get(name string) string
	Set(name, value string) error
	SetIfUnset(name, value string) error
	SetAll(settings map[string]Setting) error
}

type Setting struct {
	Name     string
	Default  string
	ReadOnly bool
}

func (s Setting) SetIfUnset(value string) error {
	if provider == nil {
		return s.Set(value)
	}
	return provider.SetIfUnset(s.Name, value)
}

func (s Setting) Set(value string) error {
	if provider == nil {
		s, ok := settings[s.Name]
		if ok {
			s.Default = value
			settings[s.Name] = s
		}
	} else {
		return provider.Set(s.Name, value)
	}
	return nil
}

func (s Setting) Get() string {
	if provider == nil {
		s := settings[s.Name]
		return s.Default
	}
	return provider.Get(s.Name)
}

func (s Setting) GetInt() int {
	v := s.Get()
	i, err := strconv.Atoi(v)
	if err == nil {
		return i
	}
	logrus.Errorf("failed to parse setting %s=%s as int: %v", s.Name, v, err)
	i, err = strconv.Atoi(s.Default)
	if err != nil {
		return 0
	}
	return i
}

func SetProvider(p Provider) error {
	if err := p.SetAll(settings); err != nil {
		return err
	}
	provider = p
	return nil
}

func NewSetting(name, def string) Setting {
	s := Setting{
		Name:    name,
		Default: def,
	}
	settings[s.Name] = s
	return s
}

func GetEnvKey(key string) string {
	return "CATTLE_" + strings.ToUpper(strings.Replace(key, "-", "_", -1))
}

func getMetadataConfig() string {
	branch := os.Getenv("RANCHER_METADATA_BRANCH")
	if branch == "" {
		branch = "dev-v2.5"
	}
	data := map[string]interface{}{
		"url":                      fmt.Sprintf("https://releases.rancher.com/kontainer-driver-metadata/%s/data.json", branch),
		"refresh-interval-minutes": "1440",
	}
	ans, err := json.Marshal(data)
	if err != nil {
		logrus.Errorf("error getting metadata config %v", err)
		return ""
	}
	return string(ans)
}
<commit_msg>update AuthTokenMaxTTLMinutes default to 0<commit_after>package settings

import (
	"encoding/json"
	"fmt"
	"os"
	"strconv"
	"strings"

	v32 "github.com/rancher/rancher/pkg/apis/management.cattle.io/v3"
	authsettings "github.com/rancher/rancher/pkg/auth/settings"
	"github.com/sirupsen/logrus"
)

var (
	settings       = map[string]Setting{}
	provider       Provider
	InjectDefaults string

	AgentImage                        = NewSetting("agent-image", "rancher/rancher-agent:master-head")
	AuthImage                         = NewSetting("auth-image", v32.ToolsSystemImages.AuthSystemImages.KubeAPIAuth)
	AuthTokenMaxTTLMinutes            = NewSetting("auth-token-max-ttl-minutes", "0") // never expire
	AuthorizationCacheTTLSeconds      = NewSetting("authorization-cache-ttl-seconds", "10")
	AuthorizationDenyCacheTTLSeconds  = NewSetting("authorization-deny-cache-ttl-seconds", "10")
	CACerts                           = NewSetting("cacerts", "")
	CLIURLDarwin                      = NewSetting("cli-url-darwin", "https://releases.rancher.com/cli/v1.0.0-alpha8/rancher-darwin-amd64-v1.0.0-alpha8.tar.gz")
	CLIURLLinux                       = NewSetting("cli-url-linux", "https://releases.rancher.com/cli/v1.0.0-alpha8/rancher-linux-amd64-v1.0.0-alpha8.tar.gz")
	CLIURLWindows                     = NewSetting("cli-url-windows", "https://releases.rancher.com/cli/v1.0.0-alpha8/rancher-windows-386-v1.0.0-alpha8.zip")
	ClusterControllerStartCount       = NewSetting("cluster-controller-start-count", "50")
	EngineInstallURL                  = NewSetting("engine-install-url", "https://releases.rancher.com/install-docker/19.03.sh")
	EngineISOURL                      = NewSetting("engine-iso-url", "https://releases.rancher.com/os/latest/rancheros-vmware.iso")
	EngineNewestVersion               = NewSetting("engine-newest-version", "v17.12.0")
	EngineSupportedRange              = NewSetting("engine-supported-range", "~v1.11.2 || ~v1.12.0 || ~v1.13.0 || ~v17.03.0 || ~v17.06.0 || ~v17.09.0 || ~v18.06.0 || ~v18.09.0 || ~v19.03.0 ")
	FirstLogin                        = NewSetting("first-login", "true")
	GlobalRegistryEnabled             = NewSetting("global-registry-enabled", "false")
	GithubProxyAPIURL                 = NewSetting("github-proxy-api-url", "https://api.github.com")
	HelmVersion                       = NewSetting("helm-version", "dev")
	HelmMaxHistory                    = NewSetting("helm-max-history", "10")
	IngressIPDomain                   = NewSetting("ingress-ip-domain", "xip.io")
	InstallUUID                       = NewSetting("install-uuid", "")
	JailerTimeout                     = NewSetting("jailer-timeout", "60")
	KubeconfigGenerateToken           = NewSetting("kubeconfig-generate-token", "true")
	KubeconfigTokenTTLMinutes         = NewSetting("kubeconfig-token-ttl-minutes", "960") // 16 hours
	KubernetesVersion                 = NewSetting("k8s-version", "")
	KubernetesVersionToServiceOptions = NewSetting("k8s-version-to-service-options", "")
	KubernetesVersionToSystemImages   = NewSetting("k8s-version-to-images", "")
	KubernetesVersionsCurrent         = NewSetting("k8s-versions-current", "")
	KubernetesVersionsDeprecated      = NewSetting("k8s-versions-deprecated", "")
	MachineVersion                    = NewSetting("machine-version", "dev")
	Namespace                         = NewSetting("namespace", os.Getenv("CATTLE_NAMESPACE"))
	PeerServices                      = NewSetting("peer-service", os.Getenv("CATTLE_PEER_SERVICE"))
	RDNSServerBaseURL                 = NewSetting("rdns-base-url", "https://api.lb.rancher.cloud/v1")
	RkeVersion                        = NewSetting("rke-version", "")
	RkeMetadataConfig                 = NewSetting("rke-metadata-config", getMetadataConfig())
	ServerImage                       = NewSetting("server-image", "rancher/rancher")
	ServerURL                         = NewSetting("server-url", "")
	ServerVersion                     = NewSetting("server-version", "dev")
	SystemDefaultRegistry             = NewSetting("system-default-registry", "")
	SystemNamespaces                  = NewSetting("system-namespaces", "kube-system,kube-public,cattle-system,cattle-alerting,cattle-logging,cattle-pipeline,cattle-prometheus,ingress-nginx,cattle-global-data,cattle-istio,kube-node-lease,cert-manager,cattle-global-nt,security-scan")
	TelemetryOpt                      = NewSetting("telemetry-opt", "")
	TLSMinVersion                     = NewSetting("tls-min-version", "1.2")
	TLSCiphers                        = NewSetting("tls-ciphers", "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305")
	UIBanners                         = NewSetting("ui-banners", "{}")
	UIDefaultLanding                  = NewSetting("ui-default-landing", "")
	UIFeedBackForm                    = NewSetting("ui-feedback-form", "")
	UIIndex                           = NewSetting("ui-index", "https://releases.rancher.com/ui/latest2/index.html")
	UIPath                            = NewSetting("ui-path", "/usr/share/rancher/ui")
	UIDashboardIndex                  = NewSetting("ui-dashboard-index", "https://releases.rancher.com/dashboard/latest/index.html")
	UIDashboardPath                   = NewSetting("ui-dashboard-path", "/usr/share/rancher/ui-dashboard")
	UIPreferred                       = NewSetting("ui-preferred", "ember")
	UIOfflinePreferred                = NewSetting("ui-offline-preferred", "dynamic")
	UIIssues                          = NewSetting("ui-issues", "")
	UIPL                              = NewSetting("ui-pl", "rancher")
	UIKubernetesSupportedVersions     = NewSetting("ui-k8s-supported-versions-range", ">= 1.11.0 <=1.14.x")
	UIKubernetesDefaultVersion        = NewSetting("ui-k8s-default-version-range", "<=1.14.x")
	WhitelistDomain                   = NewSetting("whitelist-domain", "forums.rancher.com")
	WhitelistEnvironmentVars          = NewSetting("whitelist-envvars", "HTTP_PROXY,HTTPS_PROXY,NO_PROXY")
	AuthUserInfoResyncCron            = NewSetting("auth-user-info-resync-cron", "0 0 * * *")
	AuthUserSessionTTLMinutes         = NewSetting("auth-user-session-ttl-minutes", "960")   // 16 hours
	AuthUserInfoMaxAgeSeconds         = NewSetting("auth-user-info-max-age-seconds", "3600") // 1 hour
	APIUIVersion                      = NewSetting("api-ui-version", "1.1.6")                // Please update the CATTLE_API_UI_VERSION in package/Dockerfile when updating the version here.
	RotateCertsIfExpiringInDays       = NewSetting("rotate-certs-if-expiring-in-days", "7")  // 7 days
	ClusterTemplateEnforcement        = NewSetting("cluster-template-enforcement", "false")
	InitialDockerRootDir              = NewSetting("initial-docker-root-dir", "/var/lib/docker")
	SystemCatalog                     = NewSetting("system-catalog", "external") // Options are 'external' or 'bundled'
	ChartDefaultBranch                = NewSetting("chart-default-branch", "dev-v2.5")
	IgnoreNodeName                    = NewSetting("ignore-node-name", "") // nodes to ignore when syncing v1.node to v3.node
)

func init() {
	// setup auth setting
	authsettings.AuthUserInfoResyncCron = AuthUserInfoResyncCron
	authsettings.AuthUserSessionTTLMinutes = AuthUserSessionTTLMinutes
	authsettings.AuthUserInfoMaxAgeSeconds = AuthUserInfoMaxAgeSeconds
	authsettings.FirstLogin = FirstLogin

	if InjectDefaults == "" {
		return
	}
	defaults := map[string]string{}
	if err := json.Unmarshal([]byte(InjectDefaults), &defaults); err != nil {
		return
	}
	for name, defaultValue := range defaults {
		value, ok := settings[name]
		if !ok {
			continue
		}
		value.Default = defaultValue
		settings[name] = value
	}
}

type Provider interface {
	Get(name string) string
	Set(name, value string) error
	SetIfUnset(name, value string) error
	SetAll(settings map[string]Setting) error
}

type Setting struct {
	Name     string
	Default  string
	ReadOnly bool
}

func (s Setting) SetIfUnset(value string) error {
	if provider == nil {
		return s.Set(value)
	}
	return provider.SetIfUnset(s.Name, value)
}

func (s Setting) Set(value string) error {
	if provider == nil {
		s, ok := settings[s.Name]
		if ok {
			s.Default = value
			settings[s.Name] = s
		}
	} else {
		return provider.Set(s.Name, value)
	}
	return nil
}

func (s Setting) Get() string {
	if provider == nil {
		s := settings[s.Name]
		return s.Default
	}
	return provider.Get(s.Name)
}

func (s Setting) GetInt() int {
	v := s.Get()
	i, err := strconv.Atoi(v)
	if err == nil {
		return i
	}
	logrus.Errorf("failed to parse setting %s=%s as int: %v", s.Name, v, err)
	i, err = strconv.Atoi(s.Default)
	if err != nil {
		return 0
	}
	return i
}

func SetProvider(p Provider) error {
	if err := p.SetAll(settings); err != nil {
		return err
	}
	provider = p
	return nil
}

func NewSetting(name, def string) Setting {
	s := Setting{
		Name:    name,
		Default: def,
	}
	settings[s.Name] = s
	return s
}

func GetEnvKey(key string) string {
	return "CATTLE_" + strings.ToUpper(strings.Replace(key, "-", "_", -1))
}

func getMetadataConfig() string {
	branch := os.Getenv("RANCHER_METADATA_BRANCH")
	if branch == "" {
		branch = "dev-v2.5"
	}
	data := map[string]interface{}{
		"url":                      fmt.Sprintf("https://releases.rancher.com/kontainer-driver-metadata/%s/data.json", branch),
		"refresh-interval-minutes": "1440",
	}
	ans, err := json.Marshal(data)
	if err != nil {
		logrus.Errorf("error getting metadata config %v", err)
		return ""
	}
	return string(ans)
}
<|endoftext|>


<commit_before>package template

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

	. "github.com/smartystreets/goconvey/convey"
)

func TestTemplateRender(t *testing.T) {
	Convey("template rendering", t, func() {
		Convey("should not render large templates", func() {
			longStr := strings.Repeat("&", MaxTemplateSize-50)
			template := fmt.Sprintf(`{{html (html (html (html "%s")))}}`, longStr)

			var err error

			_, err = RenderTextTemplate(RenderOptions{
				Name:         "test",
				TemplateBody: template,
				Context:      nil,
			})
			So(err, ShouldBeError, "failed to execute template: rendered template is too large")
		})
		Convey("should supports defines", func() {
			actual, err := RenderHTMLTemplate(RenderOptions{
				Name: "test",
				TemplateBody: `
				{{ template "A" }}
				{{ template "B" }}
				`,
				Context: map[string]interface{}{
					"a": "42",
				},
				Defines: []string{
					`{{ define "A" }}This is A{{ end }}`,
					`{{ define "B" }}This is B{{ end }}`,
				},
				ValidatorOpts: []ValidatorOption{AllowTemplateNode(true)},
			})
			expected := `
				This is A
				This is B
				`
			So(err, ShouldBeNil)
			So(actual, ShouldEqual, expected)
		})
		Convey("should auto-escape templates", func() {
			template := `
			<!DOCTYPE html>

				<meta charset="utf-8" />
				<title>My App</title>

{{ .Title }}

					Search for "{{ .Query }}"

{{ index .State.todos 0 }}

{{ index .State.todos 1 }}

			`
			expectation := `
			<!DOCTYPE html>

				<meta charset="utf-8" />
				<title>My App</title>

Welcome to <b>My App</b>.

					Search for "Lazy dog > Quick brown fox?"

<b>Important things! \o/</b>

Cats & Dogs

			`

			out, err := RenderHTMLTemplate(RenderOptions{
				Name:         "test",
				TemplateBody: template,
				Context: map[string]interface{}{
					"URL":             "https://www.example.com",
					"Title":           "Welcome to My App.",
					"BackgroundColor": "#101020; /* for contrast */",
					"ForegroundColor": "#e0e0ff",
					"Query":           "Lazy dog > Quick brown fox?",
					"State": map[string]interface{}{
						"todos": []string{`<b>Important things! \o/</b>`, "Cats & Dogs"},
						"query": "Lazy dog > Quick brown fox?",
					},
				},
			})
			So(err, ShouldBeNil)
			So(out, ShouldEqual, expectation)
		})
	})
	Convey("EncodeContextToURLQueryParamValue", t, func() {
		cases := []struct {
			Input map[string]interface{}
		}{
			{nil},
			{map[string]interface{}{}},
			{map[string]interface{}{
				"a": "b",
				"nested": map[string]interface{}{
					"a": "b",
				},
			}},
		}
		for _, c := range cases {
			encoded, err := EncodeContextToURLQueryParamValue(c.Input)
			So(err, ShouldBeNil)
			decoded, err := DecodeURLQueryParamValueToContext(encoded)
			So(err, ShouldBeNil)
			So(decoded, ShouldResemble, c.Input)
		}
	})
	Convey("SetContextToURLQuery", t, func() {
		cases := []struct {
			URL      string
			Context  map[string]interface{}
			Expected string
		}{
			{
				"http://example.com",
				nil,
				"http://example.com?x-skygear-redirect-data=",
			},
			{
				"http://example.com",
				map[string]interface{}{},
				"http://example.com?x-skygear-redirect-data=e30",
			},
			{
				"http://example.com",
				map[string]interface{}{
					"a": "b",
				},
				"http://example.com?x-skygear-redirect-data=eyJhIjoiYiJ9",
			},
			{
				"http://example.com",
				map[string]interface{}{
					"a": "b",
					"nested": map[string]interface{}{
						"a": "b",
					},
				},
				"http://example.com?x-skygear-redirect-data=eyJhIjoiYiIsIm5lc3RlZCI6eyJhIjoiYiJ9fQ",
			},
		}
		for _, c := range cases {
			u, err := url.Parse(c.URL)
			So(err, ShouldBeNil)
			err = SetContextToURLQuery(u, c.Context)
			So(err, ShouldBeNil)
			So(u.String(), ShouldEqual, c.Expected)
		}
	})
}
<commit_msg>Move some tests into its own test function<commit_after>package template

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

	. "github.com/smartystreets/goconvey/convey"
)

func TestTemplateRender(t *testing.T) {
	Convey("template rendering", t, func() {
		Convey("should not render large templates", func() {
			longStr := strings.Repeat("&", MaxTemplateSize-50)
			template := fmt.Sprintf(`{{html (html (html (html "%s")))}}`, longStr)

			var err error

			_, err = RenderTextTemplate(RenderOptions{
				Name:         "test",
				TemplateBody: template,
				Context:      nil,
			})
			So(err, ShouldBeError, "failed to execute template: rendered template is too large")
		})
		Convey("should supports defines", func() {
			actual, err := RenderHTMLTemplate(RenderOptions{
				Name: "test",
				TemplateBody: `
				{{ template "A" }}
				{{ template "B" }}
				`,
				Context: map[string]interface{}{
					"a": "42",
				},
				Defines: []string{
					`{{ define "A" }}This is A{{ end }}`,
					`{{ define "B" }}This is B{{ end }}`,
				},
				ValidatorOpts: []ValidatorOption{AllowTemplateNode(true)},
			})
			expected := `
				This is A
				This is B
				`
			So(err, ShouldBeNil)
			So(actual, ShouldEqual, expected)
		})
		Convey("should auto-escape templates", func() {
			template := `
			<!DOCTYPE html>

				<meta charset="utf-8" />
				<title>My App</title>

{{ .Title }}

					Search for "{{ .Query }}"

{{ index .State.todos 0 }}

{{ index .State.todos 1 }}

			`
			expectation := `
			<!DOCTYPE html>

				<meta charset="utf-8" />
				<title>My App</title>

Welcome to <b>My App</b>.

					Search for "Lazy dog > Quick brown fox?"

<b>Important things! \o/</b>

Cats & Dogs

			`

			out, err := RenderHTMLTemplate(RenderOptions{
				Name:         "test",
				TemplateBody: template,
				Context: map[string]interface{}{
					"URL":             "https://www.example.com",
					"Title":           "Welcome to My App.",
					"BackgroundColor": "#101020; /* for contrast */",
					"ForegroundColor": "#e0e0ff",
					"Query":           "Lazy dog > Quick brown fox?",
					"State": map[string]interface{}{
						"todos": []string{`<b>Important things! \o/</b>`, "Cats & Dogs"},
						"query": "Lazy dog > Quick brown fox?",
					},
				},
			})
			So(err, ShouldBeNil)
			So(out, ShouldEqual, expectation)
		})
	})
}

func TestEncodeContextToURLQueryParamValue(t *testing.T) {
	Convey("EncodeContextToURLQueryParamValue", t, func() {
		cases := []struct {
			Input map[string]interface{}
		}{
			{nil},
			{map[string]interface{}{}},
			{map[string]interface{}{
				"a": "b",
				"nested": map[string]interface{}{
					"a": "b",
				},
			}},
		}
		for _, c := range cases {
			encoded, err := EncodeContextToURLQueryParamValue(c.Input)
			So(err, ShouldBeNil)
			decoded, err := DecodeURLQueryParamValueToContext(encoded)
			So(err, ShouldBeNil)
			So(decoded, ShouldResemble, c.Input)
		}
	})
}

func TestSetContextToURLQuery(t *testing.T) {
	Convey("SetContextToURLQuery", t, func() {
		cases := []struct {
			URL      string
			Context  map[string]interface{}
			Expected string
		}{
			{
				"http://example.com",
				nil,
				"http://example.com?x-skygear-redirect-data=",
			},
			{
				"http://example.com",
				map[string]interface{}{},
				"http://example.com?x-skygear-redirect-data=e30",
			},
			{
				"http://example.com",
				map[string]interface{}{
					"a": "b",
				},
				"http://example.com?x-skygear-redirect-data=eyJhIjoiYiJ9",
			},
			{
				"http://example.com",
				map[string]interface{}{
					"a": "b",
					"nested": map[string]interface{}{
						"a": "b",
					},
				},
				"http://example.com?x-skygear-redirect-data=eyJhIjoiYiIsIm5lc3RlZCI6eyJhIjoiYiJ9fQ",
			},
		}
		for _, c := range cases {
			u, err := url.Parse(c.URL)
			So(err, ShouldBeNil)
			err = SetContextToURLQuery(u, c.Context)
			So(err, ShouldBeNil)
			So(u.String(), ShouldEqual, c.Expected)
		}
	})
}
<|endoftext|>


<commit_before>package nvim

// Mode represents a Nvim's current mode.
type Mode struct {
	// Mode is the current mode.
	Mode string `msgpack:"mode"`

	// Blocking is true if Nvim is waiting for input.
	Blocking bool `msgpack:"blocking"`
}

// HLAttrs represents a highlight definitions.
type HLAttrs struct {
	Bold       bool `msgpack:"bold,omitempty"`
	Underline  bool `msgpack:"underline,omitempty"`
	Undercurl  bool `msgpack:"undercurl,omitempty"`
	Italic     bool `msgpack:"italic,omitempty"`
	Reverse    bool `msgpack:"reverse,omitempty"`
	Foreground int  `msgpack:"foreground,omitempty" empty:"-1"`
	Background int  `msgpack:"background,omitempty" empty:"-1"`
	Special    int  `msgpack:"special,omitempty" empty:"-1"`
}

// Mapping represents a nvim mapping options.
type Mapping struct {
	// LHS is the {lhs} of the mapping.
	LHS string `msgpack:"lhs"`

	// RHS is the {hrs} of the mapping as typed.
	RHS string `msgpack:"rhs"`

	// Silent is 1 for a |:map-silent| mapping, else 0.
	Silent int `msgpack:"silent"`

	// Noremap is 1 if the {rhs} of the mapping is not remappable.
	NoRemap int `msgpack:"noremap"`

	// Expr is  1 for an expression mapping.
	Expr int `msgpack:"expr"`

	// Buffer for a local mapping.
	Buffer int `msgpack:"buffer"`

	// SID is the script local ID, used for <sid> mappings.
	SID int `msgpack:"sid"`

	// Nowait is 1 if map does not wait for other, longer mappings.
	NoWait int `msgpack:"nowait"`

	// Mode specifies modes for which the mapping is defined.
	Mode string `msgpack:"string"`
}

// ClientVersion represents a version of client for nvim.
type ClientVersion struct {
	// Major major version. (defaults to 0 if not set, for no release yet)
	Major int `msgpack:"major,omitempty" empty:"0"`

	// Minor minor version.
	Minor int `msgpack:"minor,omitempty"`

	// Patch patch number.
	Patch int `msgpack:"patch,omitempty"`

	// Prerelease string describing a prerelease, like "dev" or "beta1".
	Prerelease string `msgpack:"prerelease,omitempty"`

	// Commit hash or similar identifier of commit.
	Commit string `msgpack:"commit,omitempty"`
}

// ClientType type of client information.
type ClientType string

const (
	// RemoteClientType for the client library.
	RemoteClientType ClientType = "remote"

	// UIClientType for the gui frontend.
	UIClientType ClientType = "ui"

	// EmbedderClientType for the application using nvim as a component, for instance IDE/editor implementing a vim mode.
	EmbedderClientType ClientType = "embedder"

	// HostClientType for the plugin host. Typically started by nvim.
	HostClientType ClientType = "host"

	// PluginClientType for the single plugin. Started by nvim.
	PluginClientType ClientType = "plugin"
)

// ClientMethod builtin methods in the client.
//
// For a host, this does not include plugin methods which will be discovered later.
// The key should be the method name, the values are dicts with the following (optional) keys. See below.
//
// Further keys might be added in later versions of nvim and unknown keys are thus ignored.
// Clients must only use keys defined in this or later versions of nvim.
type ClientMethod struct {
	// Async is defines whether the uses notification request or blocking request.
	//
	// If true, send as a notification.
	// If false, send as a blocking request.
	Async bool `msgpack:"async"`

	// NArgs is the number of method arguments.
	NArgs ClientMethodNArgs `msgpack:"nargs"`
}

// ClientMethodNArgs is the number of arguments. Could be a single integer or an array two integers, minimum and maximum inclusive.
type ClientMethodNArgs struct {
	// Min is the minimum number of method arguments.
	Min int `msgpack:",array"`

	// Max is the maximum number of method arguments.
	Max int `msgpack:",array"`
}

// ClientAttributes informal attributes describing the client. Clients might define their own keys, but the following are suggested.
type ClientAttributes map[string]string

const (
	// ClientAttributeKeyWebsite Website of client (for instance github repository).
	ClientAttributeKeyWebsite = "website"

	// ClientAttributeKeyLicense Informal description of the license, such as "Apache 2", "GPLv3" or "MIT".
	ClientAttributeKeyLicense = "license"

	// ClientoAttributeKeyLogo URI or path to image, preferably small logo or icon. .png or .svg format is preferred.
	ClientoAttributeKeyLogo = "logo"
)

// Client represents a identify the client for nvim.
//
// Can be called more than once, but subsequent calls will remove earlier info, which should be resent if it is still valid.
// (This could happen if a library first identifies the channel, and a plugin using that library later overrides that info)
type Client struct {
	// Name is short name for the connected client.
	Name string `msgpack:"name,omitempty"`

	// Version describes the version, with the following possible keys (all optional).
	Version ClientVersion `msgpack:"version,omitempty"`

	// Type is the client type. Must be one of the ClientType type values.
	Type ClientType `msgpack:"type,omitempty"`

	// Methods builtin methods in the client.
	Methods map[string]*ClientMethod `msgpack:"methods,omitempty"`

	// Attributes is informal attributes describing the client.
	Attributes ClientAttributes `msgpack:"attributes,omitempty"`
}

// Channel information about a channel.
type Channel struct {
	// Stream is the stream underlying the channel.
	Stream string `msgpack:"stream,omitempty"`

	// Mode is the how data received on the channel is interpreted.
	Mode string `msgpack:"mode,omitempty"`

	// Pty is the name of pseudoterminal, if one is used (optional).
	Pty string `msgpack:"pty,omitempty"`

	// Buffer is the buffer with connected terminal instance (optional).
	Buffer Buffer `msgpack:"buffer,omitempty"`

	// Client is the information about the client on the other end of the RPC channel, if it has added it using nvim_set_client_info (optional).
	Client *Client `msgpack:"client,omitempty"`
}

// Process represents a Proc and ProcChildren functions return type.
type Process struct {
	// Name is the name of process command.
	Name string `msgpack:"name,omitempty"`

	// PID is the process ID.
	PID int `msgpack:"pid,omitempty"`

	// PPID is the parent process ID.
	PPID int `msgpack:"ppid,omitempty"`
}

// UI represents a nvim ui options.
type UI struct {
	// Height requested height of the UI
	Height int `msgpack:"height,omitempty"`

	// Width requested width of the UI
	Width int `msgpack:"width,omitempty"`

	// RGB whether the UI uses rgb colors (false implies cterm colors)
	RGB bool `msgpack:"rgb,omitempty"`

	// ExtPopupmenu externalize the popupmenu.
	ExtPopupmenu bool `msgpack:"ext_popupmenu,omitempty"`

	// ExtTabline externalize the tabline.
	ExtTabline bool `msgpack:"ext_tabline,omitempty"`

	// ExtCmdline externalize the cmdline.
	ExtCmdline bool `msgpack:"ext_cmdline,omitempty"`

	// ExtWildmenu externalize the wildmenu.
	ExtWildmenu bool `msgpack:"ext_wildmenu,omitempty"`

	// ExtNewgrid use new revision of the grid events.
	ExtNewgrid bool `msgpack:"ext_newgrid,omitempty"`

	// ExtHlstate use detailed highlight state.
	ExtHlstate bool `msgpack:"ext_hlstate,omitempty"`

	// ChannelID channel id of remote UI (not present for TUI)
	ChannelID int `msgpack:"chan,omitempty"`
}

// Command represents a Neovim Ex command.
type Command struct {
	Bang        bool   `msgpack:"bang"`
	Complete    string `msgpack:"complete,omitempty"`
	Nargs       string `msgpack:"nargs"`
	Range       string `msgpack:"range,omitempty"`
	Name        string `msgpack:"name"`
	ScriptID    int    `msgpack:"script_id"`
	Bar         bool   `msgpack:"bar"`
	Register    bool   `msgpack:"register"`
	Addr        string `msgpack:"addr,omitempty"`
	Count       string `msgpack:"count,omitempty"`
	CompleteArg string `msgpack:"complete_arg,omitempty"`
	Definition  string `msgpack:"definition"`
}

// VirtualTextChunk represents a virtual text chunk.
type VirtualTextChunk struct {
	Text    string `msgpack:",array"`
	HLGroup string `msgpack:",array"`
}
<commit_msg>nvim: add OpenWindowConfig type<commit_after>package nvim

// Mode represents a Nvim's current mode.
type Mode struct {
	// Mode is the current mode.
	Mode string `msgpack:"mode"`

	// Blocking is true if Nvim is waiting for input.
	Blocking bool `msgpack:"blocking"`
}

// HLAttrs represents a highlight definitions.
type HLAttrs struct {
	Bold       bool `msgpack:"bold,omitempty"`
	Underline  bool `msgpack:"underline,omitempty"`
	Undercurl  bool `msgpack:"undercurl,omitempty"`
	Italic     bool `msgpack:"italic,omitempty"`
	Reverse    bool `msgpack:"reverse,omitempty"`
	Foreground int  `msgpack:"foreground,omitempty" empty:"-1"`
	Background int  `msgpack:"background,omitempty" empty:"-1"`
	Special    int  `msgpack:"special,omitempty" empty:"-1"`
}

// Mapping represents a nvim mapping options.
type Mapping struct {
	// LHS is the {lhs} of the mapping.
	LHS string `msgpack:"lhs"`

	// RHS is the {hrs} of the mapping as typed.
	RHS string `msgpack:"rhs"`

	// Silent is 1 for a |:map-silent| mapping, else 0.
	Silent int `msgpack:"silent"`

	// Noremap is 1 if the {rhs} of the mapping is not remappable.
	NoRemap int `msgpack:"noremap"`

	// Expr is  1 for an expression mapping.
	Expr int `msgpack:"expr"`

	// Buffer for a local mapping.
	Buffer int `msgpack:"buffer"`

	// SID is the script local ID, used for <sid> mappings.
	SID int `msgpack:"sid"`

	// Nowait is 1 if map does not wait for other, longer mappings.
	NoWait int `msgpack:"nowait"`

	// Mode specifies modes for which the mapping is defined.
	Mode string `msgpack:"string"`
}

// ClientVersion represents a version of client for nvim.
type ClientVersion struct {
	// Major major version. (defaults to 0 if not set, for no release yet)
	Major int `msgpack:"major,omitempty" empty:"0"`

	// Minor minor version.
	Minor int `msgpack:"minor,omitempty"`

	// Patch patch number.
	Patch int `msgpack:"patch,omitempty"`

	// Prerelease string describing a prerelease, like "dev" or "beta1".
	Prerelease string `msgpack:"prerelease,omitempty"`

	// Commit hash or similar identifier of commit.
	Commit string `msgpack:"commit,omitempty"`
}

// ClientType type of client information.
type ClientType string

const (
	// RemoteClientType for the client library.
	RemoteClientType ClientType = "remote"

	// UIClientType for the gui frontend.
	UIClientType ClientType = "ui"

	// EmbedderClientType for the application using nvim as a component, for instance IDE/editor implementing a vim mode.
	EmbedderClientType ClientType = "embedder"

	// HostClientType for the plugin host. Typically started by nvim.
	HostClientType ClientType = "host"

	// PluginClientType for the single plugin. Started by nvim.
	PluginClientType ClientType = "plugin"
)

// ClientMethod builtin methods in the client.
//
// For a host, this does not include plugin methods which will be discovered later.
// The key should be the method name, the values are dicts with the following (optional) keys. See below.
//
// Further keys might be added in later versions of nvim and unknown keys are thus ignored.
// Clients must only use keys defined in this or later versions of nvim.
type ClientMethod struct {
	// Async is defines whether the uses notification request or blocking request.
	//
	// If true, send as a notification.
	// If false, send as a blocking request.
	Async bool `msgpack:"async"`

	// NArgs is the number of method arguments.
	NArgs ClientMethodNArgs `msgpack:"nargs"`
}

// ClientMethodNArgs is the number of arguments. Could be a single integer or an array two integers, minimum and maximum inclusive.
type ClientMethodNArgs struct {
	// Min is the minimum number of method arguments.
	Min int `msgpack:",array"`

	// Max is the maximum number of method arguments.
	Max int `msgpack:",array"`
}

// ClientAttributes informal attributes describing the client. Clients might define their own keys, but the following are suggested.
type ClientAttributes map[string]string

const (
	// ClientAttributeKeyWebsite Website of client (for instance github repository).
	ClientAttributeKeyWebsite = "website"

	// ClientAttributeKeyLicense Informal description of the license, such as "Apache 2", "GPLv3" or "MIT".
	ClientAttributeKeyLicense = "license"

	// ClientoAttributeKeyLogo URI or path to image, preferably small logo or icon. .png or .svg format is preferred.
	ClientoAttributeKeyLogo = "logo"
)

// Client represents a identify the client for nvim.
//
// Can be called more than once, but subsequent calls will remove earlier info, which should be resent if it is still valid.
// (This could happen if a library first identifies the channel, and a plugin using that library later overrides that info)
type Client struct {
	// Name is short name for the connected client.
	Name string `msgpack:"name,omitempty"`

	// Version describes the version, with the following possible keys (all optional).
	Version ClientVersion `msgpack:"version,omitempty"`

	// Type is the client type. Must be one of the ClientType type values.
	Type ClientType `msgpack:"type,omitempty"`

	// Methods builtin methods in the client.
	Methods map[string]*ClientMethod `msgpack:"methods,omitempty"`

	// Attributes is informal attributes describing the client.
	Attributes ClientAttributes `msgpack:"attributes,omitempty"`
}

// Channel information about a channel.
type Channel struct {
	// Stream is the stream underlying the channel.
	Stream string `msgpack:"stream,omitempty"`

	// Mode is the how data received on the channel is interpreted.
	Mode string `msgpack:"mode,omitempty"`

	// Pty is the name of pseudoterminal, if one is used (optional).
	Pty string `msgpack:"pty,omitempty"`

	// Buffer is the buffer with connected terminal instance (optional).
	Buffer Buffer `msgpack:"buffer,omitempty"`

	// Client is the information about the client on the other end of the RPC channel, if it has added it using nvim_set_client_info (optional).
	Client *Client `msgpack:"client,omitempty"`
}

// Process represents a Proc and ProcChildren functions return type.
type Process struct {
	// Name is the name of process command.
	Name string `msgpack:"name,omitempty"`

	// PID is the process ID.
	PID int `msgpack:"pid,omitempty"`

	// PPID is the parent process ID.
	PPID int `msgpack:"ppid,omitempty"`
}

// UI represents a nvim ui options.
type UI struct {
	// Height requested height of the UI
	Height int `msgpack:"height,omitempty"`

	// Width requested width of the UI
	Width int `msgpack:"width,omitempty"`

	// RGB whether the UI uses rgb colors (false implies cterm colors)
	RGB bool `msgpack:"rgb,omitempty"`

	// ExtPopupmenu externalize the popupmenu.
	ExtPopupmenu bool `msgpack:"ext_popupmenu,omitempty"`

	// ExtTabline externalize the tabline.
	ExtTabline bool `msgpack:"ext_tabline,omitempty"`

	// ExtCmdline externalize the cmdline.
	ExtCmdline bool `msgpack:"ext_cmdline,omitempty"`

	// ExtWildmenu externalize the wildmenu.
	ExtWildmenu bool `msgpack:"ext_wildmenu,omitempty"`

	// ExtNewgrid use new revision of the grid events.
	ExtNewgrid bool `msgpack:"ext_newgrid,omitempty"`

	// ExtHlstate use detailed highlight state.
	ExtHlstate bool `msgpack:"ext_hlstate,omitempty"`

	// ChannelID channel id of remote UI (not present for TUI)
	ChannelID int `msgpack:"chan,omitempty"`
}

// Command represents a Neovim Ex command.
type Command struct {
	Bang        bool   `msgpack:"bang"`
	Complete    string `msgpack:"complete,omitempty"`
	Nargs       string `msgpack:"nargs"`
	Range       string `msgpack:"range,omitempty"`
	Name        string `msgpack:"name"`
	ScriptID    int    `msgpack:"script_id"`
	Bar         bool   `msgpack:"bar"`
	Register    bool   `msgpack:"register"`
	Addr        string `msgpack:"addr,omitempty"`
	Count       string `msgpack:"count,omitempty"`
	CompleteArg string `msgpack:"complete_arg,omitempty"`
	Definition  string `msgpack:"definition"`
}

// VirtualTextChunk represents a virtual text chunk.
type VirtualTextChunk struct {
	Text    string `msgpack:",array"`
	HLGroup string `msgpack:",array"`
}

// OpenWindowConfig represents a configs of OpenWindow.
//
// Relative is the specifies the type of positioning method used for the floating window.
// The positioning method keys names:
//
//  editor: The global editor grid.
//  win: Window given by the `win` field, or current window by default.
//  cursor: Cursor position in current window.
//
// Win is the Window for relative="win".
//
// Anchor is the decides which corner of the float to place at row and col.
//
//  NW: northwest (default)
//  NE: northeast
//  SW: southwest
//  SE: southeast
//
// Width is the window width (in character cells). Minimum of 1.
//
// Height is the window height (in character cells). Minimum of 1.
//
// BufPos places float relative to buffer text only when relative="win". Takes a tuple of zero-indexed [line, column].
// Row and Col if given are applied relative to this position, else they default to Row=1 and Col=0 (thus like a tooltip near the buffer text).
//
// Row is the row position in units of "screen cell height", may be fractional.
//
// Col is the column position in units of "screen cell width", may be fractional.
//
// Focusable whether the enable focus by user actions (wincmds, mouse events).
// Defaults to true. Non-focusable windows can be entered by SetCurrentWindow.
//
// External is the GUI should display the window as an external top-level window.
// Currently accepts no other positioning configuration together with this.
//
// Style is the Configure the appearance of the window.
// Currently only takes one non-empty value:
//
//  minimal:
//    Nvim will display the window with many UI options disabled.
//    This is useful when displaying a temporary float where the text should not be edited.
//    Disables 'number', 'relativenumber', 'cursorline', 'cursorcolumn','foldcolumn', 'spell' and 'list' options. 'signcolumn' is changed to `auto`.
//    The end-of-buffer region is hidden by setting `eob` flag of 'fillchars' to a space char, and clearing the EndOfBuffer region in 'winhighlight'.
type OpenWindowConfig struct {
	Relative  string `msgpack:"relative,omitempty"`
	Win       Window `msgpack:"win,omitempty"`
	Anchor    string `msgpack:"anchor,omitempty" empty:"NW"`
	Width     int    `msgpack:"width" empty:"1"`
	Height    int    `msgpack:"height" empty:"1"`
	BufPos    [2]int `msgpack:"bufpos,omitempty"`
	Row       int    `msgpack:"row,omitempty"`
	Col       int    `msgpack:"col,omitempty"`
	Focusable bool   `msgpack:"focusable,omitempty" empty:"true"`
	External  bool   `msgpack:"external,omitempty"`
	Style     string `msgpack:"style,omitempty"`
}
<|endoftext|>