<commit_before>package server

import (
	"context"
	"fmt"
	net2 "net"
	"os"
	"path/filepath"
	"strings"

	systemd "github.com/coreos/go-systemd/daemon"
	"github.com/pkg/errors"
	"github.com/rancher/k3s/pkg/agent"
	"github.com/rancher/k3s/pkg/cli/cmds"
	"github.com/rancher/k3s/pkg/datadir"
	"github.com/rancher/k3s/pkg/netutil"
	"github.com/rancher/k3s/pkg/rootless"
	"github.com/rancher/k3s/pkg/server"
	"github.com/rancher/wrangler/pkg/signals"
	"github.com/sirupsen/logrus"
	"github.com/urfave/cli"
	"k8s.io/apimachinery/pkg/util/net"
	"k8s.io/kubernetes/pkg/master"

	_ "github.com/go-sql-driver/mysql" // ensure we have mysql
	_ "github.com/lib/pq"              // ensure we have postgres
	_ "github.com/mattn/go-sqlite3"    // ensure we have sqlite
)

func Run(app *cli.Context) error {
	if err := cmds.InitLogging(); err != nil {
		return err
	}
	return run(app, &cmds.ServerConfig)
}

func run(app *cli.Context, cfg *cmds.Server) error {
	var (
		err error
	)

	if !cfg.DisableAgent && os.Getuid() != 0 && !cfg.Rootless {
		return fmt.Errorf("must run as root unless --disable-agent is specified")
	}

	if cfg.Rootless {
		dataDir, err := datadir.LocalHome(cfg.DataDir, true)
		if err != nil {
			return err
		}
		cfg.DataDir = dataDir
		if err := rootless.Rootless(dataDir); err != nil {
			return err
		}
	}

	serverConfig := server.Config{}
	serverConfig.ControlConfig.ClusterSecret = cfg.ClusterSecret
	serverConfig.ControlConfig.DataDir = cfg.DataDir
	serverConfig.ControlConfig.KubeConfigOutput = cfg.KubeConfigOutput
	serverConfig.ControlConfig.KubeConfigMode = cfg.KubeConfigMode
	serverConfig.ControlConfig.NoScheduler = cfg.DisableScheduler
	serverConfig.Rootless = cfg.Rootless
	serverConfig.TLSConfig.HTTPSPort = cfg.HTTPSPort
	serverConfig.TLSConfig.HTTPPort = cfg.HTTPPort
	for _, san := range knownIPs(cfg.TLSSan) {
		addr := net2.ParseIP(san)
		if addr != nil {
			serverConfig.TLSConfig.KnownIPs = append(serverConfig.TLSConfig.KnownIPs, san)
		} else {
			serverConfig.TLSConfig.Domains = append(serverConfig.TLSConfig.Domains, san)
		}
	}
	serverConfig.TLSConfig.BindAddress = cfg.BindAddress
	serverConfig.ControlConfig.HTTPSPort = cfg.HTTPSPort
	serverConfig.ControlConfig.ExtraAPIArgs = cfg.ExtraAPIArgs
	serverConfig.ControlConfig.ExtraControllerArgs = cfg.ExtraControllerArgs
	serverConfig.ControlConfig.ExtraSchedulerAPIArgs = cfg.ExtraSchedulerArgs
	serverConfig.ControlConfig.ClusterDomain = cfg.ClusterDomain
	serverConfig.ControlConfig.Storage.Endpoint = cfg.StorageEndpoint
	serverConfig.ControlConfig.Storage.CAFile = cfg.StorageCAFile
	serverConfig.ControlConfig.Storage.CertFile = cfg.StorageCertFile
	serverConfig.ControlConfig.Storage.KeyFile = cfg.StorageKeyFile
	serverConfig.ControlConfig.AdvertiseIP = cfg.AdvertiseIP
	serverConfig.ControlConfig.AdvertisePort = cfg.AdvertisePort
	serverConfig.ControlConfig.BootstrapReadOnly = !cfg.StoreBootstrap
	serverConfig.ControlConfig.FlannelBackend = cfg.FlannelBackend

	if cmds.AgentConfig.FlannelIface != "" && cmds.AgentConfig.NodeIP == "" {
		cmds.AgentConfig.NodeIP = netutil.GetIPFromInterface(cmds.AgentConfig.FlannelIface)
	}

	if serverConfig.ControlConfig.AdvertiseIP == "" && cmds.AgentConfig.NodeIP != "" {
		serverConfig.ControlConfig.AdvertiseIP = cmds.AgentConfig.NodeIP
	}
	if serverConfig.ControlConfig.AdvertiseIP != "" {
		serverConfig.TLSConfig.KnownIPs = append(serverConfig.TLSConfig.KnownIPs, serverConfig.ControlConfig.AdvertiseIP)
	}

	_, serverConfig.ControlConfig.ClusterIPRange, err = net2.ParseCIDR(cfg.ClusterCIDR)
	if err != nil {
		return errors.Wrapf(err, "Invalid CIDR %s: %v", cfg.ClusterCIDR, err)
	}
	_, serverConfig.ControlConfig.ServiceIPRange, err = net2.ParseCIDR(cfg.ServiceCIDR)
	if err != nil {
		return errors.Wrapf(err, "Invalid CIDR %s: %v", cfg.ServiceCIDR, err)
	}

	_, apiServerServiceIP, err := master.DefaultServiceIPRange(*serverConfig.ControlConfig.ServiceIPRange)
	if err != nil {
		return err
	}
	serverConfig.TLSConfig.KnownIPs = append(serverConfig.TLSConfig.KnownIPs, apiServerServiceIP.String())

	// If cluster-dns CLI arg is not set, we set ClusterDNS address to be ServiceCIDR network + 10,
	// i.e. when you set service-cidr to 192.168.0.0/16 and don't provide cluster-dns, it will be set to 192.168.0.10
	if cfg.ClusterDNS == "" {
		serverConfig.ControlConfig.ClusterDNS = make(net2.IP, 4)
		copy(serverConfig.ControlConfig.ClusterDNS, serverConfig.ControlConfig.ServiceIPRange.IP.To4())
		serverConfig.ControlConfig.ClusterDNS[3] = 10
	} else {
		serverConfig.ControlConfig.ClusterDNS = net2.ParseIP(cfg.ClusterDNS)
	}

	if cfg.DefaultLocalStoragePath == "" {
		dataDir, err := datadir.LocalHome(cfg.DataDir, false)
		if err != nil {
			return err
		}
		serverConfig.ControlConfig.DefaultLocalStoragePath = filepath.Join(dataDir, "/storage")
	} else {
		serverConfig.ControlConfig.DefaultLocalStoragePath = cfg.DefaultLocalStoragePath
	}

	for _, noDeploy := range app.StringSlice("no-deploy") {
		if noDeploy == "servicelb" {
			serverConfig.DisableServiceLB = true
			continue
		}

		if !strings.HasSuffix(noDeploy, ".yaml") {
			noDeploy = noDeploy + ".yaml"
		}
		serverConfig.ControlConfig.Skips = append(serverConfig.ControlConfig.Skips, noDeploy)
	}

	logrus.Info("Starting k3s ", app.App.Version)
	notifySocket := os.Getenv("NOTIFY_SOCKET")
	os.Unsetenv("NOTIFY_SOCKET")

	ctx := signals.SetupSignalHandler(context.Background())
	certs, err := server.StartServer(ctx, &serverConfig)
	if err != nil {
		return err
	}

	logrus.Info("k3s is up and running")
	if notifySocket != "" {
		os.Setenv("NOTIFY_SOCKET", notifySocket)
		systemd.SdNotify(true, "READY=1\n")
	}

	if cfg.DisableAgent {
		<-ctx.Done()
		return nil
	}
	ip := serverConfig.TLSConfig.BindAddress
	if ip == "" {
		ip = "127.0.0.1"
	}
	url := fmt.Sprintf("https://%s:%d", ip, serverConfig.TLSConfig.HTTPSPort)
	token := server.FormatToken(serverConfig.ControlConfig.Runtime.NodeToken, certs)

	agentConfig := cmds.AgentConfig
	agentConfig.Debug = app.GlobalBool("bool")
	agentConfig.DataDir = filepath.Dir(serverConfig.ControlConfig.DataDir)
	agentConfig.ServerURL = url
	agentConfig.Token = token
	agentConfig.DisableLoadBalancer = true

	return agent.Run(ctx, agentConfig)
}

func knownIPs(ips []string) []string {
	ips = append(ips, "127.0.0.1")
	ip, err := net.ChooseHostInterface()
	if err == nil {
		ips = append(ips, ip.String())
	}
	return ips
}
<commit_msg>Add comma-separated no-deploy values<commit_after>package server

import (
	"context"
	"fmt"
	net2 "net"
	"os"
	"path/filepath"
	"strings"

	systemd "github.com/coreos/go-systemd/daemon"
	"github.com/pkg/errors"
	"github.com/rancher/k3s/pkg/agent"
	"github.com/rancher/k3s/pkg/cli/cmds"
	"github.com/rancher/k3s/pkg/datadir"
	"github.com/rancher/k3s/pkg/netutil"
	"github.com/rancher/k3s/pkg/rootless"
	"github.com/rancher/k3s/pkg/server"
	"github.com/rancher/wrangler/pkg/signals"
	"github.com/sirupsen/logrus"
	"github.com/urfave/cli"
	"k8s.io/apimachinery/pkg/util/net"
	"k8s.io/kubernetes/pkg/master"

	_ "github.com/go-sql-driver/mysql" // ensure we have mysql
	_ "github.com/lib/pq"              // ensure we have postgres
	_ "github.com/mattn/go-sqlite3"    // ensure we have sqlite
)

func Run(app *cli.Context) error {
	if err := cmds.InitLogging(); err != nil {
		return err
	}
	return run(app, &cmds.ServerConfig)
}

func run(app *cli.Context, cfg *cmds.Server) error {
	var (
		err error
	)

	if !cfg.DisableAgent && os.Getuid() != 0 && !cfg.Rootless {
		return fmt.Errorf("must run as root unless --disable-agent is specified")
	}

	if cfg.Rootless {
		dataDir, err := datadir.LocalHome(cfg.DataDir, true)
		if err != nil {
			return err
		}
		cfg.DataDir = dataDir
		if err := rootless.Rootless(dataDir); err != nil {
			return err
		}
	}

	serverConfig := server.Config{}
	serverConfig.ControlConfig.ClusterSecret = cfg.ClusterSecret
	serverConfig.ControlConfig.DataDir = cfg.DataDir
	serverConfig.ControlConfig.KubeConfigOutput = cfg.KubeConfigOutput
	serverConfig.ControlConfig.KubeConfigMode = cfg.KubeConfigMode
	serverConfig.ControlConfig.NoScheduler = cfg.DisableScheduler
	serverConfig.Rootless = cfg.Rootless
	serverConfig.TLSConfig.HTTPSPort = cfg.HTTPSPort
	serverConfig.TLSConfig.HTTPPort = cfg.HTTPPort
	for _, san := range knownIPs(cfg.TLSSan) {
		addr := net2.ParseIP(san)
		if addr != nil {
			serverConfig.TLSConfig.KnownIPs = append(serverConfig.TLSConfig.KnownIPs, san)
		} else {
			serverConfig.TLSConfig.Domains = append(serverConfig.TLSConfig.Domains, san)
		}
	}
	serverConfig.TLSConfig.BindAddress = cfg.BindAddress
	serverConfig.ControlConfig.HTTPSPort = cfg.HTTPSPort
	serverConfig.ControlConfig.ExtraAPIArgs = cfg.ExtraAPIArgs
	serverConfig.ControlConfig.ExtraControllerArgs = cfg.ExtraControllerArgs
	serverConfig.ControlConfig.ExtraSchedulerAPIArgs = cfg.ExtraSchedulerArgs
	serverConfig.ControlConfig.ClusterDomain = cfg.ClusterDomain
	serverConfig.ControlConfig.Storage.Endpoint = cfg.StorageEndpoint
	serverConfig.ControlConfig.Storage.CAFile = cfg.StorageCAFile
	serverConfig.ControlConfig.Storage.CertFile = cfg.StorageCertFile
	serverConfig.ControlConfig.Storage.KeyFile = cfg.StorageKeyFile
	serverConfig.ControlConfig.AdvertiseIP = cfg.AdvertiseIP
	serverConfig.ControlConfig.AdvertisePort = cfg.AdvertisePort
	serverConfig.ControlConfig.BootstrapReadOnly = !cfg.StoreBootstrap
	serverConfig.ControlConfig.FlannelBackend = cfg.FlannelBackend

	if cmds.AgentConfig.FlannelIface != "" && cmds.AgentConfig.NodeIP == "" {
		cmds.AgentConfig.NodeIP = netutil.GetIPFromInterface(cmds.AgentConfig.FlannelIface)
	}

	if serverConfig.ControlConfig.AdvertiseIP == "" && cmds.AgentConfig.NodeIP != "" {
		serverConfig.ControlConfig.AdvertiseIP = cmds.AgentConfig.NodeIP
	}
	if serverConfig.ControlConfig.AdvertiseIP != "" {
		serverConfig.TLSConfig.KnownIPs = append(serverConfig.TLSConfig.KnownIPs, serverConfig.ControlConfig.AdvertiseIP)
	}

	_, serverConfig.ControlConfig.ClusterIPRange, err = net2.ParseCIDR(cfg.ClusterCIDR)
	if err != nil {
		return errors.Wrapf(err, "Invalid CIDR %s: %v", cfg.ClusterCIDR, err)
	}
	_, serverConfig.ControlConfig.ServiceIPRange, err = net2.ParseCIDR(cfg.ServiceCIDR)
	if err != nil {
		return errors.Wrapf(err, "Invalid CIDR %s: %v", cfg.ServiceCIDR, err)
	}

	_, apiServerServiceIP, err := master.DefaultServiceIPRange(*serverConfig.ControlConfig.ServiceIPRange)
	if err != nil {
		return err
	}
	serverConfig.TLSConfig.KnownIPs = append(serverConfig.TLSConfig.KnownIPs, apiServerServiceIP.String())

	// If cluster-dns CLI arg is not set, we set ClusterDNS address to be ServiceCIDR network + 10,
	// i.e. when you set service-cidr to 192.168.0.0/16 and don't provide cluster-dns, it will be set to 192.168.0.10
	if cfg.ClusterDNS == "" {
		serverConfig.ControlConfig.ClusterDNS = make(net2.IP, 4)
		copy(serverConfig.ControlConfig.ClusterDNS, serverConfig.ControlConfig.ServiceIPRange.IP.To4())
		serverConfig.ControlConfig.ClusterDNS[3] = 10
	} else {
		serverConfig.ControlConfig.ClusterDNS = net2.ParseIP(cfg.ClusterDNS)
	}

	if cfg.DefaultLocalStoragePath == "" {
		dataDir, err := datadir.LocalHome(cfg.DataDir, false)
		if err != nil {
			return err
		}
		serverConfig.ControlConfig.DefaultLocalStoragePath = filepath.Join(dataDir, "/storage")
	} else {
		serverConfig.ControlConfig.DefaultLocalStoragePath = cfg.DefaultLocalStoragePath
	}

	noDeploys := make([]string, 0)
	for _, noDeploy := range app.StringSlice("no-deploy") {
		for _, splitNoDeploy := range strings.Split(noDeploy, ",") {
			noDeploys = append(noDeploys, splitNoDeploy)
		}
	}

	for _, noDeploy := range noDeploys {
		if noDeploy == "servicelb" {
			serverConfig.DisableServiceLB = true
			continue
		}

		if !strings.HasSuffix(noDeploy, ".yaml") {
			noDeploy = noDeploy + ".yaml"
		}
		serverConfig.ControlConfig.Skips = append(serverConfig.ControlConfig.Skips, noDeploy)
	}

	logrus.Info("Starting k3s ", app.App.Version)
	notifySocket := os.Getenv("NOTIFY_SOCKET")
	os.Unsetenv("NOTIFY_SOCKET")

	ctx := signals.SetupSignalHandler(context.Background())
	certs, err := server.StartServer(ctx, &serverConfig)
	if err != nil {
		return err
	}

	logrus.Info("k3s is up and running")
	if notifySocket != "" {
		os.Setenv("NOTIFY_SOCKET", notifySocket)
		systemd.SdNotify(true, "READY=1\n")
	}

	if cfg.DisableAgent {
		<-ctx.Done()
		return nil
	}
	ip := serverConfig.TLSConfig.BindAddress
	if ip == "" {
		ip = "127.0.0.1"
	}
	url := fmt.Sprintf("https://%s:%d", ip, serverConfig.TLSConfig.HTTPSPort)
	token := server.FormatToken(serverConfig.ControlConfig.Runtime.NodeToken, certs)

	agentConfig := cmds.AgentConfig
	agentConfig.Debug = app.GlobalBool("bool")
	agentConfig.DataDir = filepath.Dir(serverConfig.ControlConfig.DataDir)
	agentConfig.ServerURL = url
	agentConfig.Token = token
	agentConfig.DisableLoadBalancer = true

	return agent.Run(ctx, agentConfig)
}

func knownIPs(ips []string) []string {
	ips = append(ips, "127.0.0.1")
	ip, err := net.ChooseHostInterface()
	if err == nil {
		ips = append(ips, ip.String())
	}
	return ips
}
<|endoftext|>


<commit_before>package gctemplates

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

	"github.com/gochan-org/gochan/pkg/config"
	"github.com/gochan-org/gochan/pkg/gcsql"
	"github.com/gochan-org/gochan/pkg/gcutil"
	x_html "golang.org/x/net/html"
)

var funcMap = template.FuncMap{
	// Arithmetic functions
	"add": func(a, b int) int {
		return a + b
	},
	"subtract": func(a, b int) int {
		return a - b
	},

	// Comparison functions (some copied from text/template for compatibility)
	"ge": func(a int, b int) bool {
		return a >= b
	},
	"gt": func(a int, b int) bool {
		return a > b
	},
	"le": func(a int, b int) bool {
		return a <= b
	},
	"lt": func(a int, b int) bool {
		return a < b
	},
	"intEq": func(a, b int) bool {
		return a == b
	},
	"isNil": func(i interface{}) bool {
		return i == nil
	},

	// Array functions
	"getSlice": func(arr []interface{}, start, length int) []interface{} {
		if start < 0 {
			start = 0
		}
		if length > len(arr) {
			length = len(arr)
		}
		return arr[start:length]
	},
	"len": func(arr []interface{}) int {
		return len(arr)
	},

	// String functions
	// "arrToString": arrToString,
	"intToString": strconv.Itoa,
	"escapeString": func(a string) string {
		return html.EscapeString(a)
	},
	"formatFilesize": func(sizeInt int) string {
		size := float32(sizeInt)
		if size < 1000 {
			return fmt.Sprintf("%d B", sizeInt)
		} else if size <= 100000 {
			return fmt.Sprintf("%0.1f KB", size/1024)
		} else if size <= 100000000 {
			return fmt.Sprintf("%0.2f MB", size/1024/1024)
		}
		return fmt.Sprintf("%0.2f GB", size/1024/1024/1024)
	},
	"formatTimestamp": func(t time.Time) string {
		return t.Format(config.Config.DateTimeFormat)
	},
	"stringAppend": func(strings ...string) string {
		var appended string
		for _, str := range strings {
			appended += str
		}
		return appended
	},
	"truncateMessage": func(msg string, limit int, maxLines int) string {
		var truncated bool
		split := strings.SplitN(msg, "
", -1)

		if len(split) > maxLines {
			split = split[:maxLines]
			msg = strings.Join(split, "
")
			truncated = true
		}

		if len(msg) < limit {
			if truncated {
				msg = msg + "..."
			}
			return msg
		}
		msg = msg[:limit]
		truncated = true

		if truncated {
			msg = msg + "..."
		}
		return msg
	},
	"stripHTML": func(htmlStr string) string {
		dom := x_html.NewTokenizer(strings.NewReader(htmlStr))
		for tokenType := dom.Next(); tokenType != x_html.ErrorToken; {
			if tokenType != x_html.TextToken {
				tokenType = dom.Next()
				continue
			}
			txtContent := strings.TrimSpace(x_html.UnescapeString(string(dom.Text())))
			if len(txtContent) > 0 {
				return x_html.EscapeString(txtContent)
			}
			tokenType = dom.Next()
		}
		return ""
	},
	"truncateString": func(msg string, limit int, ellipsis bool) string {
		if len(msg) > limit {
			if ellipsis {
				return msg[:limit] + "..."
			}
			return msg[:limit]
		}
		return msg
	},

	// Imageboard functions
	"getCatalogThumbnail": func(img string) string {
		return gcutil.GetThumbnailPath("catalog", img)
	},
	"getThreadID": func(postInterface interface{}) (thread int) {
		post, ok := postInterface.(gcsql.Post)
		if !ok {
			thread = 0
		} else if post.ParentID == 0 {
			thread = post.ID
		} else {
			thread = post.ParentID
		}
		return
	},
	"getPostURL": func(postInterface interface{}, typeOf string, withDomain bool) (postURL string) {
		if withDomain {
			postURL = config.Config.SiteDomain
		}
		postURL += config.Config.SiteWebfolder

		if typeOf == "recent" {
			post, ok := postInterface.(*gcsql.RecentPost)
			if !ok {
				return
			}
			postURL = post.GetURL(withDomain)
		} else {
			post, ok := postInterface.(*gcsql.Post)
			if !ok {
				return
			}
			postURL = post.GetURL(withDomain)
		}
		return
	},
	"getThreadThumbnail": func(img string) string {
		return gcutil.GetThumbnailPath("thread", img)
	},
	"getUploadType": func(name string) string {
		extension := gcutil.GetFileExtension(name)
		var uploadType string
		switch extension {
		case "":
			fallthrough
		case "deleted":
			uploadType = ""
		case "webm":
			fallthrough
		case "jpg":
			fallthrough
		case "jpeg":
			fallthrough
		case "gif":
			uploadType = "jpg"
		case "png":
			uploadType = "png"
		}
		return uploadType
	},
	"imageToThumbnailPath": func(thumbType string, img string) string {
		filetype := strings.ToLower(img[strings.LastIndex(img, ".")+1:])
		if filetype == "gif" || filetype == "webm" {
			filetype = "jpg"
		}
		index := strings.LastIndex(img, ".")
		if index < 0 || index > len(img) {
			return ""
		}
		thumbSuffix := "t." + filetype
		if thumbType == "catalog" {
			thumbSuffix = "c." + filetype
		}
		return img[0:index] + thumbSuffix
	},
	"numReplies": func(boardid, threadid int) int {
		num, err := gcsql.GetReplyCount(threadid)
		if err != nil {
			return 0
		}
		return num
	},
	"getBoardDir": func(id int) string {
		var board gcsql.Board
		if err := board.PopulateData(id); err != nil {
			return ""
		}
		return board.Dir
	},

	// Template convenience functions
	"makeLoop": func(n int, offset int) []int {
		loopArr := make([]int, n)
		for i := range loopArr {
			loopArr[i] = i + offset
		}
		return loopArr
	},
	"generateConfigTable": func() string {
		configType := reflect.TypeOf(config.Config)
		tableOut := `<table style="border-collapse: collapse;" id="config"><tr><th>Field name</th><th>Value</th><th>Type</th><th>Description</th></tr>`
		numFields := configType.NumField()
		for f := 17; f < numFields-2; f++ {
			// starting at Lockdown because the earlier fields can't be safely edited from a web interface
			field := configType.Field(f)
			if field.Tag.Get("critical") != "" {
				continue
			}
			name := field.Name
			tableOut += "
" + name + "	"
			f := reflect.Indirect(reflect.ValueOf(config.Config)).FieldByName(name)

			kind := f.Kind()
			switch kind {
			case reflect.Int:
				tableOut += `<input name="` + name + `" type="number" value="` + html.EscapeString(fmt.Sprintf("%v", f)) + `" class="config-text"/>`
			case reflect.String:
				tableOut += `<input name="` + name + `" type="text" value="` + html.EscapeString(fmt.Sprintf("%v", f)) + `" class="config-text"/>`
			case reflect.Bool:
				checked := ""
				if f.Bool() {
					checked = "checked"
				}
				tableOut += `<input name="` + name + `" type="checkbox" ` + checked + " />"
			case reflect.Slice:
				tableOut += `<textarea name="` + name + `" rows="4" cols="28">`
				arrLength := f.Len()
				for s := 0; s < arrLength; s++ {
					newLine := "\n"
					if s == arrLength-1 {
						newLine = ""
					}
					tableOut += html.EscapeString(f.Slice(s, s+1).Index(0).String()) + newLine
				}
				tableOut += "</textarea>"
			default:
				tableOut += fmt.Sprintf("%v", kind)
			}
			tableOut += "	" + kind.String() + "	"
			defaultTag := field.Tag.Get("default")
			var defaultTagHTML string
			if defaultTag != "" {
				defaultTagHTML = " Default: " + defaultTag + ""
			}
			tableOut += field.Tag.Get("description") + defaultTagHTML + "	"
			tableOut += "
"
		}
		tableOut += "
"
		return tableOut
	},
	"isStyleDefault": func(style string) bool {
		return style == config.Config.DefaultStyle
	},
	"version": func() string {
		return config.Config.Version.String()
	},
}
<commit_msg>Fix missing template messages<commit_after>package gctemplates

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

	"github.com/gochan-org/gochan/pkg/config"
	"github.com/gochan-org/gochan/pkg/gcsql"
	"github.com/gochan-org/gochan/pkg/gcutil"
	x_html "golang.org/x/net/html"
)

var funcMap = template.FuncMap{
	// Arithmetic functions
	"add": func(a, b int) int {
		return a + b
	},
	"subtract": func(a, b int) int {
		return a - b
	},

	// Comparison functions (some copied from text/template for compatibility)
	"ge": func(a int, b int) bool {
		return a >= b
	},
	"gt": func(a int, b int) bool {
		return a > b
	},
	"le": func(a int, b int) bool {
		return a <= b
	},
	"lt": func(a int, b int) bool {
		return a < b
	},
	"intEq": func(a, b int) bool {
		return a == b
	},
	"isNil": func(i interface{}) bool {
		return i == nil
	},

	// Array functions
	"getSlice": func(arr []interface{}, start, length int) []interface{} {
		if start < 0 {
			start = 0
		}
		if length > len(arr) {
			length = len(arr)
		}
		return arr[start:length]
	},
	"len": func(arr []interface{}) int {
		return len(arr)
	},

	// String functions
	// "arrToString": arrToString,
	"intToString": strconv.Itoa,
	"escapeString": func(a string) string {
		return html.EscapeString(a)
	},
	"formatFilesize": func(sizeInt int) string {
		size := float32(sizeInt)
		if size < 1000 {
			return fmt.Sprintf("%d B", sizeInt)
		} else if size <= 100000 {
			return fmt.Sprintf("%0.1f KB", size/1024)
		} else if size <= 100000000 {
			return fmt.Sprintf("%0.2f MB", size/1024/1024)
		}
		return fmt.Sprintf("%0.2f GB", size/1024/1024/1024)
	},
	"formatTimestamp": func(t time.Time) string {
		return t.Format(config.Config.DateTimeFormat)
	},
	"stringAppend": func(strings ...string) string {
		var appended string
		for _, str := range strings {
			appended += str
		}
		return appended
	},
	"truncateMessage": func(msg string, limit int, maxLines int) string {
		var truncated bool
		split := strings.SplitN(msg, "
", -1)

		if len(split) > maxLines {
			split = split[:maxLines]
			msg = strings.Join(split, "
")
			truncated = true
		}

		if len(msg) < limit {
			if truncated {
				msg = msg + "..."
			}
			return msg
		}
		msg = msg[:limit]
		truncated = true

		if truncated {
			msg = msg + "..."
		}
		return msg
	},
	"stripHTML": func(htmlStr string) string {
		dom := x_html.NewTokenizer(strings.NewReader(htmlStr))
		for tokenType := dom.Next(); tokenType != x_html.ErrorToken; {
			if tokenType != x_html.TextToken {
				tokenType = dom.Next()
				continue
			}
			txtContent := strings.TrimSpace(x_html.UnescapeString(string(dom.Text())))
			if len(txtContent) > 0 {
				return x_html.EscapeString(txtContent)
			}
			tokenType = dom.Next()
		}
		return ""
	},
	"truncateString": func(msg string, limit int, ellipsis bool) string {
		if len(msg) > limit {
			if ellipsis {
				return msg[:limit] + "..."
			}
			return msg[:limit]
		}
		return msg
	},

	// Imageboard functions
	"bannedForever": func(banInfo *gcsql.BanInfo) bool {
		return banInfo.BannedForever()
	},
	"isBanned": func(banInfo *gcsql.BanInfo, board string) bool {
		return banInfo.IsBanned(board)
	},
	"getCatalogThumbnail": func(img string) string {
		return gcutil.GetThumbnailPath("catalog", img)
	},
	"getThreadID": func(postInterface interface{}) (thread int) {
		post, ok := postInterface.(gcsql.Post)
		if !ok {
			thread = 0
		} else if post.ParentID == 0 {
			thread = post.ID
		} else {
			thread = post.ParentID
		}
		return
	},
	"getPostURL": func(postInterface interface{}, typeOf string, withDomain bool) (postURL string) {
		if withDomain {
			postURL = config.Config.SiteDomain
		}
		postURL += config.Config.SiteWebfolder

		if typeOf == "recent" {
			post, ok := postInterface.(*gcsql.RecentPost)
			if !ok {
				return
			}
			postURL = post.GetURL(withDomain)
		} else {
			post, ok := postInterface.(*gcsql.Post)
			if !ok {
				return
			}
			postURL = post.GetURL(withDomain)
		}
		return
	},
	"getThreadThumbnail": func(img string) string {
		return gcutil.GetThumbnailPath("thread", img)
	},
	"getUploadType": func(name string) string {
		extension := gcutil.GetFileExtension(name)
		var uploadType string
		switch extension {
		case "":
			fallthrough
		case "deleted":
			uploadType = ""
		case "webm":
			fallthrough
		case "jpg":
			fallthrough
		case "jpeg":
			fallthrough
		case "gif":
			uploadType = "jpg"
		case "png":
			uploadType = "png"
		}
		return uploadType
	},
	"imageToThumbnailPath": func(thumbType string, img string) string {
		filetype := strings.ToLower(img[strings.LastIndex(img, ".")+1:])
		if filetype == "gif" || filetype == "webm" {
			filetype = "jpg"
		}
		index := strings.LastIndex(img, ".")
		if index < 0 || index > len(img) {
			return ""
		}
		thumbSuffix := "t." + filetype
		if thumbType == "catalog" {
			thumbSuffix = "c." + filetype
		}
		return img[0:index] + thumbSuffix
	},
	"numReplies": func(boardid, threadid int) int {
		num, err := gcsql.GetReplyCount(threadid)
		if err != nil {
			return 0
		}
		return num
	},
	"getBoardDir": func(id int) string {
		var board gcsql.Board
		if err := board.PopulateData(id); err != nil {
			return ""
		}
		return board.Dir
	},

	// Template convenience functions
	"makeLoop": func(n int, offset int) []int {
		loopArr := make([]int, n)
		for i := range loopArr {
			loopArr[i] = i + offset
		}
		return loopArr
	},
	"generateConfigTable": func() string {
		configType := reflect.TypeOf(config.Config)
		tableOut := `<table style="border-collapse: collapse;" id="config"><tr><th>Field name</th><th>Value</th><th>Type</th><th>Description</th></tr>`
		numFields := configType.NumField()
		for f := 17; f < numFields-2; f++ {
			// starting at Lockdown because the earlier fields can't be safely edited from a web interface
			field := configType.Field(f)
			if field.Tag.Get("critical") != "" {
				continue
			}
			name := field.Name
			tableOut += "
" + name + "	"
			f := reflect.Indirect(reflect.ValueOf(config.Config)).FieldByName(name)

			kind := f.Kind()
			switch kind {
			case reflect.Int:
				tableOut += `<input name="` + name + `" type="number" value="` + html.EscapeString(fmt.Sprintf("%v", f)) + `" class="config-text"/>`
			case reflect.String:
				tableOut += `<input name="` + name + `" type="text" value="` + html.EscapeString(fmt.Sprintf("%v", f)) + `" class="config-text"/>`
			case reflect.Bool:
				checked := ""
				if f.Bool() {
					checked = "checked"
				}
				tableOut += `<input name="` + name + `" type="checkbox" ` + checked + " />"
			case reflect.Slice:
				tableOut += `<textarea name="` + name + `" rows="4" cols="28">`
				arrLength := f.Len()
				for s := 0; s < arrLength; s++ {
					newLine := "\n"
					if s == arrLength-1 {
						newLine = ""
					}
					tableOut += html.EscapeString(f.Slice(s, s+1).Index(0).String()) + newLine
				}
				tableOut += "</textarea>"
			default:
				tableOut += fmt.Sprintf("%v", kind)
			}
			tableOut += "	" + kind.String() + "	"
			defaultTag := field.Tag.Get("default")
			var defaultTagHTML string
			if defaultTag != "" {
				defaultTagHTML = " Default: " + defaultTag + ""
			}
			tableOut += field.Tag.Get("description") + defaultTagHTML + "	"
			tableOut += "
"
		}
		tableOut += "
"
		return tableOut
	},
	"isStyleDefault": func(style string) bool {
		return style == config.Config.DefaultStyle
	},
	"version": func() string {
		return config.Config.Version.String()
	},
}
<|endoftext|>


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

package minikube

import (
	"fmt"
	"os"
	"os/exec"
	"regexp"
	"runtime"
	"strconv"
	"strings"
	"time"

	"knative.dev/kn-plugin-quickstart/pkg/install"
)

var clusterName string
var kubernetesVersion = "1.22.4"
var minikubeVersion = 1.23

// SetUp creates a local Minikube cluster and installs all the relevant Knative components
func SetUp(name string) error {
	start := time.Now()
	clusterName = name

	if err := createMinikubeCluster(); err != nil {
		return fmt.Errorf("creating cluster: %w", err)
	}
	if runtime.GOOS == "darwin" || runtime.GOOS == "windows" {
		fmt.Print("\n")
		fmt.Println("To finish setting up networking for minikube, run the following command in a separate terminal window:")
		fmt.Println("    minikube tunnel --profile knative")
		fmt.Println("The tunnel command must be running in a terminal window any time when using the knative quickstart environment.")
		fmt.Println("\nPress the Enter key to continue")
		fmt.Scanln()
	}
	if err := install.Serving(); err != nil {
		return fmt.Errorf("install serving: %w", err)
	}
	if err := install.Kourier(); err != nil {
		return fmt.Errorf("install kourier: %w", err)
	}
	if err := install.KourierMinikube(); err != nil {
		return fmt.Errorf("configure kourier: %w", err)
	}
	if err := install.Eventing(); err != nil {
		return fmt.Errorf("install eventing: %w", err)
	}

	finish := time.Since(start).Round(time.Second)
	fmt.Printf("🚀 Knative install took: %s \n", finish)
	fmt.Println("🎉 Now have some fun with Serverless and Event Driven Apps!")

	return nil
}

func createMinikubeCluster() error {
	if err := checkMinikubeVersion(); err != nil {
		return fmt.Errorf("minikube version: %w", err)
	}
	if err := checkForExistingCluster(); err != nil {
		return fmt.Errorf("existing cluster: %w", err)
	}
	return nil
}

// checkMinikubeVersion validates that the user has the correct version of Minikube installed.
// If not, it prompts the user to download a newer version before continuing.
func checkMinikubeVersion() error {
	versionCheck := exec.Command("minikube", "version", "--short")
	out, err := versionCheck.CombinedOutput()
	if err != nil {
		return fmt.Errorf("minikube version: %w", err)
	}
	fmt.Printf("Minikube version is: %s\n", string(out))

	userMinikubeVersion, err := parseMinikubeVersion(string(out))
	if err != nil {
		return fmt.Errorf("parsing minikube version: %w", err)
	}
	if userMinikubeVersion < minikubeVersion {
		var resp string
		fmt.Printf("WARNING: We require at least Minikube v%.2f, while you are using v%.2f\n", minikubeVersion, userMinikubeVersion)
		fmt.Println("You can download a newer version from https://github.com/kubernetes/minikube/releases/")
		fmt.Print("Continue anyway? (not recommended) [y/N]: ")
		fmt.Scanf("%s", &resp)
		if strings.ToLower(resp) != "y" {
			fmt.Println("Installation stopped. Please upgrade minikube and run again")
			os.Exit(0)
		}
	}

	return nil
}

// checkForExistingCluster checks if the user already has a Minikube cluster. If so, it provides
// the option of deleting the existing cluster and recreating it. If not, it proceeds to
// creating a new cluster
func checkForExistingCluster() error {
	getClusters := exec.Command("minikube", "profile", "list")
	out, err := getClusters.CombinedOutput()
	if err != nil {
		// there are no existing minikube profiles, the listing profiles command will error
		// if there were no profiles, we simply want to create a new one and not stop the install
		// so if the error is the "MK_USAGE_NO_PROFILE" error, we ignore it and continue onwards
		if !strings.Contains(string(out), "MK_USAGE_NO_PROFILE") {
			return fmt.Errorf("check cluster: %w", err)
		}
	}
	// TODO Add tests for regex
	r := regexp.MustCompile(clusterName)
	matches := r.Match(out)
	if matches {
		var resp string
		fmt.Print("Knative Cluster " + clusterName + " already installed.\nDelete and recreate [y/N]: ")
		fmt.Scanf("%s", &resp)
		if strings.ToLower(resp) != "y" {
			fmt.Println("Installation skipped")
			return nil
		}
		fmt.Println("deleting cluster...")
		deleteCluster := exec.Command("minikube", "delete", "--profile", clusterName)
		if err := deleteCluster.Run(); err != nil {
			return fmt.Errorf("delete cluster: %w", err)
		}
		if err := createNewCluster(); err != nil {
			return fmt.Errorf("new cluster: %w", err)
		}
		return nil
	}

	if err := createNewCluster(); err != nil {
		return fmt.Errorf("new cluster: %w", err)
	}

	return nil
}

// createNewCluster creates a new Minikube cluster
func createNewCluster() error {
	fmt.Println("☸ Creating Minikube cluster...")
	fmt.Println("\nBy default, using the standard minikube driver for your system")
	fmt.Println("If you wish to use a different driver, please configure minikube using")
	fmt.Print("    minikube config set driver <your-driver>\n\n")

	// create cluster and wait until ready
	createCluster := exec.Command("minikube", "start", "--kubernetes-version", kubernetesVersion, "--cpus", "3", "--profile", clusterName, "--wait", "all")
	if err := runCommandWithOutput(createCluster); err != nil {
		return fmt.Errorf("minikube create: %w", err)
	}

	return nil
}

func runCommandWithOutput(c *exec.Cmd) error {
	c.Stdout = os.Stdout
	c.Stderr = os.Stderr
	if err := c.Run(); err != nil {
		return fmt.Errorf("piping output: %w", err)
	}
	fmt.Print("\n")
	return nil
}

func parseMinikubeVersion(v string) (float64, error) {
	strippedVersion := strings.TrimLeft(strings.TrimRight(v, "\n"), "v")
	dotVersion := strings.Split(strippedVersion, ".")
	floatVersion, err := strconv.ParseFloat(dotVersion[0]+"."+dotVersion[1], 64)
	if err != nil {
		return 0, err
	}

	return floatVersion, nil
}
<commit_msg>use minikube config value for k8s version if present (#226)<commit_after>// Copyright © 2021 The Knative Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package minikube

import (
	"fmt"
	"os"
	"os/exec"
	"regexp"
	"runtime"
	"strconv"
	"strings"
	"time"

	"knative.dev/kn-plugin-quickstart/pkg/install"
)

var clusterName string
var kubernetesVersion = "1.23.3"
var minikubeVersion = 1.23

// SetUp creates a local Minikube cluster and installs all the relevant Knative components
func SetUp(name string) error {
	start := time.Now()
	clusterName = name

	if err := createMinikubeCluster(); err != nil {
		return fmt.Errorf("creating cluster: %w", err)
	}
	if runtime.GOOS == "darwin" || runtime.GOOS == "windows" {
		fmt.Print("\n")
		fmt.Println("To finish setting up networking for minikube, run the following command in a separate terminal window:")
		fmt.Println("    minikube tunnel --profile knative")
		fmt.Println("The tunnel command must be running in a terminal window any time when using the knative quickstart environment.")
		fmt.Println("\nPress the Enter key to continue")
		fmt.Scanln()
	}
	if err := install.Serving(); err != nil {
		return fmt.Errorf("install serving: %w", err)
	}
	if err := install.Kourier(); err != nil {
		return fmt.Errorf("install kourier: %w", err)
	}
	if err := install.KourierMinikube(); err != nil {
		return fmt.Errorf("configure kourier: %w", err)
	}
	if err := install.Eventing(); err != nil {
		return fmt.Errorf("install eventing: %w", err)
	}

	finish := time.Since(start).Round(time.Second)
	fmt.Printf("🚀 Knative install took: %s \n", finish)
	fmt.Println("🎉 Now have some fun with Serverless and Event Driven Apps!")

	return nil
}

func createMinikubeCluster() error {
	if err := checkMinikubeVersion(); err != nil {
		return fmt.Errorf("minikube version: %w", err)
	}
	if err := checkForExistingCluster(); err != nil {
		return fmt.Errorf("existing cluster: %w", err)
	}
	return nil
}

// checkMinikubeVersion validates that the user has the correct version of Minikube installed.
// If not, it prompts the user to download a newer version before continuing.
func checkMinikubeVersion() error {
	versionCheck := exec.Command("minikube", "version", "--short")
	out, err := versionCheck.CombinedOutput()
	if err != nil {
		return fmt.Errorf("minikube version: %w", err)
	}
	fmt.Printf("Minikube version is: %s\n", string(out))

	userMinikubeVersion, err := parseMinikubeVersion(string(out))
	if err != nil {
		return fmt.Errorf("parsing minikube version: %w", err)
	}
	if userMinikubeVersion < minikubeVersion {
		var resp string
		fmt.Printf("WARNING: We require at least Minikube v%.2f, while you are using v%.2f\n", minikubeVersion, userMinikubeVersion)
		fmt.Println("You can download a newer version from https://github.com/kubernetes/minikube/releases/")
		fmt.Print("Continue anyway? (not recommended) [y/N]: ")
		fmt.Scanf("%s", &resp)
		if strings.ToLower(resp) != "y" {
			fmt.Println("Installation stopped. Please upgrade minikube and run again")
			os.Exit(0)
		}
	}

	return nil
}

// checkForExistingCluster checks if the user already has a Minikube cluster. If so, it provides
// the option of deleting the existing cluster and recreating it. If not, it proceeds to
// creating a new cluster
func checkForExistingCluster() error {
	getClusters := exec.Command("minikube", "profile", "list")
	out, err := getClusters.CombinedOutput()
	if err != nil {
		// there are no existing minikube profiles, the listing profiles command will error
		// if there were no profiles, we simply want to create a new one and not stop the install
		// so if the error is the "MK_USAGE_NO_PROFILE" error, we ignore it and continue onwards
		if !strings.Contains(string(out), "MK_USAGE_NO_PROFILE") {
			return fmt.Errorf("check cluster: %w", err)
		}
	}
	// TODO Add tests for regex
	r := regexp.MustCompile(clusterName)
	matches := r.Match(out)
	if matches {
		var resp string
		fmt.Print("Knative Cluster " + clusterName + " already installed.\nDelete and recreate [y/N]: ")
		fmt.Scanf("%s", &resp)
		if strings.ToLower(resp) != "y" {
			fmt.Println("Installation skipped")
			return nil
		}
		fmt.Println("deleting cluster...")
		deleteCluster := exec.Command("minikube", "delete", "--profile", clusterName)
		if err := deleteCluster.Run(); err != nil {
			return fmt.Errorf("delete cluster: %w", err)
		}
		if err := createNewCluster(); err != nil {
			return fmt.Errorf("new cluster: %w", err)
		}
		return nil
	}

	if err := createNewCluster(); err != nil {
		return fmt.Errorf("new cluster: %w", err)
	}

	return nil
}

// createNewCluster creates a new Minikube cluster
func createNewCluster() error {
	fmt.Println("☸ Creating Minikube cluster...")
	fmt.Println("\nBy default, using the standard minikube driver for your system")
	fmt.Println("If you wish to use a different driver, please configure minikube using")
	fmt.Print("    minikube config set driver <your-driver>\n\n")

	// If minikube config kubernetes-version exists, use that instead of our default
	getMinikubeVersion := exec.Command("minikube", "config", "get", "kubernetes-version")
	out, err := getMinikubeVersion.Output()
	// if the command returns a config, then use that for the kubernetes version
	if err == nil {
		kubernetesVersion = strings.TrimRight(string(out), "\n")
	}

	// create cluster and wait until ready
	createCluster := exec.Command("minikube", "start", "--kubernetes-version", kubernetesVersion, "--cpus", "3", "--profile", clusterName, "--wait", "all")
	if err := runCommandWithOutput(createCluster); err != nil {
		return fmt.Errorf("minikube create: %w", err)
	}

	return nil
}

func runCommandWithOutput(c *exec.Cmd) error {
	c.Stdout = os.Stdout
	c.Stderr = os.Stderr
	if err := c.Run(); err != nil {
		return fmt.Errorf("piping output: %w", err)
	}
	fmt.Print("\n")
	return nil
}

func parseMinikubeVersion(v string) (float64, error) {
	strippedVersion := strings.TrimLeft(strings.TrimRight(v, "\n"), "v")
	dotVersion := strings.Split(strippedVersion, ".")
	floatVersion, err := strconv.ParseFloat(dotVersion[0]+"."+dotVersion[1], 64)
	if err != nil {
		return 0, err
	}

	return floatVersion, nil
}
<|endoftext|>


<commit_before>package paxos

import (
	"fmt"
	"log"
	"os"
)

const (
	rRnd = iota
	rVrnd
	rVval
	rNumParts
)

var (
	IdOutOfRange = os.NewError("Id Out of Range")
)

// TODO maybe we can make a better name for this. Not sure.
type Cluster interface {
	Putter
	Len() int
	Quorum() int
}

// TODO this is temporary during refactoring. we should remove it when we can.
type FakeCluster struct {
	outs Putter
	length uint64
	quorum uint64
}

func (f FakeCluster) Put(m Msg) {
	f.outs.Put(m)
}

func (f FakeCluster) Len() int {
	return int(f.length)
}

func (f FakeCluster) Quorum() int {
	return int(f.quorum)
}

// TODO temporary name
type C struct {
	cluster Cluster

	ins chan Msg
	outs Putter
	clock chan int
}

func coordinator(crnd, quorum, modulus uint64, tCh chan string, ins chan Msg, outs Putter, clock chan int, logger *log.Logger) {
	c := NewC(FakeCluster{outs, modulus, quorum})
	c.ins = ins
	c.clock = clock

	target := <-tCh
	if target == "" && closed(tCh) {
		return
	}

	// TODO this ugly cast will go away when we fix Msg
	c.process(target, int(crnd))
}

func NewC(c Cluster) *C {
	return &C{
		cluster: c,
		outs: c,
	}
}

func (c *C) process(target string, crnd int) {
	//if crnd > c.cluster.Len() {
	//	panic(IdOutOfRange)
	//}

	var cval string

Start:
	cval = ""
	start := Msg{
		Cmd:  "INVITE",
		To:   0, // send to all acceptors
		Body: fmt.Sprintf("%d", crnd),
	}
	c.outs.Put(start)

	var rsvps int
	var vr uint64
	var vv string

	for {
		select {
		case in := <-c.ins:
			if closed(c.ins) {
				goto Done
			}
			switch in.Cmd {
			case "RSVP":
				bodyParts := splitExactly(in.Body, rNumParts)

				// TODO this ugly cast will go away when we fix Msg
				i := int(dtoui64(bodyParts[rRnd]))

				vrnd := dtoui64(bodyParts[rVrnd])
				vval := bodyParts[rVval]

				if cval != "" {
					continue
				}

				if i < crnd {
					continue
				}

				if vrnd > vr {
					vr = vrnd
					vv = vval
				}

				rsvps++
				if rsvps >= c.cluster.Quorum() {
					var v string

					if vr > 0 {
						v = vv
					} else {
						v = target
					}
					cval = v

					choosen := Msg{
						Cmd:  "NOMINATE",
						To:   0, // send to all acceptors
						Body: fmt.Sprintf("%d:%s", crnd, v),
					}
					c.outs.Put(choosen)
				}
			}
		case <-c.clock:
			crnd += c.cluster.Len()
			goto Start
		}
	}

Done:
	return
}
<commit_msg>refactor<commit_after>package paxos

import (
	"fmt"
	"log"
	"os"
)

const (
	rRnd = iota
	rVrnd
	rVval
	rNumParts
)

var (
	IdOutOfRange = os.NewError("Id Out of Range")
)

// TODO maybe we can make a better name for this. Not sure.
type Cluster interface {
	Putter
	Len() int
	Quorum() int
}

// TODO this is temporary during refactoring. we should remove it when we can.
type FakeCluster struct {
	outs Putter
	length uint64
	quorum uint64
}

func (f FakeCluster) Put(m Msg) {
	f.outs.Put(m)
}

func (f FakeCluster) Len() int {
	return int(f.length)
}

func (f FakeCluster) Quorum() int {
	return int(f.quorum)
}

// TODO temporary name
type C struct {
	cluster Cluster

	ins chan Msg
	outs Putter
	clock chan int
}

func coordinator(crnd, quorum, modulus uint64, tCh chan string, ins chan Msg, outs Putter, clock chan int, logger *log.Logger) {
	c := NewC(FakeCluster{outs, modulus, quorum})
	c.ins = ins
	c.clock = clock

	target := <-tCh
	if target == "" && closed(tCh) {
		return
	}

	// TODO this ugly cast will go away when we fix Msg
	c.process(target, int(crnd))
}

func NewC(c Cluster) *C {
	return &C{
		cluster: c,
		outs: c,
	}
}

func (c *C) process(target string, crnd int) {
	//if crnd > c.cluster.Len() {
	//	panic(IdOutOfRange)
	//}

	var cval string

Start:
	cval = ""
	start := Msg{
		Cmd:  "INVITE",
		To:   0, // send to all acceptors
		Body: fmt.Sprintf("%d", crnd),
	}
	c.cluster.Put(start)

	var rsvps int
	var vr uint64
	var vv string

	for {
		select {
		case in := <-c.ins:
			if closed(c.ins) {
				goto Done
			}
			switch in.Cmd {
			case "RSVP":
				bodyParts := splitExactly(in.Body, rNumParts)

				// TODO this ugly cast will go away when we fix Msg
				i := int(dtoui64(bodyParts[rRnd]))

				vrnd := dtoui64(bodyParts[rVrnd])
				vval := bodyParts[rVval]

				if cval != "" {
					continue
				}

				if i < crnd {
					continue
				}

				if vrnd > vr {
					vr = vrnd
					vv = vval
				}

				rsvps++
				if rsvps >= c.cluster.Quorum() {
					var v string

					if vr > 0 {
						v = vv
					} else {
						v = target
					}
					cval = v

					choosen := Msg{
						Cmd:  "NOMINATE",
						To:   0, // send to all acceptors
						Body: fmt.Sprintf("%d:%s", crnd, v),
					}
					c.cluster.Put(choosen)
				}
			}
		case <-c.clock:
			crnd += c.cluster.Len()
			goto Start
		}
	}

Done:
	return
}
<|endoftext|>


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

package policy

import (
	"encoding/json"

	"github.com/cilium/cilium/pkg/labels"
	"github.com/cilium/cilium/pkg/lock"
	"github.com/cilium/cilium/pkg/policy/api"

	log "github.com/sirupsen/logrus"
)

// Repository is a list of policy rules which in combination form the security
// policy. A policy repository can be
type Repository struct {
	// Mutex protects the whole policy tree
	Mutex lock.RWMutex
	rules []*rule

	// revision is the revision of the policy repository. It will be
	// incremented whenever the policy repository is changed
	revision uint64
}

// NewPolicyRepository allocates a new policy repository
func NewPolicyRepository() *Repository {
	return &Repository{}
}

// traceState is an internal structure used to collect information
// while determining policy decision
type traceState struct {
	// selectedRules is the number of rules with matching EndpointSelector
	selectedRules int

	// ruleID is the rule ID currently being evaluated
	ruleID int
}

// CanReachRLocked evaluates the policy repository for the provided search
// context and returns the verdict or api.Undecided if no rule matches. The
// policy repository mutex must be held.
func (p *Repository) CanReachRLocked(ctx *SearchContext) api.Decision {
	decision := api.Undecided
	state := traceState{}

	for i, r := range p.rules {
		state.ruleID = i
		switch r.canReach(ctx, &state) {
		// The rule contained a constraint which was not met, this
		// connection is not allowed
		case api.Denied:
			return api.Denied

		// The rule allowed the connection but a later rule may impose
		// additional constraints, so we store the decision but allow
		// it to be overwritten by an additional requirement
		case api.Allowed:
			decision = api.Allowed
		}
	}

	ctx.PolicyTrace("%d rules matched", state.selectedRules)

	return decision
}

// AllowsLabelAccess evaluates the policy repository for the provided search
// context and returns the verdict. If no matching policy allows for the
// connection, the request will be denied. The policy repository mutex must be
// held.
func (p *Repository) AllowsLabelAccess(ctx *SearchContext) api.Decision {
	ctx.PolicyTrace("Tracing %s\n", ctx.String())
	decision := api.Denied

	if len(p.rules) == 0 {
		ctx.PolicyTrace("  No rules found\n")
	} else {
		if p.CanReachRLocked(ctx) == api.Allowed {
			decision = api.Allowed
		}
	}

	ctx.PolicyTrace("Label verdict: %s\n", decision.String())

	return decision
}

// ResolveL4Policy resolves the L4 policy for a set of endpoints by searching
// the policy repository for `PortRule` rules that are attached to a `Rule`
// where the EndpointSelector matches `ctx.To`. `ctx.From` takes no effect and
// is ignored in the search.  If multiple `PortRule` rules are found, all rules
// are merged together. If rules contains overlapping port definitions, the first
// rule found in the repository takes precedence.
//
// TODO: Coalesce l7 rules?
func (p *Repository) ResolveL4Policy(ctx *SearchContext) (*L4Policy, error) {
	result := NewL4Policy()

	if ctx.EgressL4Only {
		ctx.PolicyTrace("Resolving egress port policy for %+v\n", ctx.To)
	} else if ctx.IngressL4Only {
		ctx.PolicyTrace("Resolving ingress port policy for %+v\n", ctx.To)
	} else {
		ctx.PolicyTrace("Resolving port policy for %+v\n", ctx.To)
	}

	state := traceState{}
	for _, r := range p.rules {
		_, err := r.resolveL4Policy(ctx, &state, result)
		if err != nil {
			return nil, err
		}
		state.ruleID++
	}

	ctx.PolicyTrace("%d rules matched\n", state.selectedRules)
	return result, nil
}

// ResolveL3Policy resolves the L3 policy for a set of endpoints by searching
// the policy repository for `CIDR` rules that are attached to a `Rule`
// where the EndpointSelector matches `ctx.To`. `ctx.From` takes no effect and
// is ignored in the search.
func (p *Repository) ResolveL3Policy(ctx *SearchContext) *L3Policy {
	result := NewL3Policy()

	ctx.PolicyTrace("Resolving L3 (CIDR) policy for %+v\n", ctx.To)

	state := traceState{}
	for _, r := range p.rules {
		r.resolveL3Policy(ctx, &state, result)
		state.ruleID++
	}

	ctx.PolicyTrace("%d rules matched\n", state.selectedRules)
	return result
}

func (p *Repository) allowsL4Egress(searchCtx *SearchContext) api.Decision {
	ctx := *searchCtx
	ctx.To = ctx.From
	ctx.From = labels.LabelArray{}
	ctx.EgressL4Only = true

	ctx.PolicyTrace("\n")
	policy, err := p.ResolveL4Policy(&ctx)
	if err != nil {
		log.WithError(err).Warning("Evaluation error while resolving L4 egress policy")
	}
	verdict := api.Undecided
	if err == nil && len(policy.Egress) > 0 {
		verdict = policy.EgressCoversDPorts(ctx.DPorts)
	}

	if len(ctx.DPorts) == 0 {
		ctx.PolicyTrace("L4 egress verdict: [no port context specified]\n")
	} else {
		ctx.PolicyTrace("L4 egress verdict: %s\n", verdict.String())
	}

	return verdict
}

func (p *Repository) allowsL4Ingress(ctx *SearchContext) api.Decision {
	ctx.IngressL4Only = true

	ctx.PolicyTrace("\n")
	policy, err := p.ResolveL4Policy(ctx)
	if err != nil {
		log.WithError(err).Warning("Evaluation error while resolving L4 ingress policy")
	}
	verdict := api.Undecided
	if err == nil && len(policy.Ingress) > 0 {
		verdict = policy.IngressCoversContext(ctx)
	}

	if len(ctx.DPorts) == 0 {
		ctx.PolicyTrace("L4 ingress verdict: [no port context specified]\n")
	} else {
		ctx.PolicyTrace("L4 ingress verdict: %s\n", verdict.String())
	}

	return verdict
}

// AllowsRLocked evaluates the policy repository for the provided search
// context and returns the verdict. If no matching policy allows for the
// connection, the request will be denied. The policy repository mutex must be
// held.
func (p *Repository) AllowsRLocked(ctx *SearchContext) api.Decision {
	ctx.PolicyTrace("Tracing %s\n", ctx.String())
	decision := p.CanReachRLocked(ctx)
	ctx.PolicyTrace("Label verdict: %s\n", decision.String())
	if decision == api.Allowed {
		return decision
	}

	// We only report the overall decision as L4 inclusive if a port has
	// been specified
	if len(ctx.DPorts) != 0 {
		l4Egress := p.allowsL4Egress(ctx)
		l4Ingress := p.allowsL4Ingress(ctx)

		// Explicit deny should deny; Allow+Undecided should allow
		if l4Egress == api.Denied || l4Ingress == api.Denied {
			decision = api.Denied
		} else if l4Egress == api.Allowed || l4Ingress == api.Allowed {
			decision = api.Allowed
		}
	}

	if decision != api.Allowed {
		decision = api.Denied
	}
	return decision
}

// SearchRLocked searches the policy repository for rules which match the
// specified labels and will return an array of all rules which matched.
func (p *Repository) SearchRLocked(labels labels.LabelArray) api.Rules {
	result := api.Rules{}

	for _, r := range p.rules {
		if r.Labels.Contains(labels) {
			result = append(result, &r.Rule)
		}
	}

	return result
}

// Add inserts a rule into the policy repository
func (p *Repository) Add(r api.Rule) (uint64, error) {
	p.Mutex.Lock()
	defer p.Mutex.Unlock()

	realRule := &rule{Rule: r}
	if err := realRule.sanitize(); err != nil {
		return p.revision, err
	}

	p.rules = append(p.rules, realRule)
	p.revision++

	return p.revision, nil
}

// AddListLocked inserts a rule into the policy repository with the repository already locked
func (p *Repository) AddListLocked(rules api.Rules) (uint64, error) {
	// Validate entire rule list first and only append array if
	// all rules are valid
	newList := make([]*rule, len(rules))
	for i := range rules {
		newList[i] = &rule{Rule: *rules[i]}
		if err := newList[i].sanitize(); err != nil {
			return p.revision, err
		}
	}

	p.rules = append(p.rules, newList...)
	p.revision++

	return p.revision, nil
}

// AddList inserts a rule into the policy repository
func (p *Repository) AddList(rules api.Rules) (uint64, error) {
	p.Mutex.Lock()
	defer p.Mutex.Unlock()
	return p.AddListLocked(rules)
}

// DeleteByLabelsLocked deletes all rules in the policy repository which
// contain the specified labels
func (p *Repository) DeleteByLabelsLocked(labels labels.LabelArray) (uint64, int) {
	deleted := 0
	new := p.rules[:0]

	for _, r := range p.rules {
		if !r.Labels.Contains(labels) {
			new = append(new, r)
		} else {
			deleted++
		}
	}

	if deleted > 0 {
		p.revision++
		p.rules = new
	}

	return p.revision, deleted
}

// DeleteByLabels deletes all rules in the policy repository which contain the
// specified labels
func (p *Repository) DeleteByLabels(labels labels.LabelArray) (uint64, int) {
	p.Mutex.Lock()
	defer p.Mutex.Unlock()
	return p.DeleteByLabelsLocked(labels)
}

// JSONMarshalRules returns a slice of policy rules as string in JSON
// representation
func JSONMarshalRules(rules api.Rules) string {
	b, err := json.MarshalIndent(rules, "", "  ")
	if err != nil {
		return err.Error()
	}
	return string(b)
}

// GetJSON returns all rules of the policy repository as string in JSON
// representation
func (p *Repository) GetJSON() string {
	p.Mutex.RLock()
	defer p.Mutex.RUnlock()

	result := api.Rules{}
	for _, r := range p.rules {
		result = append(result, &r.Rule)
	}

	return JSONMarshalRules(result)
}

// GetRulesMatching returns whether any of the rules in a repository contain a
// rule with labels matching the labels in the provided LabelArray.
//
// Must be called with p.Mutex held
func (p *Repository) GetRulesMatching(labels labels.LabelArray, includeEntities bool) bool {

	for _, r := range p.rules {
		rulesMatch := r.EndpointSelector.Matches(labels)
		if rulesMatch {
			return true
		}

		if includeEntities && len(r.toEntities)+len(r.fromEntities) > 0 {
			return true
		}
	}
	return false
}

// NumRules returns the amount of rules in the policy repository.
//
// Must be called with p.Mutex held
func (p *Repository) NumRules() int {
	return len(p.rules)
}

// GetRevision returns the revision of the policy repository
func (p *Repository) GetRevision() uint64 {
	return p.revision
}
<commit_msg>policy: Trace matched rules on deny rule<commit_after>// Copyright 2016-2017 Authors of Cilium
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package policy

import (
	"encoding/json"

	"github.com/cilium/cilium/pkg/labels"
	"github.com/cilium/cilium/pkg/lock"
	"github.com/cilium/cilium/pkg/policy/api"

	log "github.com/sirupsen/logrus"
)

// Repository is a list of policy rules which in combination form the security
// policy. A policy repository can be
type Repository struct {
	// Mutex protects the whole policy tree
	Mutex lock.RWMutex
	rules []*rule

	// revision is the revision of the policy repository. It will be
	// incremented whenever the policy repository is changed
	revision uint64
}

// NewPolicyRepository allocates a new policy repository
func NewPolicyRepository() *Repository {
	return &Repository{}
}

// traceState is an internal structure used to collect information
// while determining policy decision
type traceState struct {
	// selectedRules is the number of rules with matching EndpointSelector
	selectedRules int

	// ruleID is the rule ID currently being evaluated
	ruleID int
}

// CanReachRLocked evaluates the policy repository for the provided search
// context and returns the verdict or api.Undecided if no rule matches. The
// policy repository mutex must be held.
func (p *Repository) CanReachRLocked(ctx *SearchContext) api.Decision {
	decision := api.Undecided
	state := traceState{}

loop:
	for i, r := range p.rules {
		state.ruleID = i
		switch r.canReach(ctx, &state) {
		// The rule contained a constraint which was not met, this
		// connection is not allowed
		case api.Denied:
			decision = api.Denied
			break loop

		// The rule allowed the connection but a later rule may impose
		// additional constraints, so we store the decision but allow
		// it to be overwritten by an additional requirement
		case api.Allowed:
			decision = api.Allowed
		}
	}

	ctx.PolicyTrace("%d rules matched", state.selectedRules)

	return decision
}

// AllowsLabelAccess evaluates the policy repository for the provided search
// context and returns the verdict. If no matching policy allows for the
// connection, the request will be denied. The policy repository mutex must be
// held.
func (p *Repository) AllowsLabelAccess(ctx *SearchContext) api.Decision {
	ctx.PolicyTrace("Tracing %s\n", ctx.String())
	decision := api.Denied

	if len(p.rules) == 0 {
		ctx.PolicyTrace("  No rules found\n")
	} else {
		if p.CanReachRLocked(ctx) == api.Allowed {
			decision = api.Allowed
		}
	}

	ctx.PolicyTrace("Label verdict: %s\n", decision.String())

	return decision
}

// ResolveL4Policy resolves the L4 policy for a set of endpoints by searching
// the policy repository for `PortRule` rules that are attached to a `Rule`
// where the EndpointSelector matches `ctx.To`. `ctx.From` takes no effect and
// is ignored in the search.  If multiple `PortRule` rules are found, all rules
// are merged together. If rules contains overlapping port definitions, the first
// rule found in the repository takes precedence.
//
// TODO: Coalesce l7 rules?
func (p *Repository) ResolveL4Policy(ctx *SearchContext) (*L4Policy, error) {
	result := NewL4Policy()

	if ctx.EgressL4Only {
		ctx.PolicyTrace("Resolving egress port policy for %+v\n", ctx.To)
	} else if ctx.IngressL4Only {
		ctx.PolicyTrace("Resolving ingress port policy for %+v\n", ctx.To)
	} else {
		ctx.PolicyTrace("Resolving port policy for %+v\n", ctx.To)
	}

	state := traceState{}
	for _, r := range p.rules {
		_, err := r.resolveL4Policy(ctx, &state, result)
		if err != nil {
			return nil, err
		}
		state.ruleID++
	}

	ctx.PolicyTrace("%d rules matched\n", state.selectedRules)
	return result, nil
}

// ResolveL3Policy resolves the L3 policy for a set of endpoints by searching
// the policy repository for `CIDR` rules that are attached to a `Rule`
// where the EndpointSelector matches `ctx.To`. `ctx.From` takes no effect and
// is ignored in the search.
func (p *Repository) ResolveL3Policy(ctx *SearchContext) *L3Policy {
	result := NewL3Policy()

	ctx.PolicyTrace("Resolving L3 (CIDR) policy for %+v\n", ctx.To)

	state := traceState{}
	for _, r := range p.rules {
		r.resolveL3Policy(ctx, &state, result)
		state.ruleID++
	}

	ctx.PolicyTrace("%d rules matched\n", state.selectedRules)
	return result
}

func (p *Repository) allowsL4Egress(searchCtx *SearchContext) api.Decision {
	ctx := *searchCtx
	ctx.To = ctx.From
	ctx.From = labels.LabelArray{}
	ctx.EgressL4Only = true

	ctx.PolicyTrace("\n")
	policy, err := p.ResolveL4Policy(&ctx)
	if err != nil {
		log.WithError(err).Warning("Evaluation error while resolving L4 egress policy")
	}
	verdict := api.Undecided
	if err == nil && len(policy.Egress) > 0 {
		verdict = policy.EgressCoversDPorts(ctx.DPorts)
	}

	if len(ctx.DPorts) == 0 {
		ctx.PolicyTrace("L4 egress verdict: [no port context specified]\n")
	} else {
		ctx.PolicyTrace("L4 egress verdict: %s\n", verdict.String())
	}

	return verdict
}

func (p *Repository) allowsL4Ingress(ctx *SearchContext) api.Decision {
	ctx.IngressL4Only = true

	ctx.PolicyTrace("\n")
	policy, err := p.ResolveL4Policy(ctx)
	if err != nil {
		log.WithError(err).Warning("Evaluation error while resolving L4 ingress policy")
	}
	verdict := api.Undecided
	if err == nil && len(policy.Ingress) > 0 {
		verdict = policy.IngressCoversContext(ctx)
	}

	if len(ctx.DPorts) == 0 {
		ctx.PolicyTrace("L4 ingress verdict: [no port context specified]\n")
	} else {
		ctx.PolicyTrace("L4 ingress verdict: %s\n", verdict.String())
	}

	return verdict
}

// AllowsRLocked evaluates the policy repository for the provided search
// context and returns the verdict. If no matching policy allows for the
// connection, the request will be denied. The policy repository mutex must be
// held.
func (p *Repository) AllowsRLocked(ctx *SearchContext) api.Decision {
	ctx.PolicyTrace("Tracing %s\n", ctx.String())
	decision := p.CanReachRLocked(ctx)
	ctx.PolicyTrace("Label verdict: %s\n", decision.String())
	if decision == api.Allowed {
		return decision
	}

	// We only report the overall decision as L4 inclusive if a port has
	// been specified
	if len(ctx.DPorts) != 0 {
		l4Egress := p.allowsL4Egress(ctx)
		l4Ingress := p.allowsL4Ingress(ctx)

		// Explicit deny should deny; Allow+Undecided should allow
		if l4Egress == api.Denied || l4Ingress == api.Denied {
			decision = api.Denied
		} else if l4Egress == api.Allowed || l4Ingress == api.Allowed {
			decision = api.Allowed
		}
	}

	if decision != api.Allowed {
		decision = api.Denied
	}
	return decision
}

// SearchRLocked searches the policy repository for rules which match the
// specified labels and will return an array of all rules which matched.
func (p *Repository) SearchRLocked(labels labels.LabelArray) api.Rules {
	result := api.Rules{}

	for _, r := range p.rules {
		if r.Labels.Contains(labels) {
			result = append(result, &r.Rule)
		}
	}

	return result
}

// Add inserts a rule into the policy repository
func (p *Repository) Add(r api.Rule) (uint64, error) {
	p.Mutex.Lock()
	defer p.Mutex.Unlock()

	realRule := &rule{Rule: r}
	if err := realRule.sanitize(); err != nil {
		return p.revision, err
	}

	p.rules = append(p.rules, realRule)
	p.revision++

	return p.revision, nil
}

// AddListLocked inserts a rule into the policy repository with the repository already locked
func (p *Repository) AddListLocked(rules api.Rules) (uint64, error) {
	// Validate entire rule list first and only append array if
	// all rules are valid
	newList := make([]*rule, len(rules))
	for i := range rules {
		newList[i] = &rule{Rule: *rules[i]}
		if err := newList[i].sanitize(); err != nil {
			return p.revision, err
		}
	}

	p.rules = append(p.rules, newList...)
	p.revision++

	return p.revision, nil
}

// AddList inserts a rule into the policy repository
func (p *Repository) AddList(rules api.Rules) (uint64, error) {
	p.Mutex.Lock()
	defer p.Mutex.Unlock()
	return p.AddListLocked(rules)
}

// DeleteByLabelsLocked deletes all rules in the policy repository which
// contain the specified labels
func (p *Repository) DeleteByLabelsLocked(labels labels.LabelArray) (uint64, int) {
	deleted := 0
	new := p.rules[:0]

	for _, r := range p.rules {
		if !r.Labels.Contains(labels) {
			new = append(new, r)
		} else {
			deleted++
		}
	}

	if deleted > 0 {
		p.revision++
		p.rules = new
	}

	return p.revision, deleted
}

// DeleteByLabels deletes all rules in the policy repository which contain the
// specified labels
func (p *Repository) DeleteByLabels(labels labels.LabelArray) (uint64, int) {
	p.Mutex.Lock()
	defer p.Mutex.Unlock()
	return p.DeleteByLabelsLocked(labels)
}

// JSONMarshalRules returns a slice of policy rules as string in JSON
// representation
func JSONMarshalRules(rules api.Rules) string {
	b, err := json.MarshalIndent(rules, "", "  ")
	if err != nil {
		return err.Error()
	}
	return string(b)
}

// GetJSON returns all rules of the policy repository as string in JSON
// representation
func (p *Repository) GetJSON() string {
	p.Mutex.RLock()
	defer p.Mutex.RUnlock()

	result := api.Rules{}
	for _, r := range p.rules {
		result = append(result, &r.Rule)
	}

	return JSONMarshalRules(result)
}

// GetRulesMatching returns whether any of the rules in a repository contain a
// rule with labels matching the labels in the provided LabelArray.
//
// Must be called with p.Mutex held
func (p *Repository) GetRulesMatching(labels labels.LabelArray, includeEntities bool) bool {

	for _, r := range p.rules {
		rulesMatch := r.EndpointSelector.Matches(labels)
		if rulesMatch {
			return true
		}

		if includeEntities && len(r.toEntities)+len(r.fromEntities) > 0 {
			return true
		}
	}
	return false
}

// NumRules returns the amount of rules in the policy repository.
//
// Must be called with p.Mutex held
func (p *Repository) NumRules() int {
	return len(p.rules)
}

// GetRevision returns the revision of the policy repository
func (p *Repository) GetRevision() uint64 {
	return p.revision
}
<|endoftext|>


<commit_before>package agent

import (
	log "github.com/coreos/fleet/third_party/github.com/golang/glog"

	"github.com/coreos/fleet/event"
	"github.com/coreos/fleet/job"
	"github.com/coreos/fleet/unit"
)

type EventHandler struct {
	agent *Agent
}

func NewEventHandler(agent *Agent) *EventHandler {
	return &EventHandler{agent}
}

func (eh *EventHandler) HandleEventJobOffered(ev event.Event) {
	jo := ev.Payload.(job.JobOffer)

	if !jo.OfferedTo(eh.agent.Machine.State().ID) {
		log.V(1).Infof("EventJobOffered(%s): not offered to this machine, ignoring", jo.Job.Name)
		return
	}

	log.Infof("EventJobOffered(%s): deciding whether to bid or not", jo.Job.Name)
	eh.agent.MaybeBid(jo)
}

func (eh *EventHandler) HandleEventJobScheduled(ev event.Event) {
	jobName := ev.Payload.(string)
	target := ev.Context.(string)

	if target != eh.agent.Machine.State().ID {
		log.Infof("EventJobScheduled(%s): Job scheduled to other Machine(%s), informing Agent", jobName, target)
		eh.agent.JobScheduledElsewhere(jobName)
	} else {
		log.Infof("EventJobScheduled(%s): Job scheduled here, informing Agent", jobName)
		eh.agent.JobScheduledLocally(jobName)
	}
}

func (eh *EventHandler) HandleCommandStartJob(ev event.Event) {
	jobName := ev.Payload.(string)
	target := ev.Context.(string)

	if target != eh.agent.Machine.State().ID {
		log.V(1).Infof("CommandStartJob(%s): scheduled elsewhere, ignoring", jobName)
		return
	}

	log.Infof("CommandStartJob(%s): instructing Agent to start Job", jobName)
	eh.agent.StartJob(jobName)
}

func (eh *EventHandler) HandleCommandStopJob(ev event.Event) {
	jobName := ev.Payload.(string)
	target := ev.Context.(string)

	if target != eh.agent.Machine.State().ID {
		log.V(1).Infof("CommandStopJob(%s): scheduled elsewhere, ignoring", jobName)
		return
	}

	log.Infof("CommandStopJob(%s): instructing Agent to stop Job", jobName)
	eh.agent.StopJob(jobName)
}

func (eh *EventHandler) HandleEventJobUnscheduled(ev event.Event) {
	eh.unloadJobEvent(ev)
}

func (eh *EventHandler) HandleEventJobDestroyed(ev event.Event) {
	eh.unloadJobEvent(ev)
}

// unloadJobEvent handles an event by unloading the job to which it
// refers. The event's payload must be a string representing the
// name of a Job. If the Job is not scheduled locally, it will be
// ignored.
func (eh *EventHandler) unloadJobEvent(ev event.Event) {
	jobName := ev.Payload.(string)

	log.Infof("%s(%s): Job(%s) unscheduled, deciding what to do", ev.Type, jobName, jobName)
	eh.agent.JobUnscheduled(jobName)
}

func (eh *EventHandler) HandleEventUnitStateUpdated(ev event.Event) {
	jobName := ev.Context.(string)
	state := ev.Payload.(*unit.UnitState)

	if state == nil {
		log.V(1).Infof("EventUnitStateUpdated(%s): received nil UnitState object, ignoring", jobName)
		return
	}

	log.Infof("EventUnitStateUpdated(%s): reporting state to Registry")
	eh.agent.ReportUnitState(jobName, state)
}
<commit_msg>fix(agent): missing jobName in log message<commit_after>package agent

import (
	log "github.com/coreos/fleet/third_party/github.com/golang/glog"

	"github.com/coreos/fleet/event"
	"github.com/coreos/fleet/job"
	"github.com/coreos/fleet/unit"
)

type EventHandler struct {
	agent *Agent
}

func NewEventHandler(agent *Agent) *EventHandler {
	return &EventHandler{agent}
}

func (eh *EventHandler) HandleEventJobOffered(ev event.Event) {
	jo := ev.Payload.(job.JobOffer)

	if !jo.OfferedTo(eh.agent.Machine.State().ID) {
		log.V(1).Infof("EventJobOffered(%s): not offered to this machine, ignoring", jo.Job.Name)
		return
	}

	log.Infof("EventJobOffered(%s): deciding whether to bid or not", jo.Job.Name)
	eh.agent.MaybeBid(jo)
}

func (eh *EventHandler) HandleEventJobScheduled(ev event.Event) {
	jobName := ev.Payload.(string)
	target := ev.Context.(string)

	if target != eh.agent.Machine.State().ID {
		log.Infof("EventJobScheduled(%s): Job scheduled to other Machine(%s), informing Agent", jobName, target)
		eh.agent.JobScheduledElsewhere(jobName)
	} else {
		log.Infof("EventJobScheduled(%s): Job scheduled here, informing Agent", jobName)
		eh.agent.JobScheduledLocally(jobName)
	}
}

func (eh *EventHandler) HandleCommandStartJob(ev event.Event) {
	jobName := ev.Payload.(string)
	target := ev.Context.(string)

	if target != eh.agent.Machine.State().ID {
		log.V(1).Infof("CommandStartJob(%s): scheduled elsewhere, ignoring", jobName)
		return
	}

	log.Infof("CommandStartJob(%s): instructing Agent to start Job", jobName)
	eh.agent.StartJob(jobName)
}

func (eh *EventHandler) HandleCommandStopJob(ev event.Event) {
	jobName := ev.Payload.(string)
	target := ev.Context.(string)

	if target != eh.agent.Machine.State().ID {
		log.V(1).Infof("CommandStopJob(%s): scheduled elsewhere, ignoring", jobName)
		return
	}

	log.Infof("CommandStopJob(%s): instructing Agent to stop Job", jobName)
	eh.agent.StopJob(jobName)
}

func (eh *EventHandler) HandleEventJobUnscheduled(ev event.Event) {
	eh.unloadJobEvent(ev)
}

func (eh *EventHandler) HandleEventJobDestroyed(ev event.Event) {
	eh.unloadJobEvent(ev)
}

// unloadJobEvent handles an event by unloading the job to which it
// refers. The event's payload must be a string representing the
// name of a Job. If the Job is not scheduled locally, it will be
// ignored.
func (eh *EventHandler) unloadJobEvent(ev event.Event) {
	jobName := ev.Payload.(string)

	log.Infof("%s(%s): Job(%s) unscheduled, deciding what to do", ev.Type, jobName, jobName)
	eh.agent.JobUnscheduled(jobName)
}

func (eh *EventHandler) HandleEventUnitStateUpdated(ev event.Event) {
	jobName := ev.Context.(string)
	state := ev.Payload.(*unit.UnitState)

	if state == nil {
		log.V(1).Infof("EventUnitStateUpdated(%s): received nil UnitState object, ignoring", jobName)
		return
	}

	log.Infof("EventUnitStateUpdated(%s): reporting state to Registry", jobName)
	eh.agent.ReportUnitState(jobName, state)
}
<|endoftext|>


<commit_before>package tagexpressions

import (
	"github.com/stretchr/testify/require"
	"testing"
)

func TestParse(t *testing.T) {
	t.Run("toString", func(t *testing.T) {
		examples := [][]string{
			{"a and b", "( a and b )"},
			{"a or b", "( a or b )"},
			{"not a", "not ( a )"},
			{"( a and b ) or ( c and d )", "( ( a and b ) or ( c and d ) )"},
			{
				"not a or b and not c or not d or e and f",
				"( ( ( not ( a ) or ( b and not ( c ) ) ) or not ( d ) ) or ( e and f ) )",
			},
			{
				"not a\\(\\) or b and not c or not d or e and f",
				"( ( ( not ( a\\(\\) ) or ( b and not ( c ) ) ) or not ( d ) ) or ( e and f ) )",
			},
			// a or not b
		}
		for _, example := range examples {
			infix := example[0]
			expectedStr := example[1]
			actual, err := Parse(infix)
			require.NoError(t, err)
			actualStr := actual.ToString()
			require.Equal(t, expectedStr, actualStr)

			roundTripActual, err := Parse(actualStr)
			require.NoError(t, err)
			roundTripActualStr := roundTripActual.ToString()
			require.Equal(t, expectedStr, roundTripActualStr)
		}
	})

	t.Run("syntax errors", func(t *testing.T) {
		examples := [][]string{
			{"@a @b or", "Syntax error. Expected operator"},
			{"@a and (@b not)", "Syntax error. Expected operator"},
			{"@a and (@b @c) or", "Syntax error. Expected operator"},
			{"@a and or", "Syntax error. Expected operand"},
			{"or or", "Syntax error. Expected operand"},
			{"a b", "Syntax error. Expected operator"},
			{"( a and b ) )", "Syntax error. Unmatched )"},
			{"( ( a and b )", "Syntax error. Unmatched ("},
			// a or not b
		}
		for _, example := range examples {
			infix := example[0]
			expectedErrMessage := example[1]
			_, err := Parse(infix)
			require.Error(t, err)
			require.Equal(t, expectedErrMessage, err.Error())
		}
	})

	t.Run("evalutation errors", func(t *testing.T) {

		t.Run("evaluates not", func(t *testing.T) {
			expr, err := Parse("not   x")
			require.NoError(t, err)
			require.False(t, expr.Evaluate([]string{"x"}))
			require.True(t, expr.Evaluate([]string{"y"}))
		})

		t.Run("evaluates and", func(t *testing.T) {
			expr, err := Parse("x and y")
			require.NoError(t, err)
			require.True(t, expr.Evaluate([]string{"x", "y"}))
			require.False(t, expr.Evaluate([]string{"y"}))
			require.False(t, expr.Evaluate([]string{"x"}))
		})

		t.Run("evaluates or", func(t *testing.T) {
			expr, err := Parse("  x or(y) ")
			require.NoError(t, err)
			require.False(t, expr.Evaluate([]string{}))
			require.True(t, expr.Evaluate([]string{"y"}))
			require.True(t, expr.Evaluate([]string{"x"}))
		})

		t.Run("evaluates expressions with escaped chars", func(t *testing.T) {
			expr, err := Parse("  x\\(1\\) or(y\\(2\\)) ")
			require.NoError(t, err)
			require.False(t, expr.Evaluate([]string{}))
			require.True(t, expr.Evaluate([]string{"y(2)"}))
			require.True(t, expr.Evaluate([]string{"x(1)"}))
			require.False(t, expr.Evaluate([]string{"y"}))
			require.False(t, expr.Evaluate([]string{"x"}))
		})

		t.Run("evaluates empty expressions to true", func(t *testing.T) {
			expr, err := Parse("")
			require.NoError(t, err)
			require.True(t, expr.Evaluate([]string{}))
			require.True(t, expr.Evaluate([]string{"y"}))
			require.True(t, expr.Evaluate([]string{"x"}))
		})
	})
}
<commit_msg>Switch to table driven tests<commit_after>package tagexpressions

import (
	"testing"

	"github.com/stretchr/testify/require"
)

func TestParseForValidCases(t *testing.T) {
	cases := []struct {
		name     string
		given    string
		expected string
	}{
		{
			name:     "test and",
			given:    "a and b",
			expected: "( a and b )",
		},
		{
			name:     "test or",
			given:    "a or b",
			expected: "( a or b )",
		},
		{
			name:     "test unary not",
			given:    "not a",
			expected: "not ( a )",
		},
		{
			name:     "test and & or",
			given:    "( a and b ) or ( c and d )",
			expected: "( ( a and b ) or ( c and d ) )",
		},
		{
			name:     "test and, or, not",
			given:    "not a or b and not c or not d or e and f",
			expected: "( ( ( not ( a ) or ( b and not ( c ) ) ) or not ( d ) ) or ( e and f ) )",
		},
		{
			name:     "test escaping",
			given:    "not a\\(\\) or b and not c or not d or e and f",
			expected: "( ( ( not ( a\\(\\) ) or ( b and not ( c ) ) ) or not ( d ) ) or ( e and f ) )",
		},
	}

	for _, tc := range cases {
		tc := tc
		t.Run(tc.name, func(t *testing.T) {
			t.Parallel()

			actual, err := Parse(tc.given)
			require.NoError(t, err)

			actualStr := actual.ToString()
			require.Equal(t, tc.expected, actualStr)

			roundTripActual, err := Parse(actualStr)
			require.NoError(t, err)

			roundTripActualStr := roundTripActual.ToString()
			require.Equal(t, tc.expected, roundTripActualStr)
		})
	}
}

func TestParseForSyntaxErrors(t *testing.T) {
	cases := []struct {
		name     string
		given    string
		expected string
	}{
		{
			name:     "no operators",
			given:    "a b",
			expected: "Syntax error. Expected operator",
		},
		{
			name:     "missing operator in binary expression",
			given:    "@a @b or",
			expected: "Syntax error. Expected operator",
		},
		{
			name:     "missing operator in unary expression",
			given:    "@a and (@b not)",
			expected: "Syntax error. Expected operator",
		},
		{
			name:     "missing operator between operands",
			given:    "@a and (@b @c) or",
			expected: "Syntax error. Expected operator",
		},
		{
			name:     "no operands",
			given:    "or or",
			expected: "Syntax error. Expected operand",
		},
		{
			name:     "missing operand",
			given:    "@a and or",
			expected: "Syntax error. Expected operand",
		},
		{
			name:     "unmatched closing parenthesis",
			given:    "( a and b ) )",
			expected: "Syntax error. Unmatched )",
		},
		{
			name:     "unmatched opening parenthesis",
			given:    "( ( a and b )",
			expected: "Syntax error. Unmatched (",
		},
	}

	for _, tc := range cases {
		tc := tc
		t.Run(tc.name, func(t *testing.T) {
			t.Parallel()

			_, err := Parse(tc.given)
			require.Error(t, err)
			require.Equal(t, tc.expected, err.Error())
		})
	}
}

func TestParseForEvaluationErrors(t *testing.T) {
	cases := []struct {
		name         string
		given        string
		expectations func(*testing.T, Evaluatable)
	}{
		{
			name:  "evaluate not",
			given: "not   x",
			expectations: func(t *testing.T, expr Evaluatable) {
				require.False(t, expr.Evaluate([]string{"x"}))
				require.True(t, expr.Evaluate([]string{"y"}))
			},
		},
		{
			name:  "evaluate and",
			given: "x and y",
			expectations: func(t *testing.T, expr Evaluatable) {
				require.True(t, expr.Evaluate([]string{"x", "y"}))
				require.False(t, expr.Evaluate([]string{"y"}))
				require.False(t, expr.Evaluate([]string{"x"}))
			},
		},
		{
			name:  "evaluate or",
			given: "  x or(y) ",
			expectations: func(t *testing.T, expr Evaluatable) {
				require.False(t, expr.Evaluate([]string{}))
				require.True(t, expr.Evaluate([]string{"y"}))
				require.True(t, expr.Evaluate([]string{"x"}))
			},
		},
		{
			name:  "evaluate expressions with escaped chars",
			given: "  x\\(1\\) or(y\\(2\\)) ",
			expectations: func(t *testing.T, expr Evaluatable) {
				require.False(t, expr.Evaluate([]string{}))
				require.True(t, expr.Evaluate([]string{"y(2)"}))
				require.True(t, expr.Evaluate([]string{"x(1)"}))
				require.False(t, expr.Evaluate([]string{"y"}))
				require.False(t, expr.Evaluate([]string{"x"}))
			},
		},
		{
			name:  "evaluate empty expressions to true",
			given: "",
			expectations: func(t *testing.T, expr Evaluatable) {
				require.True(t, expr.Evaluate([]string{}))
				require.True(t, expr.Evaluate([]string{"y"}))
				require.True(t, expr.Evaluate([]string{"x"}))
			},
		},
	}

	for _, tc := range cases {
		tc := tc
		t.Run(tc.name, func(t *testing.T) {
			t.Parallel()

			expr, err := Parse(tc.given)
			require.NoError(t, err)
			tc.expectations(t, expr)
		})
	}
}
<|endoftext|>


<commit_before>package uishow

import (
	"github.com/urfave/cli"
	ui "github.com/gizak/termui" // <- ui shortcut, optional
	log "github.com/wupeaking/logrus"
	kafka "github.com/wupeaking/sarama"
	"errors"
	"strings"
	"fmt"
)

func renderInit() {
	err := ui.Init()
	if err != nil {
		panic(err)
	}

	// handle key q pressing
	ui.Handle("/sys/kbd/q", func(ui.Event) {
		// press q to quit
		ui.StopLoop()
	})

	ui.Handle("/sys/kbd/C-x", func(ui.Event) {
		// handle Ctrl + x combination
	})

	ui.Handle("/sys/kbd", func(ui.Event) {
	    // handle all other key pressing
		ui.StopLoop()
	})

}

//此块区域的左上角和有下角坐标
func renderPar(tx, ty, bx, by int) (int, int, int, int){
	//p := ui.NewPar("有边框文本")
	//p.Height = 3
	//p.Width = 50
	//p.TextFgColor = ui.ColorWhite
	////
	//p.BorderLabel = "介绍"
	//p.BorderFg = ui.ColorCyan

	// 再创建一个无边框
	p2 := ui.NewPar("     kafkainfo 是一个简单的调试工具（按任意键退出）")
	p2.Border = true
	p2.X = tx
	p2.Y = by+1
	p2.Height = 3
	p2.TextFgColor = ui.ColorCyan
	p2.Width = 60

	ui.Render(p2) // feel free to call Render, it's async and non-block

	return p2.X, p2.Y, p2.X+p2.Width, p2.Y+p2.Height

}

// 渲染所有的broker 返回 此块区域的左上角和有下角坐标
func renderBrokerInfo(brokerInfo map[string]string, tx, ty, bx, by int) (int, int, int, int){
	addrs := make([]string, 0)
	ids := make([]string, 0)

	for addr, id := range brokerInfo {
		addrs = append(addrs, addr)
		ids = append(ids, id)
	}

	addrList := ui.NewList()
	addrList.Items = addrs
	addrList.BorderFg = ui.ColorYellow
	addrList.ItemFgColor = ui.ColorCyan
	addrList.BorderLabel = "kafka broker addr list"
	addrList.Height = len(addrs) + 3
	addrList.Width = 50
	addrList.Y = by + 1
	addrList.X = tx

	idList := ui.NewList()
	idList.Items = ids
	idList.BorderFg = ui.ColorYellow
	idList.ItemFgColor = ui.ColorCyan
	idList.BorderLabel = "kafka broker id list"
	idList.Height = len(addrs) + 3
	idList.Width = 50
	idList.Y = addrList.Y
	idList.X = addrList.X + addrList.Width

	ui.Render(addrList, idList)

	return addrList.X, addrList.Y, addrList.X+addrList.Width, addrList.Y+addrList.Height
}

// 渲染topic信息
// topic名称 | 分区数量 每个分区的leader
func renderTopicInfo(topicinfo map[string]int, partsLeader map[string][]string, tx, ty, bx, by int) (int, int, int, int) {

	infos := make([]string, 0)
	infos = append(infos, "主题 | 分区数量 | 分区leader地址")
	for topic, num := range topicinfo {
		info := fmt.Sprintf("%s | %d | %s", topic, num, strings.Join(partsLeader[topic], " "))
		infos = append(infos, info)
	}
	infoList := ui.NewList()
	infoList.Items = infos
	infoList.BorderFg = ui.ColorYellow
	infoList.ItemFgColor = ui.ColorCyan
	infoList.BorderLabel = "kafka broker topics info list"
	infoList.Height = len(infos) + 3
	infoList.Width = 100
	infoList.Y = by + 1
	infoList.X = tx

	ui.Render(infoList)
	return infoList.X, infoList.Y, infoList.X+infoList.Width, infoList.Y+infoList.Height
}

func renderList() {

	strs := []string{
		"[0] github.com/gizak/termui",
		"[1] [你好，世界](fg-blue)",
		"[2] [こんにちは世界](fg-red)",
		"[3] [color output](fg-white,bg-green)",
		"[4] output.go",
		"[5] random_out.go",
		"[6] dashboard.go",
		"[7] nsf/termbox-go"}
	list := ui.NewList()
	list.Items = strs
	list.BorderFg = ui.ColorYellow
	list.ItemFgColor = ui.ColorCyan
	list.BorderLabel = "列表示例"
	list.Height = 8
	list.Width = 50
	list.Y = 5
	ui.Render(list)
	ui.Handle("/sys/kdb/l", func(ui.Event) {
		// 当按下l键时  更新一些参数 重新渲染
		list.Items[3] = "[3] [l键被按下](fg-white,bg-green)"
		ui.Render(list)
	})
}

func renderLoop() {
	ui.Loop()
}

func getAllTopics(kafkaCli kafka.Client) []string {
	topics, err := kafkaCli.Topics()

	if err != nil {
		return nil
	}else{
		return topics
	}

	brokers := kafkaCli.Brokers()

	for i, broker := range brokers {
		log.WithField("index:", i).Info("id: ", broker.ID, " addr: ", broker.Addr())
	}
	return nil
}

// 获取所有的topic对应的分区数量
func getTopicsInfo(kafkaCli kafka.Client, topics []string) map[string]int {
	info := make(map[string]int)
	for _, topic := range topics {
		parts, err := kafkaCli.Partitions(topic)
		if err != nil {
			info[topic] = 0
		}else{
			info[topic] = len(parts)
		}
	}
	return info
}

// 获取每个topic的每个分区的leader
func getPartsLeader(kafkaCli kafka.Client, topicInfo map[string]int) map[string][]string {
	partLeader := make(map[string][]string)
	for topic, parts := range topicInfo {
		partLeader[topic] = make([]string, 0)
		p := 0
		for p < parts{
			broker, e := kafkaCli.Leader(topic, int32(p))
			if e != nil {
				partLeader[topic] = append(partLeader[topic], "")
			}else{
				partLeader[topic] = append(partLeader[topic], broker.Addr())
			}
			p += 1
			if p > 3{
				// 只显示前三个
				break
			}
		}
	}
	return partLeader
}

// 获取所有的broker key: addr value id
func getAllBrokerINfo(kafkaCli kafka.Client) map[string]string {
	brkInfo := make(map[string]string, 0)
	brokers := kafkaCli.Brokers()
	for _, b := range brokers {
		brkInfo[b.Addr()] = fmt.Sprintf("%d", b.ID())
	}
	return brkInfo
}

func UIshowCommand(c *cli.Context) error{

	addr := c.String("addr")
	if addr == "" {
		log.Error(`必须传递kafka地址 使用"kakfainfo uishow -h" 查看帮助信息`)
		return errors.New("kafka地址为空")
	}
	// 创建一个kafka客户端
	kafkaCli, err := kafka.NewClient(strings.Split(addr, ","), nil)
	if err != nil {
		log.Error("连接kafka失败: ", err)
		return err
	}

	// 获取所有topic
	topics := getAllTopics(kafkaCli)
	log.Info(topics)

	// 获取所有topic对应的分区数量
	topicsinfos := getTopicsInfo(kafkaCli, topics)
	log.Info(topicsinfos)
	// 获取每个topic的每个分区的leader
	partsLeader := getPartsLeader(kafkaCli, topicsinfos)
	log.Info(partsLeader)
	// 获取所有的broker
	brokerInfo := getAllBrokerINfo(kafkaCli)
	log.Info(brokerInfo)
	//println(topicsinfos, partsLeader, brokerInfo)

	defer kafkaCli.Close()
	//// 初始化渲染
	renderInit()
	//
	//// 渲染一个标题
	tx, ty, bx, by := renderPar(0, 0, 0, 0)
	//
	//// 渲染broker信息
	tx, ty, bx, by =renderBrokerInfo(brokerInfo, tx, ty, bx, by)
	////  渲染主题详情
	renderTopicInfo(topicsinfos, partsLeader, tx, ty, bx, by)
	renderLoop()

	return nil
}
<commit_msg>增加动态显示效果<commit_after>package uishow

import (
	"github.com/urfave/cli"
	ui "github.com/gizak/termui" // <- ui shortcut, optional
	log "github.com/wupeaking/logrus"
	kafka "github.com/wupeaking/sarama"
	"errors"
	"strings"
	"fmt"
	"time"
)

var renderCallBacks = make([]func()error, 0)

func registerRenderCallBacks(cb func()error) {
	renderCallBacks = append(renderCallBacks, cb)
}

func renderInit() {
	err := ui.Init()
	if err != nil {
		panic(err)
	}

	// handle key q pressing
	ui.Handle("/sys/kbd/q", func(ui.Event) {
		// press q to quit
		ui.StopLoop()
	})

	ui.Handle("/sys/kbd/C-x", func(ui.Event) {
		// handle Ctrl + x combination
	})

	ui.Handle("/sys/kbd", func(ui.Event) {
	    // handle all other key pressing
		ui.StopLoop()
		ui.Close()
	})
	ui.Handle("/timer/1s", func(e ui.Event) {
		for _, cb := range renderCallBacks{
			cb()
		}
	})
}

//此块区域的左上角和有下角坐标
func renderPar(tx, ty, bx, by int) (int, int, int, int){
	//p := ui.NewPar("有边框文本")
	//p.Height = 3
	//p.Width = 50
	//p.TextFgColor = ui.ColorWhite
	////
	//p.BorderLabel = "介绍"
	//p.BorderFg = ui.ColorCyan

	// 再创建一个无边框
	p2 := ui.NewPar("     kafkainfo 是一个简单的调试工具（按任意键退出）")
	p2.Border = false
	p2.X = tx
	p2.Y = by+1
	p2.Height = 3
	p2.TextFgColor = ui.ColorCyan
	p2.Width = 60

	ui.Render(p2) // feel free to call Render, it's async and non-block

	registerRenderCallBacks(func() error{
		p2.Border = !p2.Border
		p2.BorderLabel = time.Now().String()
		ui.Render(p2)
		return nil
	})

	return p2.X, p2.Y, p2.X+p2.Width, p2.Y+p2.Height

}

// 渲染所有的broker 返回 此块区域的左上角和有下角坐标
func renderBrokerInfo(brokerInfo map[string]string, tx, ty, bx, by int) (int, int, int, int){
	addrs := make([]string, 0)
	ids := make([]string, 0)

	for addr, id := range brokerInfo {
		addrs = append(addrs, addr)
		ids = append(ids, id)
	}

	addrList := ui.NewList()
	addrList.Items = addrs
	addrList.BorderFg = ui.ColorYellow
	addrList.ItemFgColor = ui.ColorCyan
	addrList.BorderLabel = "kafka broker addr list"
	addrList.Height = len(addrs) + 3
	addrList.Width = 50
	addrList.Y = by + 1
	addrList.X = tx

	idList := ui.NewList()
	idList.Items = ids
	idList.BorderFg = ui.ColorYellow
	idList.ItemFgColor = ui.ColorCyan
	idList.BorderLabel = "kafka broker id list"
	idList.Height = len(addrs) + 3
	idList.Width = 50
	idList.Y = addrList.Y
	idList.X = addrList.X + addrList.Width

	ui.Render(addrList, idList)

	return addrList.X, addrList.Y, addrList.X+addrList.Width, addrList.Y+addrList.Height
}

// 渲染topic信息
// topic名称 | 分区数量 每个分区的leader
func renderTopicInfo(topicinfo map[string]int, partsLeader map[string][]string, tx, ty, bx, by int) (int, int, int, int) {

	infos := make([]string, 0)
	infos = append(infos, "主题 | 分区数量 | 分区leader地址")
	for topic, num := range topicinfo {
		info := fmt.Sprintf("%s | %d | %s", topic, num, strings.Join(partsLeader[topic], " "))
		infos = append(infos, info)
	}
	infoList := ui.NewList()
	infoList.Items = infos
	infoList.BorderFg = ui.ColorYellow
	infoList.ItemFgColor = ui.ColorCyan
	infoList.BorderLabel = "kafka broker topics info list"
	infoList.Height = len(infos) + 3
	infoList.Width = 100
	infoList.Y = by + 1
	infoList.X = tx

	ui.Render(infoList)
	return infoList.X, infoList.Y, infoList.X+infoList.Width, infoList.Y+infoList.Height
}

func renderList() {

	strs := []string{
		"[0] github.com/gizak/termui",
		"[1] [你好，世界](fg-blue)",
		"[2] [こんにちは世界](fg-red)",
		"[3] [color output](fg-white,bg-green)",
		"[4] output.go",
		"[5] random_out.go",
		"[6] dashboard.go",
		"[7] nsf/termbox-go"}
	list := ui.NewList()
	list.Items = strs
	list.BorderFg = ui.ColorYellow
	list.ItemFgColor = ui.ColorCyan
	list.BorderLabel = "列表示例"
	list.Height = 8
	list.Width = 50
	list.Y = 5
	ui.Render(list)
	ui.Handle("/sys/kdb/l", func(ui.Event) {
		// 当按下l键时  更新一些参数 重新渲染
		list.Items[3] = "[3] [l键被按下](fg-white,bg-green)"
		ui.Render(list)
	})
}

func renderLoop() {
	ui.Loop()
}

func getAllTopics(kafkaCli kafka.Client) []string {
	topics, err := kafkaCli.Topics()

	if err != nil {
		return nil
	}else{
		return topics
	}
	//brokers := kafkaCli.Brokers()
	//
	//for i, broker := range brokers {
	//	log.WithField("index:", i).Info("id: ", broker.ID, " addr: ", broker.Addr())
	//}
	//return nil
}

// 获取所有的topic对应的分区数量
func getTopicsInfo(kafkaCli kafka.Client, topics []string) map[string]int {
	info := make(map[string]int)
	for _, topic := range topics {
		parts, err := kafkaCli.Partitions(topic)
		if err != nil {
			info[topic] = 0
		}else{
			info[topic] = len(parts)
		}
	}
	return info
}

// 获取每个topic的每个分区的leader
func getPartsLeader(kafkaCli kafka.Client, topicInfo map[string]int) map[string][]string {
	partLeader := make(map[string][]string)
	for topic, parts := range topicInfo {
		partLeader[topic] = make([]string, 0)
		p := 0
		for p < parts{
			broker, e := kafkaCli.Leader(topic, int32(p))
			if e != nil {
				partLeader[topic] = append(partLeader[topic], "")
			}else{
				partLeader[topic] = append(partLeader[topic], broker.Addr())
			}
			p += 1
			if p > 3{
				// 只显示前三个
				break
			}
		}
	}
	return partLeader
}

// 获取所有的broker key: addr value id
func getAllBrokerINfo(kafkaCli kafka.Client) map[string]string {
	brkInfo := make(map[string]string, 0)
	brokers := kafkaCli.Brokers()
	for _, b := range brokers {
		brkInfo[b.Addr()] = fmt.Sprintf("%d", b.ID())
	}
	return brkInfo
}

func UIshowCommand(c *cli.Context) error{

	addr := c.String("addr")
	if addr == "" {
		log.Error(`必须传递kafka地址 使用"kakfainfo uishow -h" 查看帮助信息`)
		return errors.New("kafka地址为空")
	}
	// 创建一个kafka客户端
	kafkaCli, err := kafka.NewClient(strings.Split(addr, ","), nil)
	if err != nil {
		log.Error("连接kafka失败: ", err)
		return err
	}

	// 获取所有topic
	topics := getAllTopics(kafkaCli)
	// 获取所有topic对应的分区数量
	topicsinfos := getTopicsInfo(kafkaCli, topics)
	// 获取每个topic的每个分区的leader
	partsLeader := getPartsLeader(kafkaCli, topicsinfos)
	// 获取所有的broker
	brokerInfo := getAllBrokerINfo(kafkaCli)
	defer kafkaCli.Close()
	//// 初始化渲染
	renderInit()

	//// 渲染一个标题
	tx, ty, bx, by := renderPar(0, 0, 0, 0)

	//// 渲染broker信息
	tx, ty, bx, by =renderBrokerInfo(brokerInfo, tx, ty, bx, by)
	////  渲染主题详情
	renderTopicInfo(topicsinfos, partsLeader, tx, ty, bx, by)
	renderLoop()

	return nil
}
<|endoftext|>