<commit_before>// +build darwin,cgo

package gogui

import (
	"C"
	"unsafe"
)

const (
	keyEventDown = iota
	keyEventUp   = iota
)

const (
	keyFlagAlt   = 1
	keyFlagCtrl  = 2
	keyFlagMeta  = 4
	keyFlagShift = 8
)

const (
	mouseEventDown = iota
	mouseEventDrag = iota
	mouseEventMove = iota
	mouseEventUp   = iota
)

func findWindow(ptr unsafe.Pointer) *window {
	for _, w := range showingWindows {
		wptr := w.(*window)
		if wptr.pointer == ptr {
			return wptr
		}
	}
	return nil
}

//export windowClosed
func windowClosed(ptr unsafe.Pointer) {
	for i, w := range showingWindows {
		wptr := w.(*window)
		if wptr.pointer == ptr {
			// Remove the window from the list and set it not showing.
			wptr.showing = false
			showingWindows[i] = showingWindows[len(showingWindows)-1]
			showingWindows[len(showingWindows)-1] = nil
			showingWindows = showingWindows[0 : len(showingWindows)-1]

			// Call the close handler
			if h := w.CloseHandler(); h != nil {
				h()
			}
		}
	}
}

//export windowKeyFlagsChanged
func windowKeyFlagsChanged(ptr unsafe.Pointer, flags int) {
	// TODO: this
}

//export windowKeyEvent
func windowKeyEvent(ptr unsafe.Pointer, eventType int, chars, modChars *C.char,
	keyCode, mods int) {
	// TODO: this
}

//export windowMouseEvent
func windowMouseEvent(ptr unsafe.Pointer, eventType int, x, y C.double) {
	w := findWindow(ptr)
	if w == nil {
		return
	}

	// Get the handler.
	var handler MouseHandler
	switch eventType {
	case mouseEventDown:
		handler = w.MouseDownHandler()
	case mouseEventDrag:
		handler = w.MouseDragHandler()
	case mouseEventMove:
		handler = w.MouseMoveHandler()
	case mouseEventUp:
		handler = w.MouseUpHandler()
	default:
		panic("Unknown mouse event.")
	}

	// Call the handler if there is one.
	if handler == nil {
		return
	}
	handler(MouseEvent{float64(x), float64(y)})
}
<commit_msg>key events are up to speed<commit_after>// +build darwin,cgo

package gogui

import (
	"C"
	"strings"
	"unsafe"
)

const (
	keyEventDown = iota
	keyEventUp   = iota
)

const (
	keyFlagAlt   = 1
	keyFlagCtrl  = 2
	keyFlagMeta  = 4
	keyFlagShift = 8
)

const (
	mouseEventDown = iota
	mouseEventDrag = iota
	mouseEventMove = iota
	mouseEventUp   = iota
)

func callKeyDown(w *window, evt KeyEvent) {
	if h := w.KeyDownHandler(); h != nil {
		h(evt)
	}
}

func callKeyPress(w *window, evt KeyEvent) {
	if h := w.KeyPressHandler(); h != nil {
		h(evt)
	}
}

func callKeyUp(w *window, evt KeyEvent) {
	if h := w.KeyUpHandler(); h != nil {
		h(evt)
	}
}

func findWindow(ptr unsafe.Pointer) *window {
	for _, w := range showingWindows {
		wptr := w.(*window)
		if wptr.pointer == ptr {
			return wptr
		}
	}
	return nil
}

func makeKeyEvent(keyCode, charCode int, flags int) KeyEvent {
	res := KeyEvent{KeyCode: keyCode, CharCode: charCode}
	if (flags & keyFlagAlt) != 0 {
		res.AltKey = true
	}
	if (flags & keyFlagCtrl) != 0 {
		res.CtrlKey = true
	}
	if (flags & keyFlagMeta) != 0 {
		res.MetaKey = true
	}
	if (flags & keyFlagShift) != 0 {
		res.ShiftKey = true
	}
	return res
}

//export windowClosed
func windowClosed(ptr unsafe.Pointer) {
	for i, w := range showingWindows {
		wptr := w.(*window)
		if wptr.pointer == ptr {
			// Remove the window from the list and set it not showing.
			wptr.showing = false
			showingWindows[i] = showingWindows[len(showingWindows)-1]
			showingWindows[len(showingWindows)-1] = nil
			showingWindows = showingWindows[0 : len(showingWindows)-1]

			// Call the close handler
			if h := w.CloseHandler(); h != nil {
				h()
			}
		}
	}
}

//export windowKeyFlagsChanged
func windowKeyFlagsChanged(ptr unsafe.Pointer, flags int) {
	w := findWindow(ptr)
	if w == nil {
		return
	}
	difference := flags ^ w.modifiers
	w.modifiers = flags
	if (difference & keyFlagAlt) != 0 {
		evt := makeKeyEvent(-1, 18, flags)
		if (flags & keyFlagAlt) != 0 {
			callKeyDown(w, evt)
		} else {
			callKeyUp(w, evt)
		}
	}
	if (difference & keyFlagCtrl) != 0 {
		evt := makeKeyEvent(-1, 17, flags)
		if (flags & keyFlagCtrl) != 0 {
			callKeyDown(w, evt)
		} else {
			callKeyUp(w, evt)
		}
	}
	if (difference & keyFlagMeta) != 0 {
		evt := makeKeyEvent(-1, 91, flags)
		if (flags & keyFlagMeta) != 0 {
			callKeyDown(w, evt)
		} else {
			callKeyUp(w, evt)
		}
	}
	if (difference & keyFlagShift) != 0 {
		evt := makeKeyEvent(-1, 16, flags)
		if (flags & keyFlagShift) != 0 {
			callKeyDown(w, evt)
		} else {
			callKeyUp(w, evt)
		}
	}
}

//export windowKeyEvent
func windowKeyEvent(ptr unsafe.Pointer, eventType int, chars, modChars *C.char,
	keyCode, flags int) {
	w := findWindow(ptr)
	if w == nil {
		return
	}

	// Normally, the character code is just the ASCII character of the key.
	rawCodeStr := C.GoString(chars)
	rawCodeStr = strings.ToUpper(rawCodeStr)
	rawCode := int([]rune(rawCodeStr)[0])
	modCode := int([]rune(C.GoString(modChars))[0])

	// Certain keys have weird character codes that Cocoa doesn't report nicely.
	mapping := map[int]int{51: 8, 123: 37, 126: 38, 124: 39, 125: 40, 117: 46}
	if mapped, ok := mapping[keyCode]; ok {
		rawCode = mapped
		modCode = mapped
	}

	if eventType == keyEventDown {
		// Call down and press events.
		evt := makeKeyEvent(keyCode, rawCode, flags)
		callKeyDown(w, evt)
		evt.CharCode = modCode
		callKeyPress(w, evt)
	} else {
		// Call key up event.
		evt := makeKeyEvent(keyCode, rawCode, flags)
		callKeyUp(w, evt)
	}
}

//export windowMouseEvent
func windowMouseEvent(ptr unsafe.Pointer, eventType int, x, y C.double) {
	w := findWindow(ptr)
	if w == nil {
		return
	}

	// Get the handler.
	var handler MouseHandler
	switch eventType {
	case mouseEventDown:
		handler = w.MouseDownHandler()
	case mouseEventDrag:
		handler = w.MouseDragHandler()
	case mouseEventMove:
		handler = w.MouseMoveHandler()
	case mouseEventUp:
		handler = w.MouseUpHandler()
	default:
		panic("Unknown mouse event.")
	}

	// Call the handler if there is one.
	if handler == nil {
		return
	}
	handler(MouseEvent{float64(x), float64(y)})
}
<|endoftext|>


<commit_before>/*
 * MinIO Client (C) 2018 MinIO, Inc.
 *
 * 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 cmd

import (
	"strings"

	"github.com/minio/cli"
	"github.com/minio/mc/pkg/probe"
	"github.com/minio/minio-go/v7/pkg/set"
	"github.com/minio/minio/pkg/console"
	"github.com/minio/minio/pkg/madmin"
)

var adminProfileStartFlags = []cli.Flag{
	cli.StringFlag{
		Name:  "type",
		Usage: "start profiler type, possible values are 'cpu', 'mem', 'block', 'mutex', 'trace', 'threads' and 'goroutines'",
		Value: "cpu,mem,block",
	},
}

var adminProfileStartCmd = cli.Command{
	Name:            "start",
	Usage:           "start recording profile data",
	Action:          mainAdminProfileStart,
	OnUsageError:    onUsageError,
	Before:          setGlobalsFromContext,
	Flags:           append(adminProfileStartFlags, globalFlags...),
	HideHelpCommand: true,
	CustomHelpTemplate: `NAME:
  {{.HelpName}} - {{.Usage}}

USAGE:
  {{.HelpName}} [FLAGS] TARGET

FLAGS:
  {{range .VisibleFlags}}{{.}}
  {{end}}
EXAMPLES:
    1. Start CPU profiling only
       {{.Prompt}} {{.HelpName}} --type cpu myminio/

    2. Start CPU, Memory and Block profiling concurrently
       {{.Prompt}} {{.HelpName}} --type cpu,mem,block myminio/
`,
}

func checkAdminProfileStartSyntax(ctx *cli.Context) {
	// Check flags combinations
	if len(ctx.Args()) != 1 {
		cli.ShowCommandHelpAndExit(ctx, "start", 1) // last argument is exit code
	}

	s := set.NewStringSet()
	supportedProfilerTypes := []madmin.ProfilerType{
		madmin.ProfilerCPU,
		madmin.ProfilerMEM,
		madmin.ProfilerBlock,
		madmin.ProfilerMutex,
		madmin.ProfilerTrace,
		madmin.ProfilerThreads,
		madmin.ProfilerGoroutines,
	}
	for _, profilerType := range supportedProfilerTypes {
		s.Add(string(profilerType))
	}
	// Check if the provided profiler type is known and supported
	supportedProfiler := false
	profilers := strings.Split(strings.ToLower(ctx.String("type")), ",")
	for _, profiler := range profilers {
		if profiler != "" {
			if s.Contains(profiler) {
				supportedProfiler = true
				break
			}
		}
	}
	if !supportedProfiler {
		fatalIf(errDummy().Trace(ctx.String("type")),
			"Profiler type unrecognized. Possible values are: %v.", supportedProfilerTypes)
	}
}

// mainAdminProfileStart - the entry function of profile command
func mainAdminProfileStart(ctx *cli.Context) error {
	// Check for command syntax
	checkAdminProfileStartSyntax(ctx)

	// Get the alias parameter from cli
	args := ctx.Args()
	aliasedURL := args.Get(0)

	profilers := ctx.String("type")

	// Create a new MinIO Admin Client
	client, err := newAdminClient(aliasedURL)
	if err != nil {
		fatalIf(err.Trace(aliasedURL), "Unable to initialize admin client.")
		return nil
	}

	// Start profile
	_, cmdErr := client.StartProfiling(globalContext, madmin.ProfilerType(profilers))
	fatalIf(probe.NewError(cmdErr), "Unable to start profile.")

	console.Infoln("Profile data successfully started.")
	return nil
}
<commit_msg>trace: Add goroutines by default (#3664)<commit_after>/*
 * MinIO Client (C) 2018 MinIO, Inc.
 *
 * 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 cmd

import (
	"strings"

	"github.com/minio/cli"
	"github.com/minio/mc/pkg/probe"
	"github.com/minio/minio-go/v7/pkg/set"
	"github.com/minio/minio/pkg/console"
	"github.com/minio/minio/pkg/madmin"
)

var adminProfileStartFlags = []cli.Flag{
	cli.StringFlag{
		Name:  "type",
		Usage: "start profiler type, possible values are 'cpu', 'mem', 'block', 'mutex', 'trace', 'threads' and 'goroutines'",
		Value: "cpu,mem,block,goroutines",
	},
}

var adminProfileStartCmd = cli.Command{
	Name:            "start",
	Usage:           "start recording profile data",
	Action:          mainAdminProfileStart,
	OnUsageError:    onUsageError,
	Before:          setGlobalsFromContext,
	Flags:           append(adminProfileStartFlags, globalFlags...),
	HideHelpCommand: true,
	CustomHelpTemplate: `NAME:
  {{.HelpName}} - {{.Usage}}

USAGE:
  {{.HelpName}} [FLAGS] TARGET

FLAGS:
  {{range .VisibleFlags}}{{.}}
  {{end}}
EXAMPLES:
    1. Start CPU profiling only
       {{.Prompt}} {{.HelpName}} --type cpu myminio/

    2. Start CPU, Memory and Block profiling concurrently
       {{.Prompt}} {{.HelpName}} --type cpu,mem,block myminio/
`,
}

func checkAdminProfileStartSyntax(ctx *cli.Context) {
	// Check flags combinations
	if len(ctx.Args()) != 1 {
		cli.ShowCommandHelpAndExit(ctx, "start", 1) // last argument is exit code
	}

	s := set.NewStringSet()
	supportedProfilerTypes := []madmin.ProfilerType{
		madmin.ProfilerCPU,
		madmin.ProfilerMEM,
		madmin.ProfilerBlock,
		madmin.ProfilerMutex,
		madmin.ProfilerTrace,
		madmin.ProfilerThreads,
		madmin.ProfilerGoroutines,
	}
	for _, profilerType := range supportedProfilerTypes {
		s.Add(string(profilerType))
	}
	// Check if the provided profiler type is known and supported
	supportedProfiler := false
	profilers := strings.Split(strings.ToLower(ctx.String("type")), ",")
	for _, profiler := range profilers {
		if profiler != "" {
			if s.Contains(profiler) {
				supportedProfiler = true
				break
			}
		}
	}
	if !supportedProfiler {
		fatalIf(errDummy().Trace(ctx.String("type")),
			"Profiler type unrecognized. Possible values are: %v.", supportedProfilerTypes)
	}
}

// mainAdminProfileStart - the entry function of profile command
func mainAdminProfileStart(ctx *cli.Context) error {
	// Check for command syntax
	checkAdminProfileStartSyntax(ctx)

	// Get the alias parameter from cli
	args := ctx.Args()
	aliasedURL := args.Get(0)

	profilers := ctx.String("type")

	// Create a new MinIO Admin Client
	client, err := newAdminClient(aliasedURL)
	if err != nil {
		fatalIf(err.Trace(aliasedURL), "Unable to initialize admin client.")
		return nil
	}

	// Start profile
	_, cmdErr := client.StartProfiling(globalContext, madmin.ProfilerType(profilers))
	fatalIf(probe.NewError(cmdErr), "Unable to start profile.")

	console.Infoln("Profile data successfully started.")
	return nil
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net"
	"net/http"
	"os"
	"os/exec"
	"strings"
	"time"

	"github.com/datawire/ambassador/pkg/k8s"
	"github.com/pkg/errors"
	"github.com/spf13/cobra"
)

func aesInstallCmd() *cobra.Command {
	res := &cobra.Command{
		Use:   "install",
		Short: "Install the Ambassador Edge Stack in your cluster",
		Args:  cobra.ExactArgs(0),
		RunE:  aesInstall,
	}
	_ = res.Flags().StringP(
		"context", "c", "",
		"The Kubernetes context to use. Defaults to the current kubectl context.",
	)
	_ = res.Flags().StringP(
		"namespace", "n", "",
		"The Kubernetes namespace to use. Defaults to kubectl's default for the context.",
	)

	return res
}

func aesInstall(cmd *cobra.Command, args []string) error {
	metrics := NewMetrics()
	_ = metrics.Report("install")

	// Display version information
	// TODO: This displays the version of Edge Control, not the image version in
	// the manifests being downloaded. We should figure out what to do if those
	// don't match up.
	// 1. Allow an old Edge Control to install a newer AES
	// 2. Insist that Edge Control be the same version as the AES it's installing
	// 3. Something else?
	// Note that the second option will allow us to make the install process
	// more complicated in the future without having to subject our users to
	// increasing complexity, assuming this approach to installation gains
	// traction.
	fmt.Printf("-> Installing the Ambassador Edge Stack %s\n", Version)

	// Attempt to talk to the specified cluster
	context, _ := cmd.Flags().GetString("context")
	namespace, _ := cmd.Flags().GetString("namespace")
	kubeinfo := k8s.NewKubeInfo("", context, namespace)
	i := &Installer{
		kubeinfo,
	}
	if err := i.ShowKubectl("cluster-info", "cluster-info"); err != nil {
		return err
	}

	if err := i.ShowKubectl("install CRDs", "apply", "-f", "https://www.getambassador.io/yaml/aes-crds.yaml"); err != nil {
		return err
	}

	if err := i.ShowKubectl("wait for CRDs", "wait", "--for", "condition=established", "--timeout=90s", "crd", "-lproduct=aes"); err != nil {
		return err
	}

	if err := i.ShowKubectl("install AES", "apply", "-f", "https://www.getambassador.io/yaml/aes.yaml"); err != nil {
		return err
	}

	if err := i.ShowKubectl("wait for AES", "-n", "ambassador", "wait", "--for", "condition=available", "--timeout=90s", "deploy", "-lproduct=aes"); err != nil {
		return err
	}

	_ = metrics.Report("deploy") // TODO: Send cluster type and Helm version

	ipAddress := ""
	for {
		var err error
		ipAddress, err = i.CaptureKubectl("get IP address", "get", "-n", "ambassador", "service", "ambassador", "-o", `go-template={{range .status.loadBalancer.ingress}}{{print .ip "\n"}}{{end}}`)
		if err != nil {
			return err
		}
		ipAddress = strings.TrimSpace(ipAddress)
		if ipAddress != "" {
			break
		}
		time.Sleep(250 * time.Millisecond) // FIXME: Time out at some point...
	}

	fmt.Println("\nYour IP address is", ipAddress)

	// Wait for Ambassador to be ready to serve ACME requests.
	// FIXME: This assumes we can connect to the load balancer. If this
	// assumption is incorrect, this code will loop forever.
	for {
		// FIXME: Time out at some point...
		time.Sleep(500 * time.Millisecond)

		// Verify that we can connect to something
		resp, err := http.Get("http://" + ipAddress + "/.well-known/acme-challenge/")
		if err != nil {
			fmt.Printf("Waiting for Ambassador (get): %#v\n", err)
			continue
		}
		_, _ = ioutil.ReadAll(resp.Body)
		resp.Body.Close()

		// Verify that we get the expected status code. If Ambassador is still
		// starting up, then Envoy may return "upstream request timeout" (503),
		// in which case we should keep looping.
		if resp.StatusCode != 404 {
			fmt.Printf("Waiting for Ambassador: wrong status code: %d\n", resp.StatusCode)
			continue
		}

		// Sanity check that we're talking to Envoy. This is probably unnecessary.
		if resp.Header.Get("server") != "envoy" {
			fmt.Printf("Waiting for Ambassador: wrong server header: %s\n", resp.Header.Get("server"))
			continue
		}
		break
	}

	// Send a request to acquire a DNS name for this cluster's IP address
	regURL := "https://metriton.datawire.io/beta/register-domain"
	emailAddress := "ark3+eci@datawire.io"
	buf := new(bytes.Buffer)
	_ = json.NewEncoder(buf).Encode(registration{emailAddress, ipAddress})
	resp, err := http.Post(regURL, "application/json", buf)
	if err != nil {
		return errors.Wrap(err, "acquire DNS name (post)")
	}
	content, err := ioutil.ReadAll(resp.Body)
	resp.Body.Close()
	if err != nil {
		return errors.Wrap(err, "acquire DNS name (read body)")
	}

	if resp.StatusCode == 200 {
		hostname := string(content)
		fmt.Println("-> Acquiring DNS name", hostname)

		// Wait for DNS to propagate. This tries to avoid waiting for a ten
		// minute error backoff if the ACME registration races ahead of the DNS
		// name appearing for LetsEncrypt.
		for {
			conn, err := net.Dial("tcp", hostname+":443")
			if err == nil {
				conn.Close()
				break
			}
			// fmt.Printf("Waiting for DNS: %#v\n", err)
			time.Sleep(500 * time.Millisecond)
		}
		fmt.Println("-> Automatically configuring TLS")
		fmt.Println("Please enter an email address. We'll use this email address to notify you prior to domain and certification expiration [None]:", emailAddress)
		fmt.Println("FIXME: let the user enter an address")
		// Create a Host resource
		hostResource := fmt.Sprintf(hostManifest, hostname, namespace, hostname, emailAddress)
		kargs, err := i.kubeinfo.GetKubectlArray("apply", "-f", "-")
		if err != nil {
			return errors.Wrapf(err, "cluster access for install AES")
		}
		fmt.Println("\n$ kubectl apply -f - < [Host Resource]")
		cmd := exec.Command("kubectl", kargs...)
		cmd.Stdout = os.Stdout
		cmd.Stderr = os.Stderr
		cmd.Stdin = strings.NewReader(hostResource)
		if err := cmd.Run(); err != nil {
			return errors.Wrap(err, "install AES")
		}

		fmt.Println("\n-> Obtaining a TLS certificate from Let's Encrypt")

		for {
			state, err := i.CaptureKubectl("get Host state", "get", "host", hostname, "-o", "go-template={{.status.state}}")
			if err != nil {
				return err
			}
			if state == "Ready" {
				break
			}
			time.Sleep(500 * time.Millisecond) // FIXME: Time out at some point...
			// FIXME: Do something smart for state == "Error"
		}

		_ = metrics.Report("cert_provisioned")
		fmt.Println("-> TLS configured successfully")
		if err := i.ShowKubectl("show Host", "get", "host", hostname); err != nil {
			return err
		}

		// Open a browser window to the Edge Policy Console
		if err := do_login(kubeinfo, context, "ambassador", hostname, false, false); err != nil {
			return err
		}

	} else {
		message := strings.TrimSpace(string(content))
		fmt.Println("\n-> Failed to create a DNS name:", message)
		fmt.Println()
		fmt.Println("If this IP address is reachable from here, then the following command")
		fmt.Println("will open the Edge Policy Console once you accept a self-signed")
		fmt.Println("certificate in your browser.")
		fmt.Println()
		fmt.Println("    edgectl login -n ambassador", ipAddress)
		fmt.Println()
		fmt.Println("If the IP is not reachable from here, you can use port forwarding to")
		fmt.Println("access the Edge Policy Console.")
		fmt.Println()
		fmt.Println("    kubectl -n ambassador port-forward deploy/ambassador 8443 &")
		fmt.Println("    edgectl login -n ambassador 127.0.0.1:8443")
		fmt.Println()
		fmt.Println("You will need to accept a self-signed certificate in your browser.")
		fmt.Println()
		fmt.Println("See https://www.getambassador.io/user-guide/getting-started/")
	}

	_ = metrics.Report("aes_health_good") // or aes_health_bad TODO: Send cluster's install_id and AES version

	return nil
}

type Installer struct {
	kubeinfo *k8s.KubeInfo
}

// Kubernetes Cluster

func (i *Installer) ShowKubectl(name string, args ...string) error {
	kargs, err := i.kubeinfo.GetKubectlArray(args...)
	if err != nil {
		return errors.Wrapf(err, "cluster access for %s", name)
	}
	fmt.Printf("\n$ kubectl %s\n", strings.Join(kargs, " "))
	cmd := exec.Command("kubectl", kargs...)
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	if err := cmd.Run(); err != nil {
		return errors.Wrap(err, name)
	}
	return nil
}

func (i *Installer) CaptureKubectl(name string, args ...string) (res string, err error) {
	res = ""
	kargs, err := i.kubeinfo.GetKubectlArray(args...)
	if err != nil {
		err = errors.Wrapf(err, "cluster access for %s", name)
		return
	}
	fmt.Printf("\n$ kubectl %s\n", strings.Join(kargs, " "))
	cmd := exec.Command("kubectl", kargs...)
	cmd.Stderr = nil
	resAsBytes, err := cmd.Output()
	if err != nil {
		if ee, ok := err.(*exec.ExitError); ok {
			fmt.Println(ee.Stderr)
		}
		err = errors.Wrap(err, name)
	}
	res = string(resAsBytes)
	return
}

// DNS Registration

type registration struct {
	Email string
	Ip    string
}

// Metrics

type Metrics struct {
	scout *Scout
}

func NewMetrics() *Metrics {
	scout, err := NewScout("install")
	if err != nil {
		// Don't crash if Scout stuff doesn't work
		scout = nil
	}
	return &Metrics{scout}
}

func (m *Metrics) Report(eventName string, meta ...ScoutMeta) error {
	fmt.Println("\n-> [Metrics]", eventName)
	if m.scout != nil {
		_ = m.scout.Report(eventName, meta...)
	}
	return nil
}

const hostManifest = `
apiVersion: getambassador.io/v2
kind: Host
metadata:
  name: %s
  namespace: %s
spec:
  hostname: %s
  acmeProvider:
    email: %s
`
<commit_msg>Show scout errors (but don't fail, just as before)<commit_after>package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net"
	"net/http"
	"os"
	"os/exec"
	"strings"
	"time"

	"github.com/datawire/ambassador/pkg/k8s"
	"github.com/pkg/errors"
	"github.com/spf13/cobra"
)

func aesInstallCmd() *cobra.Command {
	res := &cobra.Command{
		Use:   "install",
		Short: "Install the Ambassador Edge Stack in your cluster",
		Args:  cobra.ExactArgs(0),
		RunE:  aesInstall,
	}
	_ = res.Flags().StringP(
		"context", "c", "",
		"The Kubernetes context to use. Defaults to the current kubectl context.",
	)
	_ = res.Flags().StringP(
		"namespace", "n", "",
		"The Kubernetes namespace to use. Defaults to kubectl's default for the context.",
	)

	return res
}

func aesInstall(cmd *cobra.Command, args []string) error {
	metrics := NewMetrics()
	_ = metrics.Report("install")

	// Display version information
	// TODO: This displays the version of Edge Control, not the image version in
	// the manifests being downloaded. We should figure out what to do if those
	// don't match up.
	// 1. Allow an old Edge Control to install a newer AES
	// 2. Insist that Edge Control be the same version as the AES it's installing
	// 3. Something else?
	// Note that the second option will allow us to make the install process
	// more complicated in the future without having to subject our users to
	// increasing complexity, assuming this approach to installation gains
	// traction.
	fmt.Printf("-> Installing the Ambassador Edge Stack %s\n", Version)

	// Attempt to talk to the specified cluster
	context, _ := cmd.Flags().GetString("context")
	namespace, _ := cmd.Flags().GetString("namespace")
	kubeinfo := k8s.NewKubeInfo("", context, namespace)
	i := &Installer{
		kubeinfo,
	}
	if err := i.ShowKubectl("cluster-info", "cluster-info"); err != nil {
		return err
	}

	if err := i.ShowKubectl("install CRDs", "apply", "-f", "https://www.getambassador.io/yaml/aes-crds.yaml"); err != nil {
		return err
	}

	if err := i.ShowKubectl("wait for CRDs", "wait", "--for", "condition=established", "--timeout=90s", "crd", "-lproduct=aes"); err != nil {
		return err
	}

	if err := i.ShowKubectl("install AES", "apply", "-f", "https://www.getambassador.io/yaml/aes.yaml"); err != nil {
		return err
	}

	if err := i.ShowKubectl("wait for AES", "-n", "ambassador", "wait", "--for", "condition=available", "--timeout=90s", "deploy", "-lproduct=aes"); err != nil {
		return err
	}

	_ = metrics.Report("deploy") // TODO: Send cluster type and Helm version

	ipAddress := ""
	for {
		var err error
		ipAddress, err = i.CaptureKubectl("get IP address", "get", "-n", "ambassador", "service", "ambassador", "-o", `go-template={{range .status.loadBalancer.ingress}}{{print .ip "\n"}}{{end}}`)
		if err != nil {
			return err
		}
		ipAddress = strings.TrimSpace(ipAddress)
		if ipAddress != "" {
			break
		}
		time.Sleep(250 * time.Millisecond) // FIXME: Time out at some point...
	}

	fmt.Println("\nYour IP address is", ipAddress)

	// Wait for Ambassador to be ready to serve ACME requests.
	// FIXME: This assumes we can connect to the load balancer. If this
	// assumption is incorrect, this code will loop forever.
	for {
		// FIXME: Time out at some point...
		time.Sleep(500 * time.Millisecond)

		// Verify that we can connect to something
		resp, err := http.Get("http://" + ipAddress + "/.well-known/acme-challenge/")
		if err != nil {
			fmt.Printf("Waiting for Ambassador (get): %#v\n", err)
			continue
		}
		_, _ = ioutil.ReadAll(resp.Body)
		resp.Body.Close()

		// Verify that we get the expected status code. If Ambassador is still
		// starting up, then Envoy may return "upstream request timeout" (503),
		// in which case we should keep looping.
		if resp.StatusCode != 404 {
			fmt.Printf("Waiting for Ambassador: wrong status code: %d\n", resp.StatusCode)
			continue
		}

		// Sanity check that we're talking to Envoy. This is probably unnecessary.
		if resp.Header.Get("server") != "envoy" {
			fmt.Printf("Waiting for Ambassador: wrong server header: %s\n", resp.Header.Get("server"))
			continue
		}
		break
	}

	// Send a request to acquire a DNS name for this cluster's IP address
	regURL := "https://metriton.datawire.io/beta/register-domain"
	emailAddress := "ark3+eci@datawire.io"
	buf := new(bytes.Buffer)
	_ = json.NewEncoder(buf).Encode(registration{emailAddress, ipAddress})
	resp, err := http.Post(regURL, "application/json", buf)
	if err != nil {
		return errors.Wrap(err, "acquire DNS name (post)")
	}
	content, err := ioutil.ReadAll(resp.Body)
	resp.Body.Close()
	if err != nil {
		return errors.Wrap(err, "acquire DNS name (read body)")
	}

	if resp.StatusCode == 200 {
		hostname := string(content)
		fmt.Println("-> Acquiring DNS name", hostname)

		// Wait for DNS to propagate. This tries to avoid waiting for a ten
		// minute error backoff if the ACME registration races ahead of the DNS
		// name appearing for LetsEncrypt.
		for {
			conn, err := net.Dial("tcp", hostname+":443")
			if err == nil {
				conn.Close()
				break
			}
			// fmt.Printf("Waiting for DNS: %#v\n", err)
			time.Sleep(500 * time.Millisecond)
		}
		fmt.Println("-> Automatically configuring TLS")
		fmt.Println("Please enter an email address. We'll use this email address to notify you prior to domain and certification expiration [None]:", emailAddress)
		fmt.Println("FIXME: let the user enter an address")
		// Create a Host resource
		hostResource := fmt.Sprintf(hostManifest, hostname, namespace, hostname, emailAddress)
		kargs, err := i.kubeinfo.GetKubectlArray("apply", "-f", "-")
		if err != nil {
			return errors.Wrapf(err, "cluster access for install AES")
		}
		fmt.Println("\n$ kubectl apply -f - < [Host Resource]")
		cmd := exec.Command("kubectl", kargs...)
		cmd.Stdout = os.Stdout
		cmd.Stderr = os.Stderr
		cmd.Stdin = strings.NewReader(hostResource)
		if err := cmd.Run(); err != nil {
			return errors.Wrap(err, "install AES")
		}

		fmt.Println("\n-> Obtaining a TLS certificate from Let's Encrypt")

		for {
			state, err := i.CaptureKubectl("get Host state", "get", "host", hostname, "-o", "go-template={{.status.state}}")
			if err != nil {
				return err
			}
			if state == "Ready" {
				break
			}
			time.Sleep(500 * time.Millisecond) // FIXME: Time out at some point...
			// FIXME: Do something smart for state == "Error"
		}

		_ = metrics.Report("cert_provisioned")
		fmt.Println("-> TLS configured successfully")
		if err := i.ShowKubectl("show Host", "get", "host", hostname); err != nil {
			return err
		}

		// Open a browser window to the Edge Policy Console
		if err := do_login(kubeinfo, context, "ambassador", hostname, false, false); err != nil {
			return err
		}

	} else {
		message := strings.TrimSpace(string(content))
		fmt.Println("\n-> Failed to create a DNS name:", message)
		fmt.Println()
		fmt.Println("If this IP address is reachable from here, then the following command")
		fmt.Println("will open the Edge Policy Console once you accept a self-signed")
		fmt.Println("certificate in your browser.")
		fmt.Println()
		fmt.Println("    edgectl login -n ambassador", ipAddress)
		fmt.Println()
		fmt.Println("If the IP is not reachable from here, you can use port forwarding to")
		fmt.Println("access the Edge Policy Console.")
		fmt.Println()
		fmt.Println("    kubectl -n ambassador port-forward deploy/ambassador 8443 &")
		fmt.Println("    edgectl login -n ambassador 127.0.0.1:8443")
		fmt.Println()
		fmt.Println("You will need to accept a self-signed certificate in your browser.")
		fmt.Println()
		fmt.Println("See https://www.getambassador.io/user-guide/getting-started/")
	}

	_ = metrics.Report("aes_health_good") // or aes_health_bad TODO: Send cluster's install_id and AES version

	return nil
}

type Installer struct {
	kubeinfo *k8s.KubeInfo
}

// Kubernetes Cluster

func (i *Installer) ShowKubectl(name string, args ...string) error {
	kargs, err := i.kubeinfo.GetKubectlArray(args...)
	if err != nil {
		return errors.Wrapf(err, "cluster access for %s", name)
	}
	fmt.Printf("\n$ kubectl %s\n", strings.Join(kargs, " "))
	cmd := exec.Command("kubectl", kargs...)
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	if err := cmd.Run(); err != nil {
		return errors.Wrap(err, name)
	}
	return nil
}

func (i *Installer) CaptureKubectl(name string, args ...string) (res string, err error) {
	res = ""
	kargs, err := i.kubeinfo.GetKubectlArray(args...)
	if err != nil {
		err = errors.Wrapf(err, "cluster access for %s", name)
		return
	}
	fmt.Printf("\n$ kubectl %s\n", strings.Join(kargs, " "))
	cmd := exec.Command("kubectl", kargs...)
	cmd.Stderr = nil
	resAsBytes, err := cmd.Output()
	if err != nil {
		if ee, ok := err.(*exec.ExitError); ok {
			fmt.Println(ee.Stderr)
		}
		err = errors.Wrap(err, name)
	}
	res = string(resAsBytes)
	return
}

// DNS Registration

type registration struct {
	Email string
	Ip    string
}

// Metrics

type Metrics struct {
	scout *Scout
}

func NewMetrics() *Metrics {
	scout, err := NewScout("install")
	if err != nil {
		// Don't crash if Scout stuff doesn't work
		scout = nil
	}
	return &Metrics{scout}
}

func (m *Metrics) Report(eventName string, meta ...ScoutMeta) error {
	fmt.Println("\n-> [Metrics]", eventName)
	if m.scout != nil {
		if err := m.scout.Report(eventName, meta...); err != nil {
			fmt.Println("            ", err)
		}
	}
	return nil
}

const hostManifest = `
apiVersion: getambassador.io/v2
kind: Host
metadata:
  name: %s
  namespace: %s
spec:
  hostname: %s
  acmeProvider:
    email: %s
`
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net"
	"net/http"
	"os"
	"os/exec"
	"strings"
	"time"

	"github.com/datawire/ambassador/pkg/k8s"
	"github.com/pkg/errors"
	"github.com/spf13/cobra"
)

func aesInstallCmd() *cobra.Command {
	res := &cobra.Command{
		Use:   "install",
		Short: "Install the Ambassador Edge Stack in your cluster",
		Args:  cobra.ExactArgs(0),
		RunE:  aesInstall,
	}
	_ = res.Flags().StringP(
		"context", "c", "",
		"The Kubernetes context to use. Defaults to the current kubectl context.",
	)
	_ = res.Flags().StringP(
		"namespace", "n", "",
		"The Kubernetes namespace to use. Defaults to kubectl's default for the context.",
	)

	return res
}

func aesInstall(cmd *cobra.Command, args []string) error {
	metrics := NewMetrics()
	_ = metrics.Report("install")

	// Display version information
	// TODO: This displays the version of Edge Control, not the image version in
	// the manifests being downloaded. We should figure out what to do if those
	// don't match up.
	// 1. Allow an old Edge Control to install a newer AES
	// 2. Insist that Edge Control be the same version as the AES it's installing
	// 3. Something else?
	// Note that the second option will allow us to make the install process
	// more complicated in the future without having to subject our users to
	// increasing complexity, assuming this approach to installation gains
	// traction.
	fmt.Printf("-> Installing the Ambassador Edge Stack %s\n", Version)

	// Attempt to talk to the specified cluster
	context, _ := cmd.Flags().GetString("context")
	namespace, _ := cmd.Flags().GetString("namespace")
	kubeinfo := k8s.NewKubeInfo("", context, namespace)
	i := &Installer{
		kubeinfo,
	}
	if err := i.ShowKubectl("cluster-info", "cluster-info"); err != nil {
		return err
	}

	if err := i.ShowKubectl("install CRDs", "apply", "-f", "https://www.getambassador.io/yaml/aes-crds.yaml"); err != nil {
		return err
	}

	if err := i.ShowKubectl("wait for CRDs", "wait", "--for", "condition=established", "--timeout=90s", "crd", "-lproduct=aes"); err != nil {
		return err
	}

	if err := i.ShowKubectl("install AES", "apply", "-f", "https://www.getambassador.io/yaml/aes.yaml"); err != nil {
		return err
	}

	if err := i.ShowKubectl("wait for AES", "-n", "ambassador", "wait", "--for", "condition=available", "--timeout=90s", "deploy", "-lproduct=aes"); err != nil {
		return err
	}

	// Grab Ambassador's install ID as the cluster ID we'll send going forward.
	// Note: Using "kubectl exec" has the side effect of making sure the Pod is
	// Running (though not necessarily Ready). This should be good enough to
	// report the "deploy" status to metrics.
	for {
		if clusterID, err := i.CaptureKubectl("get cluster ID", "-n", "ambassador", "exec", "deploy/ambassador", "python3", "kubewatch.py"); err == nil {
			metrics.SetClusterID(strings.TrimSpace(clusterID))
			break
		}
		time.Sleep(500 * time.Millisecond) // FIXME: Time out at some point...
	}

	_ = metrics.Report("deploy") // TODO: Send cluster type and Helm version

	ipAddress := ""
	for {
		var err error
		ipAddress, err = i.CaptureKubectl("get IP address", "get", "-n", "ambassador", "service", "ambassador", "-o", `go-template={{range .status.loadBalancer.ingress}}{{print .ip "\n"}}{{end}}`)
		if err != nil {
			return err
		}
		ipAddress = strings.TrimSpace(ipAddress)
		if ipAddress != "" {
			break
		}
		time.Sleep(250 * time.Millisecond) // FIXME: Time out at some point...
	}

	fmt.Println("\nYour IP address is", ipAddress)

	// Wait for Ambassador to be ready to serve ACME requests.
	// FIXME: This assumes we can connect to the load balancer. If this
	// assumption is incorrect, this code will loop forever.
	for {
		// FIXME: Time out at some point...
		time.Sleep(500 * time.Millisecond)

		// Verify that we can connect to something
		resp, err := http.Get("http://" + ipAddress + "/.well-known/acme-challenge/")
		if err != nil {
			fmt.Printf("Waiting for Ambassador (get): %#v\n", err)
			continue
		}
		_, _ = ioutil.ReadAll(resp.Body)
		resp.Body.Close()

		// Verify that we get the expected status code. If Ambassador is still
		// starting up, then Envoy may return "upstream request timeout" (503),
		// in which case we should keep looping.
		if resp.StatusCode != 404 {
			fmt.Printf("Waiting for Ambassador: wrong status code: %d\n", resp.StatusCode)
			continue
		}

		// Sanity check that we're talking to Envoy. This is probably unnecessary.
		if resp.Header.Get("server") != "envoy" {
			fmt.Printf("Waiting for Ambassador: wrong server header: %s\n", resp.Header.Get("server"))
			continue
		}
		break
	}

	// Send a request to acquire a DNS name for this cluster's IP address
	regURL := "https://metriton.datawire.io/beta/register-domain"
	emailAddress := "ark3+eci@datawire.io"
	buf := new(bytes.Buffer)
	_ = json.NewEncoder(buf).Encode(registration{emailAddress, ipAddress})
	resp, err := http.Post(regURL, "application/json", buf)
	if err != nil {
		return errors.Wrap(err, "acquire DNS name (post)")
	}
	content, err := ioutil.ReadAll(resp.Body)
	resp.Body.Close()
	if err != nil {
		return errors.Wrap(err, "acquire DNS name (read body)")
	}

	if resp.StatusCode == 200 {
		hostname := string(content)
		fmt.Println("-> Acquiring DNS name", hostname)

		// Wait for DNS to propagate. This tries to avoid waiting for a ten
		// minute error backoff if the ACME registration races ahead of the DNS
		// name appearing for LetsEncrypt.
		for {
			conn, err := net.Dial("tcp", hostname+":443")
			if err == nil {
				conn.Close()
				break
			}
			// fmt.Printf("Waiting for DNS: %#v\n", err)
			time.Sleep(500 * time.Millisecond)
		}
		fmt.Println("-> Automatically configuring TLS")
		fmt.Println("Please enter an email address. We'll use this email address to notify you prior to domain and certification expiration [None]:", emailAddress)
		fmt.Println("FIXME: let the user enter an address")
		// Create a Host resource
		hostResource := fmt.Sprintf(hostManifest, hostname, namespace, hostname, emailAddress)
		kargs, err := i.kubeinfo.GetKubectlArray("apply", "-f", "-")
		if err != nil {
			return errors.Wrapf(err, "cluster access for install AES")
		}
		fmt.Println("\n$ kubectl apply -f - < [Host Resource]")
		cmd := exec.Command("kubectl", kargs...)
		cmd.Stdout = os.Stdout
		cmd.Stderr = os.Stderr
		cmd.Stdin = strings.NewReader(hostResource)
		if err := cmd.Run(); err != nil {
			return errors.Wrap(err, "install AES")
		}

		fmt.Println("\n-> Obtaining a TLS certificate from Let's Encrypt")

		for {
			state, err := i.CaptureKubectl("get Host state", "get", "host", hostname, "-o", "go-template={{.status.state}}")
			if err != nil {
				return err
			}
			if state == "Ready" {
				break
			}
			time.Sleep(500 * time.Millisecond) // FIXME: Time out at some point...
			// FIXME: Do something smart for state == "Error"
		}

		_ = metrics.Report("cert_provisioned")
		fmt.Println("-> TLS configured successfully")
		if err := i.ShowKubectl("show Host", "get", "host", hostname); err != nil {
			return err
		}

		// Open a browser window to the Edge Policy Console
		if err := do_login(kubeinfo, context, "ambassador", hostname, false, false); err != nil {
			return err
		}

	} else {
		message := strings.TrimSpace(string(content))
		fmt.Println("\n-> Failed to create a DNS name:", message)
		fmt.Println()
		fmt.Println("If this IP address is reachable from here, then the following command")
		fmt.Println("will open the Edge Policy Console once you accept a self-signed")
		fmt.Println("certificate in your browser.")
		fmt.Println()
		fmt.Println("    edgectl login -n ambassador", ipAddress)
		fmt.Println()
		fmt.Println("If the IP is not reachable from here, you can use port forwarding to")
		fmt.Println("access the Edge Policy Console.")
		fmt.Println()
		fmt.Println("    kubectl -n ambassador port-forward deploy/ambassador 8443 &")
		fmt.Println("    edgectl login -n ambassador 127.0.0.1:8443")
		fmt.Println()
		fmt.Println("You will need to accept a self-signed certificate in your browser.")
		fmt.Println()
		fmt.Println("See https://www.getambassador.io/user-guide/getting-started/")
	}

	_ = metrics.Report("aes_health_good") // or aes_health_bad TODO: Send cluster's install_id and AES version

	return nil
}

type Installer struct {
	kubeinfo *k8s.KubeInfo
}

// Kubernetes Cluster

func (i *Installer) ShowKubectl(name string, args ...string) error {
	kargs, err := i.kubeinfo.GetKubectlArray(args...)
	if err != nil {
		return errors.Wrapf(err, "cluster access for %s", name)
	}
	fmt.Printf("\n$ kubectl %s\n", strings.Join(kargs, " "))
	cmd := exec.Command("kubectl", kargs...)
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	if err := cmd.Run(); err != nil {
		return errors.Wrap(err, name)
	}
	return nil
}

func (i *Installer) CaptureKubectl(name string, args ...string) (res string, err error) {
	res = ""
	kargs, err := i.kubeinfo.GetKubectlArray(args...)
	if err != nil {
		err = errors.Wrapf(err, "cluster access for %s", name)
		return
	}
	fmt.Printf("\n$ kubectl %s\n", strings.Join(kargs, " "))
	cmd := exec.Command("kubectl", kargs...)
	cmd.Stderr = nil
	resAsBytes, err := cmd.Output()
	if err != nil {
		if ee, ok := err.(*exec.ExitError); ok {
			fmt.Println(ee.Stderr)
		}
		err = errors.Wrap(err, name)
	}
	res = string(resAsBytes)
	fmt.Println(res)
	return
}

// DNS Registration

type registration struct {
	Email string
	Ip    string
}

// Metrics

type Metrics struct {
	scout *Scout
}

func NewMetrics() *Metrics {
	scout, err := NewScout("install")
	if err != nil {
		// Don't crash if Scout stuff doesn't work
		scout = nil
	}
	return &Metrics{scout}
}

func (m *Metrics) SetClusterID(clusterID string) {
	fmt.Println("\n-> [Metrics] Cluster ID (AES install ID) is", clusterID)
	if m.scout != nil {
		m.scout.SetClusterID(clusterID)
	}
}

func (m *Metrics) Report(eventName string, meta ...ScoutMeta) error {
	fmt.Println("\n-> [Metrics]", eventName)
	if m.scout != nil {
		if err := m.scout.Report(eventName, meta...); err != nil {
			fmt.Println("            ", err)
		}
	}
	return nil
}

const hostManifest = `
apiVersion: getambassador.io/v2
kind: Host
metadata:
  name: %s
  namespace: %s
spec:
  hostname: %s
  acmeProvider:
    email: %s
`
<commit_msg>Print errors as strings and add FIXME comment for k exec<commit_after>package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net"
	"net/http"
	"os"
	"os/exec"
	"strings"
	"time"

	"github.com/datawire/ambassador/pkg/k8s"
	"github.com/pkg/errors"
	"github.com/spf13/cobra"
)

func aesInstallCmd() *cobra.Command {
	res := &cobra.Command{
		Use:   "install",
		Short: "Install the Ambassador Edge Stack in your cluster",
		Args:  cobra.ExactArgs(0),
		RunE:  aesInstall,
	}
	_ = res.Flags().StringP(
		"context", "c", "",
		"The Kubernetes context to use. Defaults to the current kubectl context.",
	)
	_ = res.Flags().StringP(
		"namespace", "n", "",
		"The Kubernetes namespace to use. Defaults to kubectl's default for the context.",
	)

	return res
}

func aesInstall(cmd *cobra.Command, args []string) error {
	metrics := NewMetrics()
	_ = metrics.Report("install")

	// Display version information
	// TODO: This displays the version of Edge Control, not the image version in
	// the manifests being downloaded. We should figure out what to do if those
	// don't match up.
	// 1. Allow an old Edge Control to install a newer AES
	// 2. Insist that Edge Control be the same version as the AES it's installing
	// 3. Something else?
	// Note that the second option will allow us to make the install process
	// more complicated in the future without having to subject our users to
	// increasing complexity, assuming this approach to installation gains
	// traction.
	fmt.Printf("-> Installing the Ambassador Edge Stack %s\n", Version)

	// Attempt to talk to the specified cluster
	context, _ := cmd.Flags().GetString("context")
	namespace, _ := cmd.Flags().GetString("namespace")
	kubeinfo := k8s.NewKubeInfo("", context, namespace)
	i := &Installer{
		kubeinfo,
	}
	if err := i.ShowKubectl("cluster-info", "cluster-info"); err != nil {
		return err
	}

	if err := i.ShowKubectl("install CRDs", "apply", "-f", "https://www.getambassador.io/yaml/aes-crds.yaml"); err != nil {
		return err
	}

	if err := i.ShowKubectl("wait for CRDs", "wait", "--for", "condition=established", "--timeout=90s", "crd", "-lproduct=aes"); err != nil {
		return err
	}

	if err := i.ShowKubectl("install AES", "apply", "-f", "https://www.getambassador.io/yaml/aes.yaml"); err != nil {
		return err
	}

	if err := i.ShowKubectl("wait for AES", "-n", "ambassador", "wait", "--for", "condition=available", "--timeout=90s", "deploy", "-lproduct=aes"); err != nil {
		return err
	}

	// Grab Ambassador's install ID as the cluster ID we'll send going forward.
	// Note: Using "kubectl exec" has the side effect of making sure the Pod is
	// Running (though not necessarily Ready). This should be good enough to
	// report the "deploy" status to metrics.
	for {
		// FIXME This doesn't work with `kubectl` 1.13 (and possibly 1.14). We
		// FIXME need to discover and use the pod name with `kubectl exec`.
		if clusterID, err := i.CaptureKubectl("get cluster ID", "-n", "ambassador", "exec", "deploy/ambassador", "python3", "kubewatch.py"); err == nil {
			metrics.SetClusterID(strings.TrimSpace(clusterID))
			break
		}
		time.Sleep(500 * time.Millisecond) // FIXME: Time out at some point...
	}

	_ = metrics.Report("deploy") // TODO: Send cluster type and Helm version

	ipAddress := ""
	for {
		var err error
		ipAddress, err = i.CaptureKubectl("get IP address", "get", "-n", "ambassador", "service", "ambassador", "-o", `go-template={{range .status.loadBalancer.ingress}}{{print .ip "\n"}}{{end}}`)
		if err != nil {
			return err
		}
		ipAddress = strings.TrimSpace(ipAddress)
		if ipAddress != "" {
			break
		}
		time.Sleep(250 * time.Millisecond) // FIXME: Time out at some point...
	}

	fmt.Println("\nYour IP address is", ipAddress)

	// Wait for Ambassador to be ready to serve ACME requests.
	// FIXME: This assumes we can connect to the load balancer. If this
	// assumption is incorrect, this code will loop forever.
	for {
		// FIXME: Time out at some point...
		time.Sleep(500 * time.Millisecond)

		// Verify that we can connect to something
		resp, err := http.Get("http://" + ipAddress + "/.well-known/acme-challenge/")
		if err != nil {
			fmt.Printf("Waiting for Ambassador (get): %#v\n", err)
			continue
		}
		_, _ = ioutil.ReadAll(resp.Body)
		resp.Body.Close()

		// Verify that we get the expected status code. If Ambassador is still
		// starting up, then Envoy may return "upstream request timeout" (503),
		// in which case we should keep looping.
		if resp.StatusCode != 404 {
			fmt.Printf("Waiting for Ambassador: wrong status code: %d\n", resp.StatusCode)
			continue
		}

		// Sanity check that we're talking to Envoy. This is probably unnecessary.
		if resp.Header.Get("server") != "envoy" {
			fmt.Printf("Waiting for Ambassador: wrong server header: %s\n", resp.Header.Get("server"))
			continue
		}
		break
	}

	// Send a request to acquire a DNS name for this cluster's IP address
	regURL := "https://metriton.datawire.io/beta/register-domain"
	emailAddress := "ark3+eci@datawire.io"
	buf := new(bytes.Buffer)
	_ = json.NewEncoder(buf).Encode(registration{emailAddress, ipAddress})
	resp, err := http.Post(regURL, "application/json", buf)
	if err != nil {
		return errors.Wrap(err, "acquire DNS name (post)")
	}
	content, err := ioutil.ReadAll(resp.Body)
	resp.Body.Close()
	if err != nil {
		return errors.Wrap(err, "acquire DNS name (read body)")
	}

	if resp.StatusCode == 200 {
		hostname := string(content)
		fmt.Println("-> Acquiring DNS name", hostname)

		// Wait for DNS to propagate. This tries to avoid waiting for a ten
		// minute error backoff if the ACME registration races ahead of the DNS
		// name appearing for LetsEncrypt.
		for {
			conn, err := net.Dial("tcp", hostname+":443")
			if err == nil {
				conn.Close()
				break
			}
			// fmt.Printf("Waiting for DNS: %#v\n", err)
			time.Sleep(500 * time.Millisecond)
		}
		fmt.Println("-> Automatically configuring TLS")
		fmt.Println("Please enter an email address. We'll use this email address to notify you prior to domain and certification expiration [None]:", emailAddress)
		fmt.Println("FIXME: let the user enter an address")
		// Create a Host resource
		hostResource := fmt.Sprintf(hostManifest, hostname, namespace, hostname, emailAddress)
		kargs, err := i.kubeinfo.GetKubectlArray("apply", "-f", "-")
		if err != nil {
			return errors.Wrapf(err, "cluster access for install AES")
		}
		fmt.Println("\n$ kubectl apply -f - < [Host Resource]")
		cmd := exec.Command("kubectl", kargs...)
		cmd.Stdout = os.Stdout
		cmd.Stderr = os.Stderr
		cmd.Stdin = strings.NewReader(hostResource)
		if err := cmd.Run(); err != nil {
			return errors.Wrap(err, "install AES")
		}

		fmt.Println("\n-> Obtaining a TLS certificate from Let's Encrypt")

		for {
			state, err := i.CaptureKubectl("get Host state", "get", "host", hostname, "-o", "go-template={{.status.state}}")
			if err != nil {
				return err
			}
			if state == "Ready" {
				break
			}
			time.Sleep(500 * time.Millisecond) // FIXME: Time out at some point...
			// FIXME: Do something smart for state == "Error"
		}

		_ = metrics.Report("cert_provisioned")
		fmt.Println("-> TLS configured successfully")
		if err := i.ShowKubectl("show Host", "get", "host", hostname); err != nil {
			return err
		}

		// Open a browser window to the Edge Policy Console
		if err := do_login(kubeinfo, context, "ambassador", hostname, false, false); err != nil {
			return err
		}

	} else {
		message := strings.TrimSpace(string(content))
		fmt.Println("\n-> Failed to create a DNS name:", message)
		fmt.Println()
		fmt.Println("If this IP address is reachable from here, then the following command")
		fmt.Println("will open the Edge Policy Console once you accept a self-signed")
		fmt.Println("certificate in your browser.")
		fmt.Println()
		fmt.Println("    edgectl login -n ambassador", ipAddress)
		fmt.Println()
		fmt.Println("If the IP is not reachable from here, you can use port forwarding to")
		fmt.Println("access the Edge Policy Console.")
		fmt.Println()
		fmt.Println("    kubectl -n ambassador port-forward deploy/ambassador 8443 &")
		fmt.Println("    edgectl login -n ambassador 127.0.0.1:8443")
		fmt.Println()
		fmt.Println("You will need to accept a self-signed certificate in your browser.")
		fmt.Println()
		fmt.Println("See https://www.getambassador.io/user-guide/getting-started/")
	}

	_ = metrics.Report("aes_health_good") // or aes_health_bad TODO: Send cluster's install_id and AES version

	return nil
}

type Installer struct {
	kubeinfo *k8s.KubeInfo
}

// Kubernetes Cluster

func (i *Installer) ShowKubectl(name string, args ...string) error {
	kargs, err := i.kubeinfo.GetKubectlArray(args...)
	if err != nil {
		return errors.Wrapf(err, "cluster access for %s", name)
	}
	fmt.Printf("\n$ kubectl %s\n", strings.Join(kargs, " "))
	cmd := exec.Command("kubectl", kargs...)
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	if err := cmd.Run(); err != nil {
		return errors.Wrap(err, name)
	}
	return nil
}

func (i *Installer) CaptureKubectl(name string, args ...string) (res string, err error) {
	res = ""
	kargs, err := i.kubeinfo.GetKubectlArray(args...)
	if err != nil {
		err = errors.Wrapf(err, "cluster access for %s", name)
		return
	}
	fmt.Printf("\n$ kubectl %s\n", strings.Join(kargs, " "))
	cmd := exec.Command("kubectl", kargs...)
	cmd.Stderr = nil
	resAsBytes, err := cmd.Output()
	if err != nil {
		if ee, ok := err.(*exec.ExitError); ok {
			fmt.Println(string(ee.Stderr))
		}
		err = errors.Wrap(err, name)
	}
	res = string(resAsBytes)
	fmt.Println(res)
	return
}

// DNS Registration

type registration struct {
	Email string
	Ip    string
}

// Metrics

type Metrics struct {
	scout *Scout
}

func NewMetrics() *Metrics {
	scout, err := NewScout("install")
	if err != nil {
		// Don't crash if Scout stuff doesn't work
		scout = nil
	}
	return &Metrics{scout}
}

func (m *Metrics) SetClusterID(clusterID string) {
	fmt.Println("\n-> [Metrics] Cluster ID (AES install ID) is", clusterID)
	if m.scout != nil {
		m.scout.SetClusterID(clusterID)
	}
}

func (m *Metrics) Report(eventName string, meta ...ScoutMeta) error {
	fmt.Println("\n-> [Metrics]", eventName)
	if m.scout != nil {
		if err := m.scout.Report(eventName, meta...); err != nil {
			fmt.Println("            ", err)
		}
	}
	return nil
}

const hostManifest = `
apiVersion: getambassador.io/v2
kind: Host
metadata:
  name: %s
  namespace: %s
spec:
  hostname: %s
  acmeProvider:
    email: %s
`
<|endoftext|>


<commit_before>package main

import (
	//	"bytes"
	"flag"
	"fmt"
	"github.com/PuerkitoBio/fetchbot"
	"github.com/loic-fejoz/microformat-golang-toolbox"
	"log"
	"net/http"
	"net/url"
	//	"runtime"
	//	"strings"
	"golang.org/x/net/html"
	"sync"
	"time"
)

var (
	// Protect access to dup
	mu sync.Mutex
	// Duplicates table
	dup            = map[string]bool{}
	hCardDirectory = map[string]*microformat2.Element{}
	// Command-line flags
	seed      = flag.String("visit", "http://wiki.nybi.cc/index.php/Utilisateur:Loic.fejoz", "seed URL, ie where to start")
	stopAfter = flag.Duration("stopafter", 0, "automatically stop the fetchbot after a given time")
)

func writePeopleDirectory() {
	for _, hCard := range hCardDirectory {
		fmt.Printf("%s\t%s\n", hCard.Properties["name"], hCard.Properties["url"])
	}
}

func main() {
	flag.Parse()
	// Parse the provided seed
	u, err := url.Parse(*seed)
	if err != nil {
		log.Fatal(err)
	}
	// Create the muxer
	mux := fetchbot.NewMux()

	// Handle all errors the same
	mux.HandleErrors(fetchbot.HandlerFunc(func(ctx *fetchbot.Context, res *http.Response, err error) {
		fmt.Printf("[ERR] %s %s - %s\n", ctx.Cmd.Method(), ctx.Cmd.URL(), err)
	}))
	// Handle GET requests for html responses, parse for Microformat 2 (more to come), and follow new links
	mux.Response().Method("GET").ContentType("text/html").Handler(fetchbot.HandlerFunc(
		func(ctx *fetchbot.Context, res *http.Response, err error) {
			// Process the body to find the links

			doc, err := html.Parse(res.Body)
			if err != nil {
				fmt.Printf("[ERR] %s %s - %s\n", ctx.Cmd.Method(), ctx.Cmd.URL(), err)
				return
			}
			microformats, err := microformat2.Parse(doc)
			if err != nil {
				fmt.Printf("[ERR] %s %s - %s\n", ctx.Cmd.Method(), ctx.Cmd.URL(), err)
				return
			}
			enqueueLinks(ctx, microformats)
		}))
	// Handle HEAD requests for html responses coming from the source host - we don't want
	// to crawl links from other hosts.
	mux.Response().Method("HEAD").Host(u.Host).ContentType("text/html").Handler(fetchbot.HandlerFunc(
		func(ctx *fetchbot.Context, res *http.Response, err error) {
			if _, err := ctx.Q.SendStringGet(ctx.Cmd.URL().String()); err != nil {
				fmt.Printf("[ERR] %s %s - %s\n", ctx.Cmd.Method(), ctx.Cmd.URL(), err)
			}
		}))
	// Create the Fetcher, handle the logging first, then dispatch to the Muxer
	h := logHandler(mux)
	f := fetchbot.New(h)
	f.AutoClose = true
	// Start processing
	q := f.Start()
	if *stopAfter > 0 {
		go func() {
			c := time.After(*stopAfter)
			<-c
			q.Close()
		}()
	}
	// Enqueue the seed, which is the first entry in the dup map
	dup[*seed] = true
	_, err = q.SendStringGet(*seed)
	if err != nil {
		fmt.Printf("[ERR] GET %s - %s\n", *seed, err)
	}
	q.Block()
	writePeopleDirectory()
	fmt.Printf("bye\n")
}

// logHandler prints the fetch information and dispatches the call to the wrapped Handler.
func logHandler(wrapped fetchbot.Handler) fetchbot.Handler {
	return fetchbot.HandlerFunc(func(ctx *fetchbot.Context, res *http.Response, err error) {
		if err == nil {
			fmt.Printf("[%d] %s %s - %s\n", res.StatusCode, ctx.Cmd.Method(), ctx.Cmd.URL(), res.Header.Get("Content-Type"))
		}
		wrapped.Handle(ctx, res, err)
	})
}

func visitUrl(ctx *fetchbot.Context, url string) {
	// Resolve address
	u, err := ctx.Cmd.URL().Parse(url)
	if err != nil {
		fmt.Printf("error: resolve URL %s - %s\n", url, err)
		return
	}
	if !dup[u.String()] {
		if _, err := ctx.Q.SendStringGet(u.String()); err != nil {
			fmt.Printf("error: enqueue get %s - %s\n", u, err)
		} else {
			fmt.Printf("Enqueuing %s\n", u)
			dup[u.String()] = true
		}
	}
}

func enqueueLinks(ctx *fetchbot.Context, result *microformat2.Result) {
	mu.Lock()
	for _, h_card := range result.Items {
		urls := h_card.Properties["url"]
		if urls != nil {
			// Keep (or consolidate) the h_card in the directory
			if len(urls) > 0 {
				theUrl := urls[0].(string)
				previous := hCardDirectory[theUrl]
				if previous == nil {
					hCardDirectory[theUrl] = h_card
				} else {
					hCardDirectory[theUrl] = microformat2.Append(previous, h_card)
				}

			}
			for _, url := range urls {
				visitUrl(ctx, url.(string))
			}
		}
	}
	fmt.Printf("[TREATED] %s %s\n", ctx.Cmd.Method(), ctx.Cmd.URL())
	mu.Unlock()
}
<commit_msg>Code refactoring.<commit_after>package main

import (
	//	"bytes"
	"flag"
	"fmt"
	"github.com/PuerkitoBio/fetchbot"
	"github.com/loic-fejoz/microformat-golang-toolbox"
	"log"
	"net/http"
	"net/url"
	//	"runtime"
	//	"strings"
	"golang.org/x/net/html"
	"sync"
	"time"
)

var (
	// Protect access to dup
	mu sync.Mutex
	// Duplicates table
	dup            = map[string]bool{}
	hCardDirectory = map[string]*microformat2.Element{}
	// Command-line flags
	seed      = flag.String("visit", "http://localhost/mediawiki/index.php/Accueil" /* "http://wiki.nybi.cc/index.php/Utilisateur:Loic.fejoz"*/, "seed URL, ie where to start")
	stopAfter = flag.Duration("stopafter", 0, "automatically stop the fetchbot after a given time")
)

func writePeopleDirectory() {
	for _, hCard := range hCardDirectory {
		fmt.Printf("%s\t%s\n", hCard.Properties["name"], hCard.Properties["url"])
	}
}

func main() {
	flag.Parse()
	// Parse the provided seed
	u, err := url.Parse(*seed)
	if err != nil {
		log.Fatal(err)
	}
	// Create the muxer
	mux := fetchbot.NewMux()

	// Handle all errors the same
	mux.HandleErrors(fetchbot.HandlerFunc(func(ctx *fetchbot.Context, res *http.Response, err error) {
		fmt.Printf("[ERR] %s %s - %s\n", ctx.Cmd.Method(), ctx.Cmd.URL(), err)
	}))
	// Handle GET requests for html responses, parse for Microformat 2 (more to come), and follow new links
	mux.Response().Method("GET").ContentType("text/html").Handler(fetchbot.HandlerFunc(
		func(ctx *fetchbot.Context, res *http.Response, err error) {
			// Process the body to find the links

			doc, err := html.Parse(res.Body)
			if err != nil {
				fmt.Printf("[ERR] %s %s - %s\n", ctx.Cmd.Method(), ctx.Cmd.URL(), err)
				return
			}
			microformats, err := microformat2.Parse(doc)
			if err != nil {
				fmt.Printf("[ERR] %s %s - %s\n", ctx.Cmd.Method(), ctx.Cmd.URL(), err)
				return
			}
			enqueueLinks(ctx, microformats)
		}))
	// Handle HEAD requests for html responses coming from the source host - we don't want
	// to crawl links from other hosts.
	mux.Response().Method("HEAD").Host(u.Host).ContentType("text/html").Handler(fetchbot.HandlerFunc(
		func(ctx *fetchbot.Context, res *http.Response, err error) {
			if _, err := ctx.Q.SendStringGet(ctx.Cmd.URL().String()); err != nil {
				fmt.Printf("[ERR] %s %s - %s\n", ctx.Cmd.Method(), ctx.Cmd.URL(), err)
			}
		}))
	// Create the Fetcher, handle the logging first, then dispatch to the Muxer
	h := logHandler(mux)
	f := fetchbot.New(h)
	f.AutoClose = true
	// Start processing
	q := f.Start()
	if *stopAfter > 0 {
		go func() {
			c := time.After(*stopAfter)
			<-c
			q.Close()
		}()
	}
	// Enqueue the seed, which is the first entry in the dup map
	dup[*seed] = true
	_, err = q.SendStringGet(*seed)
	if err != nil {
		fmt.Printf("[ERR] GET %s - %s\n", *seed, err)
	}
	q.Block()
	writePeopleDirectory()
	fmt.Printf("bye\n")
}

// logHandler prints the fetch information and dispatches the call to the wrapped Handler.
func logHandler(wrapped fetchbot.Handler) fetchbot.Handler {
	return fetchbot.HandlerFunc(func(ctx *fetchbot.Context, res *http.Response, err error) {
		if err == nil {
			fmt.Printf("[%d] %s %s - %s\n", res.StatusCode, ctx.Cmd.Method(), ctx.Cmd.URL(), res.Header.Get("Content-Type"))
		}
		wrapped.Handle(ctx, res, err)
	})
}

func visitUrl(ctx *fetchbot.Context, url string) {
	// Resolve address
	u, err := ctx.Cmd.URL().Parse(url)
	if err != nil {
		fmt.Printf("error: resolve URL %s - %s\n", url, err)
		return
	}
	if !dup[u.String()] {
		if _, err := ctx.Q.SendStringGet(u.String()); err != nil {
			fmt.Printf("error: enqueue get %s - %s\n", u, err)
		} else {
			fmt.Printf("Enqueuing %s\n", u)
			dup[u.String()] = true
		}
	}
}

func enqueueLinksOf(ctx *fetchbot.Context, hCard *microformat2.Element) {
	urls := hCard.Properties["url"]
	if urls != nil {
		// Keep (or consolidate) the hCard in the directory under first url
		// TODO: use u-uid as identifier if any
		if len(urls) > 0 {
			theUrl := urls[0].(string)
			previous := hCardDirectory[theUrl]
			if previous == nil {
				hCardDirectory[theUrl] = hCard
			} else {
				hCardDirectory[theUrl] = microformat2.Append(previous, hCard)
			}

		}
		// Enqueue all urls
		for _, url := range urls {
			visitUrl(ctx, url.(string))
		}
	}
}

func enqueueLinks(ctx *fetchbot.Context, result *microformat2.Result) {
	mu.Lock()
	for _, hCard := range result.Items {
		// Visit this card urls from properties
		enqueueLinksOf(ctx, hCard)
		orgs := hCard.Properties["org"]
		// Visit urls of this card's organisation
		if orgs != nil {
			for _, org := range orgs {
				orgElt := org.(*microformat2.Element)
				if orgElt != nil {
					enqueueLinksOf(ctx, hCard)
				}
			}
		}
	}
	fmt.Printf("[TREATED] %s %s\n", ctx.Cmd.Method(), ctx.Cmd.URL())
	mu.Unlock()
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"strings"
	"time"

	"github.com/cactus/go-statsd-client/statsd"
	"github.com/chrishoffman/haproxylog"
)

type statsdHandler struct {
	client statsd.Statter
}

func newStatsdHandler(address string, prefix string) *statsdHandler {
	flushInterval := time.Second * 1

	client, err := statsd.NewBufferedClient(address, prefix, flushInterval, 0)
	if err != nil {
		panic(err)
	}

	return &statsdHandler{client}
}

func (s *statsdHandler) logHandler(log *haproxy.Log) {
	switch log.GetFormat() {
	case haproxy.HTTP:
		s.sendHTTPStats(log)
	}
}

func (s *statsdHandler) sendHTTPStats(log *haproxy.Log) {
	frontendName := cleanStatToken(log.BackendName)
	backendName := cleanStatToken(log.FrontendName)
	serverName := cleanAndLowerStatToken(log.ServerName)
	requestStatPrefix := fmt.Sprintf("%s.%s.%s", frontendName, backendName, serverName)

	// Request stats
	s.inc(fmt.Sprintf("%s.response_size", requestStatPrefix), log.BytesRead)
	s.inc(fmt.Sprintf("%s.hits", requestStatPrefix), 1)
	s.inc(fmt.Sprintf("%s.responses.%d", requestStatPrefix, log.HTTPStatusCode), 1)

	// Timing Stats
	s.timing(fmt.Sprintf("%s.response_time", requestStatPrefix), log.Tt)
	s.timing(fmt.Sprintf("%s.queue_time", requestStatPrefix), log.Tw)
	s.timing(fmt.Sprintf("%s.request_time", requestStatPrefix), log.Tq)

	// Misc Stats (stored in timing to average)
	s.timing(fmt.Sprintf("%s.retries", requestStatPrefix), log.Retries)
	s.timing(fmt.Sprintf("%s.queue", requestStatPrefix), log.ServerQueue)
	s.timing(fmt.Sprintf("%s.active_connections", requestStatPrefix), log.ActConn)
	s.timing(fmt.Sprintf("%s.backend_connections", requestStatPrefix), log.BeConn)
	s.timing(fmt.Sprintf("%s.frontend_connections", requestStatPrefix), log.FeConn)
	s.timing(fmt.Sprintf("%s.server_connections", requestStatPrefix), log.SrvConn)
	s.timing(fmt.Sprintf("%s.response_size", requestStatPrefix), log.BytesRead)

	// Backend Stats
	backendPrefix := fmt.Sprintf("backend.%s", backendName)
	s.timing(fmt.Sprintf("%s.connect_time", backendPrefix), log.Tc)
	s.timing(fmt.Sprintf("%s.response_time", backendPrefix), log.Tr)
	s.timing(fmt.Sprintf("%s.queue", backendPrefix), log.BackendQueue)

	// Endpoint stats
	pathParts := strings.Split(log.HTTPRequest.URL.Path, "/")
	if len(pathParts) > 1 {
		basePath := pathParts[1]
		if basePath == "" {
			basePath = "_root_"
		}
		s.timing(fmt.Sprintf("%s.endpoint.%s.%s.response_time", requestStatPrefix, cleanAndLowerStatToken(basePath),
			log.HTTPRequest.Method), log.Tt)
	}

	// SSL stats
	if log.SslVersion != "" {
		sslStat := fmt.Sprintf("%s.ssl.%s.%s", cleanStatToken(log.FrontendName),
			cleanStatToken(log.SslVersion), cleanStatToken(log.SslCipher))
		s.inc(sslStat, 1)
	}
}

func (s *statsdHandler) inc(stat string, count int64) {
	s.client.Inc(stat, count, 1.0)
}

func (s *statsdHandler) timing(stat string, delta int64) {
	if delta == -1 {
		return
	}
	s.client.Timing(stat, delta, 1.0)
}

func cleanAndLowerStatToken(s string) string {
	return strings.ToLower(cleanStatToken(s))
}

func cleanStatToken(s string) string {
	r := map[string]string{
		".": "_",
		"~": "",
		"<": "-",
		">": "-",
	}
	for o, n := range r {
		s = strings.Replace(s, o, n, -1)
	}
	return s
}
<commit_msg>Fixing some stats prefixes<commit_after>package main

import (
	"fmt"
	"strings"
	"time"

	"github.com/cactus/go-statsd-client/statsd"
	"github.com/chrishoffman/haproxylog"
)

type statsdHandler struct {
	client statsd.Statter
}

func newStatsdHandler(address string, prefix string) *statsdHandler {
	flushInterval := time.Second * 1

	client, err := statsd.NewBufferedClient(address, prefix, flushInterval, 0)
	if err != nil {
		panic(err)
	}

	return &statsdHandler{client}
}

func (s *statsdHandler) logHandler(log *haproxy.Log) {
	switch log.GetFormat() {
	case haproxy.HTTP:
		s.sendHTTPStats(log)
	}
}

func (s *statsdHandler) sendHTTPStats(log *haproxy.Log) {
	frontendName := cleanStatToken(log.FrontendName)
	backendName := cleanStatToken(log.BackendName)
	serverName := cleanAndLowerStatToken(log.ServerName)
	requestStatPrefix := fmt.Sprintf("%s.%s.%s", frontendName, backendName, serverName)

	// Request stats
	s.inc(fmt.Sprintf("%s.hits", requestStatPrefix), 1)
	s.inc(fmt.Sprintf("%s.responses.%d", requestStatPrefix, log.HTTPStatusCode), 1)

	// Timing Stats
	s.timing(fmt.Sprintf("%s.response_time", requestStatPrefix), log.Tt)
	s.timing(fmt.Sprintf("%s.queue_time", requestStatPrefix), log.Tw)
	s.timing(fmt.Sprintf("%s.request_time", requestStatPrefix), log.Tq)

	// Misc Stats (stored in timing to average)
	s.timing(fmt.Sprintf("%s.retries", requestStatPrefix), log.Retries)
	s.timing(fmt.Sprintf("%s.queue", requestStatPrefix), log.ServerQueue)
	s.timing(fmt.Sprintf("%s.active_connections", requestStatPrefix), log.ActConn)
	s.timing(fmt.Sprintf("%s.backend_connections", requestStatPrefix), log.BeConn)
	s.timing(fmt.Sprintf("%s.frontend_connections", requestStatPrefix), log.FeConn)
	s.timing(fmt.Sprintf("%s.server_connections", requestStatPrefix), log.SrvConn)
	s.timing(fmt.Sprintf("%s.response_size", requestStatPrefix), log.BytesRead)

	// Backend Stats
	backendPrefix := fmt.Sprintf("_backend.%s", backendName)
	s.timing(fmt.Sprintf("%s.connect_time", backendPrefix), log.Tc)
	s.timing(fmt.Sprintf("%s.response_time", backendPrefix), log.Tr)
	s.timing(fmt.Sprintf("%s.queue", backendPrefix), log.BackendQueue)

	// Endpoint stats
	pathParts := strings.Split(log.HTTPRequest.URL.Path, "/")
	if len(pathParts) > 1 {
		basePath := pathParts[1]
		if basePath == "" {
			basePath = "_root_"
		}
		s.timing(fmt.Sprintf("%s.endpoint.%s.%s.response_time", requestStatPrefix, cleanAndLowerStatToken(basePath),
			log.HTTPRequest.Method), log.Tt)
	}

	// SSL stats
	if log.SslVersion != "" {
		sslStat := fmt.Sprintf("_frontend.%s.ssl.%s.%s", frontendName,
			cleanStatToken(log.SslVersion), cleanStatToken(log.SslCipher))
		s.inc(sslStat, 1)
	}
}

func (s *statsdHandler) inc(stat string, count int64) {
	s.client.Inc(stat, count, 1.0)
}

func (s *statsdHandler) timing(stat string, delta int64) {
	if delta == -1 {
		return
	}
	s.client.Timing(stat, delta, 1.0)
}

func cleanAndLowerStatToken(s string) string {
	return strings.ToLower(cleanStatToken(s))
}

func cleanStatToken(s string) string {
	r := map[string]string{
		".": "_",
		"~": "",
		"<": "-",
		">": "-",
	}
	for o, n := range r {
		s = strings.Replace(s, o, n, -1)
	}
	return s
}
<|endoftext|>


<commit_before>package ov

type LogicalSwitch struct {
	Category                string                  `json:"category,omitempty"`          // "category": "logcial-switch-groups",
	ConstitencyStatus       string                  `json:"constitencyStatus,omitempty"` //"consitencyStatue": "CONSISTENT",
	Created                 string                  `json:"created,omitempty"`           // "created": "20150831T154835.250Z",
	Description             string                  `json:"description,omitempty"`       // "description": "Logical Switch 1",
	ETAG                    string                  `json:"eTag,omitempty"`              // "eTag": "1441036118675/8",
	FabricUri               utils.Nstring           `json:"fabricUri,omitempty"`         // "fabricUri": "/rest/fabrics/9b8f7ec0-52b3-475e-84f4-c4eac51c2c20",
	LogicalSwitchDomainInfo LogicalSwitchDomainInfo `json:"logicalSwitchDomainInfo"`
	Modified                string                  `json:"modified,omitempty"` // "modified": "20150831T154835.250Z",
	Name                    string                  `json:"name,omitempty"`     // "name": "Logical Switch Group1",
	State                   string                  `json:"state,omitempty"`    // "state": "Normal",
	Status                  string                  `json:"status,omitempty"`   // "status": "Critical",
	Type                    string                  `json:"type,omitempty"`     // "type": "logical-switch-groups",
	URI                     utils.Nstring           `json:"uri,omitempty"`      // "uri": "/rest/logical-switch-groups/e2f0031b-52bd-4223-9ac1-d91cb519d548",
	SwitchMapTemplate       SwitchMapTemplate       `json:"switchMapTemplate"`
}

type LogicalSwitchDomainInfo struct {
	DomainId         string              `json:"domainId"`         //"domainId": "NA",
	MasterMacAddress string              `json:"masterMacAddress"` //"masterMacAddress": "NA",
	PerSwitchDomain  []LSDomainPerSwitch `json:"perSwitchDomain"`
}

type LSDomainPerSwitch struct {
	FirmwareVersion string `json:"firmwareVersion"` //"firmwareVersion": "unknown",
	IPAddress       string `json:"ipAddress"`       //"ipAddress": "172.18.1.11",
}
<commit_msg>fixed fmt issues<commit_after>package ov

import (
	"github.com/HewlettPackard/oneview-golang/utils"
)

type LogicalSwitch struct {
	Category                string                  `json:"category,omitempty"`          // "category": "logcial-switch-groups",
	ConstitencyStatus       string                  `json:"constitencyStatus,omitempty"` //"consitencyStatue": "CONSISTENT",
	Created                 string                  `json:"created,omitempty"`           // "created": "20150831T154835.250Z",
	Description             string                  `json:"description,omitempty"`       // "description": "Logical Switch 1",
	ETAG                    string                  `json:"eTag,omitempty"`              // "eTag": "1441036118675/8",
	FabricUri               utils.Nstring           `json:"fabricUri,omitempty"`         // "fabricUri": "/rest/fabrics/9b8f7ec0-52b3-475e-84f4-c4eac51c2c20",
	LogicalSwitchDomainInfo LogicalSwitchDomainInfo `json:"logicalSwitchDomainInfo"`
	Modified                string                  `json:"modified,omitempty"` // "modified": "20150831T154835.250Z",
	Name                    string                  `json:"name,omitempty"`     // "name": "Logical Switch Group1",
	State                   string                  `json:"state,omitempty"`    // "state": "Normal",
	Status                  string                  `json:"status,omitempty"`   // "status": "Critical",
	Type                    string                  `json:"type,omitempty"`     // "type": "logical-switch-groups",
	URI                     utils.Nstring           `json:"uri,omitempty"`      // "uri": "/rest/logical-switch-groups/e2f0031b-52bd-4223-9ac1-d91cb519d548",
	SwitchMapTemplate       SwitchMapTemplate       `json:"switchMapTemplate"`
}

type LogicalSwitchDomainInfo struct {
	DomainId         string              `json:"domainId"`         //"domainId": "NA",
	MasterMacAddress string              `json:"masterMacAddress"` //"masterMacAddress": "NA",
	PerSwitchDomain  []LSDomainPerSwitch `json:"perSwitchDomain"`
}

type LSDomainPerSwitch struct {
	FirmwareVersion string `json:"firmwareVersion"` //"firmwareVersion": "unknown",
	IPAddress       string `json:"ipAddress"`       //"ipAddress": "172.18.1.11",
}
<|endoftext|>


<commit_before>// Command juggler-client can be used to send commands to a juggler
// server.
package main

import (
	"flag"
	"fmt"
	"io"
	"log"
	"os"
	"strings"
	"time"

	"golang.org/x/crypto/ssh/terminal"
)

const welcomeMessage = `
Welcome to the juggler client. Enter ? or help for the available
commands. Press ^D (ctrl-D) to exit.
`

var (
	term *terminal.Terminal
)

var (
	defaultConnFlag     = flag.String("addr", "ws://localhost:9000/ws", "default dial address to use in connect commands")
	defaultSubprotoFlag = flag.String("subprotocol", "juggler.0", "default subprotocol to request in the websocket handshake")
	timestampFmtFlag    = flag.String("timestamp", time.StampMilli, "format of the timestamp in the output, empty for none")
)

func main() {
	var exitCode int

	flag.Parse()

	// call os.Exit in a defer, otherwise defer to reset the terminal
	// will not be run.
	defer func() {
		if exitCode != 0 {
			os.Exit(exitCode)
		}
	}()

	// setup and restore the terminal
	t, fn := setupTerminal()
	defer fn()
	term = t

	printfTs(welcomeMessage, "")
	for {
		l, err := t.ReadLine()
		if err != nil {
			if err == io.EOF {
				return
			}
			printErr("failed to read line: %v", err)
			exitCode = 1
			return
		}

		args := strings.Fields(l)
		if len(args) != 0 {
			if cmd := commands[args[0]]; cmd != nil {
				args = args[1:]
				if len(args) < cmd.MinArgs {
					printErr(cmd.Usage)
					continue
				}
				if cmd == exitCmd {
					return
				}
				cmd.Run(cmd, args...)
			} else {
				printErr("unknown command: %q", args[0])
			}
		}
	}
}

func setupTerminal() (*terminal.Terminal, func()) {
	// setup terminal
	oldState, err := terminal.MakeRaw(0)
	if err != nil {
		log.Fatalf("failed to initialize the terminal: %v", err)
	}
	cleanUp := func() { terminal.Restore(0, oldState) }

	var screen = struct {
		io.Reader
		io.Writer
	}{os.Stdin, os.Stdout}
	t := terminal.NewTerminal(screen, "juggler> ")
	return t, cleanUp
}

func printfTs(msg, ts string, args ...interface{}) {
	if ts != "" {
		t := time.Now().Format(ts)
		msg = t + " | " + msg
	}
	fmt.Fprintf(term, msg+"\n", args...)
}

func printf(msg string, args ...interface{}) {
	printfTs(msg, *timestampFmtFlag, args...)
}

func printErr(msg string, args ...interface{}) {
	term.Write(term.Escape.Red)
	printf(msg, args...)
	term.Write(term.Escape.Reset)
}
<commit_msg>juggler/cmd: clean-up flag description in juggler-client<commit_after>// Command juggler-client can be used to send commands to a juggler
// server.
package main

import (
	"flag"
	"fmt"
	"io"
	"log"
	"os"
	"strings"
	"time"

	"golang.org/x/crypto/ssh/terminal"
)

const welcomeMessage = `
Welcome to the juggler client. Enter ? or help for the available
commands. Press ^D (ctrl-D) to exit.
`

var (
	term *terminal.Terminal
)

var (
	defaultConnFlag     = flag.String("addr", "ws://localhost:9000/ws", "Default server `address` used in connect command.")
	defaultSubprotoFlag = flag.String("proto", "juggler.0", "Default `subprotocol` used in connect command.")
	timestampFmtFlag    = flag.String("timestamp", time.StampMilli, "Timestamp `format`, using Go time format syntax.")
	helpFlag            = flag.Bool("help", false, "Show help.")
)

func main() {
	var exitCode int

	flag.Parse()
	if *helpFlag {
		flag.Usage()
		return
	}

	// call os.Exit in a defer, otherwise defer to reset the terminal
	// will not be run.
	defer func() {
		if exitCode != 0 {
			os.Exit(exitCode)
		}
	}()

	// setup and restore the terminal
	t, fn := setupTerminal()
	defer fn()
	term = t

	printfTs(welcomeMessage, "")
	for {
		l, err := t.ReadLine()
		if err != nil {
			if err == io.EOF {
				return
			}
			printErr("failed to read line: %v", err)
			exitCode = 1
			return
		}

		args := strings.Fields(l)
		if len(args) != 0 {
			if cmd := commands[args[0]]; cmd != nil {
				args = args[1:]
				if len(args) < cmd.MinArgs {
					printErr(cmd.Usage)
					continue
				}
				if cmd == exitCmd {
					return
				}
				cmd.Run(cmd, args...)
			} else {
				printErr("unknown command: %q", args[0])
			}
		}
	}
}

func setupTerminal() (*terminal.Terminal, func()) {
	// setup terminal
	oldState, err := terminal.MakeRaw(0)
	if err != nil {
		log.Fatalf("failed to initialize the terminal: %v", err)
	}
	cleanUp := func() { terminal.Restore(0, oldState) }

	var screen = struct {
		io.Reader
		io.Writer
	}{os.Stdin, os.Stdout}
	t := terminal.NewTerminal(screen, "juggler> ")
	return t, cleanUp
}

func printfTs(msg, ts string, args ...interface{}) {
	if ts != "" {
		t := time.Now().Format(ts)
		msg = t + " | " + msg
	}
	fmt.Fprintf(term, msg+"\n", args...)
}

func printf(msg string, args ...interface{}) {
	printfTs(msg, *timestampFmtFlag, args...)
}

func printErr(msg string, args ...interface{}) {
	term.Write(term.Escape.Red)
	printf(msg, args...)
	term.Write(term.Escape.Reset)
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"fmt"
	"net/http"
	"strconv"
	"strings"

	"github.com/Shopify/sarama"
	"github.com/funkygao/gafka/cmd/kateway/manager"
	"github.com/funkygao/gafka/cmd/kateway/meta"
	"github.com/funkygao/gafka/sla"
	log "github.com/funkygao/log4go"
	"github.com/julienschmidt/httprouter"
)

func (this *Gateway) statusHandler(w http.ResponseWriter, r *http.Request,
	params httprouter.Params) {
	output := make(map[string]interface{})
	output["options"] = options
	output["loglevel"] = logLevel.String()
	output["pubserver.type"] = this.pubServer.name
	b, _ := json.MarshalIndent(output, "", "    ")
	w.Write(b)
}

func (this *Gateway) clientsHandler(w http.ResponseWriter, r *http.Request,
	params httprouter.Params) {
	b, _ := json.Marshal(this.clientStates.Export())
	w.Write(b)
}

func (this *Gateway) clustersHandler(w http.ResponseWriter, r *http.Request,
	params httprouter.Params) {
	b, _ := json.Marshal(meta.Default.Clusters())
	w.Write(b)
}

// /options/:option/:value
func (this *Gateway) setOptionHandler(w http.ResponseWriter, r *http.Request,
	params httprouter.Params) {
	option := params.ByName("option")
	value := params.ByName("value")
	boolVal := value == "true"

	switch option {
	case "debug":
		options.Debug = boolVal

	case "clients":
		options.EnableClientStats = boolVal
		this.clientStates.Reset()

	case "nometrics":
		options.DisableMetrics = boolVal

	case "ratelimit":
		options.Ratelimit = boolVal

	case "accesslog":
		if options.EnableAccessLog != boolVal {
			// on/off switching
			if boolVal {
				this.accessLogger.Start()
			} else {
				this.accessLogger.Stop()
			}
		}
		options.EnableAccessLog = boolVal

	default:
		log.Warn("invalid option:%s=%s", option, value)

		this.writeBadRequest(w, "invalid option")
		return
	}

	log.Info("set option:%s to %s, %#v", option, value, options)

	w.Write(ResponseOk)
}

// /log/:level
func (this *Gateway) setlogHandler(w http.ResponseWriter, r *http.Request,
	params httprouter.Params) {
	logLevel = toLogLevel(params.ByName("level"))
	for name, filter := range log.Global {
		log.Info("log[%s] level: %s -> %s", name, filter.Level, logLevel)

		filter.Level = logLevel
	}

	w.Write(ResponseOk)
}

func (this *Gateway) resetCounterHandler(w http.ResponseWriter, r *http.Request,
	params httprouter.Params) {
	counterName := params.ByName("name")

	_ = counterName // TODO

	w.Write(ResponseOk)
}

func (this *Gateway) authAdmin(appid, pubkey string) (ok bool) {
	if appid == "_psubAdmin_" && pubkey == "_wandafFan_" { // FIXME
		ok = true
	}
	return
}

// /partitions/:cluster/:appid/:topic/:ver
func (this *Gateway) partitionsHandler(w http.ResponseWriter, r *http.Request,
	params httprouter.Params) {
	topic := params.ByName(UrlParamTopic)
	cluster := params.ByName(UrlParamCluster)
	hisAppid := params.ByName(UrlParamAppid)
	appid := r.Header.Get(HttpHeaderAppid)
	pubkey := r.Header.Get(HttpHeaderPubkey)
	ver := params.ByName(UrlParamVersion)
	if !this.authAdmin(appid, pubkey) {
		log.Warn("suspicous partitions call from %s(%s): {cluster:%s app:%s key:%s topic:%s ver:%s}",
			r.RemoteAddr, getHttpRemoteIp(r), cluster, appid, pubkey, topic, ver)

		this.writeAuthFailure(w, manager.ErrPermDenied)
		return
	}

	zkcluster := meta.Default.ZkCluster(cluster)
	kfk, err := sarama.NewClient(zkcluster.BrokerList(), sarama.NewConfig())
	if err != nil {
		log.Error("cluster[%s] %v", zkcluster.Name(), err)

		this.writeErrorResponse(w, err.Error(), http.StatusInternalServerError)
		return
	}
	defer kfk.Close()

	partitions, err := kfk.Partitions(meta.KafkaTopic(hisAppid, topic, ver))
	if err != nil {
		log.Error("cluster[%s] from %s(%s) {app:%s topic:%s ver:%s} %v",
			zkcluster.Name(), r.RemoteAddr, getHttpRemoteIp(r), hisAppid, topic, ver, err)

		this.writeErrorResponse(w, err.Error(), http.StatusInternalServerError)
		return
	}

	w.Write([]byte(fmt.Sprintf(`{"num": %d}`, len(partitions))))
}

// /topics/:cluster/:appid/:topic/:ver?partitions=1&replicas=2&retention.hours=72&retention.bytes=-1
func (this *Gateway) addTopicHandler(w http.ResponseWriter, r *http.Request,
	params httprouter.Params) {
	topic := params.ByName(UrlParamTopic)
	if !validateTopicName(topic) {
		log.Warn("illegal topic: %s", topic)

		this.writeBadRequest(w, "illegal topic")
		return
	}

	if !this.throttleAddTopic.Pour(getHttpRemoteIp(r), 1) {
		this.writeQuotaExceeded(w)
		return
	}

	cluster := params.ByName(UrlParamCluster)
	hisAppid := params.ByName(UrlParamAppid)
	appid := r.Header.Get(HttpHeaderAppid)
	pubkey := r.Header.Get(HttpHeaderPubkey)
	ver := params.ByName(UrlParamVersion)
	if !this.authAdmin(appid, pubkey) {
		log.Warn("suspicous add topic from %s(%s): {appid:%s, pubkey:%s, cluster:%s, topic:%s, ver:%s}",
			r.RemoteAddr, getHttpRemoteIp(r), appid, pubkey, cluster, topic, ver)

		this.writeAuthFailure(w, manager.ErrPermDenied)
		return
	}

	zkcluster := meta.Default.ZkCluster(cluster)
	info := zkcluster.RegisteredInfo()
	if !info.Public {
		log.Warn("app[%s] adding topic:%s in non-public cluster: %+v", hisAppid, topic, params)

		this.writeBadRequest(w, "invalid cluster")
		return
	}

	ts := sla.DefaultSla()
	query := r.URL.Query()
	partitionsArg := query.Get(sla.SlaKeyPartitions)
	if partitionsArg != "" {
		ts.Partitions, _ = strconv.Atoi(partitionsArg)
	}
	replicasArg := query.Get(sla.SlaKeyReplicas)
	if replicasArg != "" {
		ts.Replicas, _ = strconv.Atoi(replicasArg)
	}
	retentionBytes := query.Get(sla.SlaKeyRetentionBytes)
	if retentionBytes != "" {
		ts.RetentionBytes, _ = strconv.Atoi(retentionBytes)
	}
	ts.ParseRetentionHours(query.Get(sla.SlaKeyRetentionHours))

	log.Info("app[%s] from %s(%s) add topic: {appid:%s, cluster:%s, topic:%s, ver:%s query:%s}",
		appid, r.RemoteAddr, getHttpRemoteIp(r), hisAppid, cluster, topic, ver, query.Encode())

	topic = meta.KafkaTopic(hisAppid, topic, ver)
	lines, err := zkcluster.AddTopic(topic, ts)
	if err != nil {
		log.Error("app[%s] %s add topic: %s", appid, r.RemoteAddr, err.Error())

		this.writeErrorResponse(w, err.Error(), http.StatusInternalServerError)
		return
	}

	ok := false
	for _, l := range lines {
		log.Trace("app[%s] add topic[%s] in cluster %s: %s", appid, topic, cluster, l)

		if strings.Contains(l, "Created topic") {
			ok = true
		}
	}

	if ok {
		w.Write(ResponseOk)
	} else {
		this.writeErrorResponse(w, strings.Join(lines, ";"), http.StatusInternalServerError)
	}
}
<commit_msg>handle the nil zkcluster condition<commit_after>package main

import (
	"encoding/json"
	"fmt"
	"net/http"
	"strconv"
	"strings"

	"github.com/Shopify/sarama"
	"github.com/funkygao/gafka/cmd/kateway/manager"
	"github.com/funkygao/gafka/cmd/kateway/meta"
	"github.com/funkygao/gafka/sla"
	log "github.com/funkygao/log4go"
	"github.com/julienschmidt/httprouter"
)

func (this *Gateway) statusHandler(w http.ResponseWriter, r *http.Request,
	params httprouter.Params) {
	output := make(map[string]interface{})
	output["options"] = options
	output["loglevel"] = logLevel.String()
	output["pubserver.type"] = this.pubServer.name
	b, _ := json.MarshalIndent(output, "", "    ")
	w.Write(b)
}

func (this *Gateway) clientsHandler(w http.ResponseWriter, r *http.Request,
	params httprouter.Params) {
	b, _ := json.Marshal(this.clientStates.Export())
	w.Write(b)
}

func (this *Gateway) clustersHandler(w http.ResponseWriter, r *http.Request,
	params httprouter.Params) {
	b, _ := json.Marshal(meta.Default.Clusters())
	w.Write(b)
}

// /options/:option/:value
func (this *Gateway) setOptionHandler(w http.ResponseWriter, r *http.Request,
	params httprouter.Params) {
	option := params.ByName("option")
	value := params.ByName("value")
	boolVal := value == "true"

	switch option {
	case "debug":
		options.Debug = boolVal

	case "clients":
		options.EnableClientStats = boolVal
		this.clientStates.Reset()

	case "nometrics":
		options.DisableMetrics = boolVal

	case "ratelimit":
		options.Ratelimit = boolVal

	case "accesslog":
		if options.EnableAccessLog != boolVal {
			// on/off switching
			if boolVal {
				this.accessLogger.Start()
			} else {
				this.accessLogger.Stop()
			}
		}
		options.EnableAccessLog = boolVal

	default:
		log.Warn("invalid option:%s=%s", option, value)

		this.writeBadRequest(w, "invalid option")
		return
	}

	log.Info("set option:%s to %s, %#v", option, value, options)

	w.Write(ResponseOk)
}

// /log/:level
func (this *Gateway) setlogHandler(w http.ResponseWriter, r *http.Request,
	params httprouter.Params) {
	logLevel = toLogLevel(params.ByName("level"))
	for name, filter := range log.Global {
		log.Info("log[%s] level: %s -> %s", name, filter.Level, logLevel)

		filter.Level = logLevel
	}

	w.Write(ResponseOk)
}

func (this *Gateway) resetCounterHandler(w http.ResponseWriter, r *http.Request,
	params httprouter.Params) {
	counterName := params.ByName("name")

	_ = counterName // TODO

	w.Write(ResponseOk)
}

func (this *Gateway) authAdmin(appid, pubkey string) (ok bool) {
	if appid == "_psubAdmin_" && pubkey == "_wandafFan_" { // FIXME
		ok = true
	}
	return
}

// /partitions/:cluster/:appid/:topic/:ver
func (this *Gateway) partitionsHandler(w http.ResponseWriter, r *http.Request,
	params httprouter.Params) {
	topic := params.ByName(UrlParamTopic)
	cluster := params.ByName(UrlParamCluster)
	hisAppid := params.ByName(UrlParamAppid)
	appid := r.Header.Get(HttpHeaderAppid)
	pubkey := r.Header.Get(HttpHeaderPubkey)
	ver := params.ByName(UrlParamVersion)
	if !this.authAdmin(appid, pubkey) {
		log.Warn("suspicous partitions call from %s(%s): {cluster:%s app:%s key:%s topic:%s ver:%s}",
			r.RemoteAddr, getHttpRemoteIp(r), cluster, appid, pubkey, topic, ver)

		this.writeAuthFailure(w, manager.ErrPermDenied)
		return
	}

	zkcluster := meta.Default.ZkCluster(cluster)
	if zkcluster == nil {
		log.Error("suspicous partitions call from %s(%s): {cluster:%s app:%s key:%s topic:%s ver:%s} undefined cluster",
			r.RemoteAddr, getHttpRemoteIp(r), cluster, appid, pubkey, topic, ver)

		this.writeBadRequest(w, "undefined cluster")
		return
	}

	kfk, err := sarama.NewClient(zkcluster.BrokerList(), sarama.NewConfig())
	if err != nil {
		log.Error("cluster[%s] %v", zkcluster.Name(), err)

		this.writeErrorResponse(w, err.Error(), http.StatusInternalServerError)
		return
	}
	defer kfk.Close()

	partitions, err := kfk.Partitions(meta.KafkaTopic(hisAppid, topic, ver))
	if err != nil {
		log.Error("cluster[%s] from %s(%s) {app:%s topic:%s ver:%s} %v",
			zkcluster.Name(), r.RemoteAddr, getHttpRemoteIp(r), hisAppid, topic, ver, err)

		this.writeErrorResponse(w, err.Error(), http.StatusInternalServerError)
		return
	}

	w.Write([]byte(fmt.Sprintf(`{"num": %d}`, len(partitions))))
}

// /topics/:cluster/:appid/:topic/:ver?partitions=1&replicas=2&retention.hours=72&retention.bytes=-1
func (this *Gateway) addTopicHandler(w http.ResponseWriter, r *http.Request,
	params httprouter.Params) {
	topic := params.ByName(UrlParamTopic)
	if !validateTopicName(topic) {
		log.Warn("illegal topic: %s", topic)

		this.writeBadRequest(w, "illegal topic")
		return
	}

	if !this.throttleAddTopic.Pour(getHttpRemoteIp(r), 1) {
		this.writeQuotaExceeded(w)
		return
	}

	cluster := params.ByName(UrlParamCluster)
	hisAppid := params.ByName(UrlParamAppid)
	appid := r.Header.Get(HttpHeaderAppid)
	pubkey := r.Header.Get(HttpHeaderPubkey)
	ver := params.ByName(UrlParamVersion)
	if !this.authAdmin(appid, pubkey) {
		log.Warn("suspicous add topic from %s(%s): {appid:%s, pubkey:%s, cluster:%s, topic:%s, ver:%s}",
			r.RemoteAddr, getHttpRemoteIp(r), appid, pubkey, cluster, topic, ver)

		this.writeAuthFailure(w, manager.ErrPermDenied)
		return
	}

	zkcluster := meta.Default.ZkCluster(cluster)
	if zkcluster == nil {
		log.Error("add topic from %s(%s): {appid:%s, pubkey:%s, cluster:%s, topic:%s, ver:%s} undefined cluster",
			r.RemoteAddr, getHttpRemoteIp(r), appid, pubkey, cluster, topic, ver)

		this.writeBadRequest(w, "undefined cluster")
		return
	}

	info := zkcluster.RegisteredInfo()
	if !info.Public {
		log.Warn("app[%s] adding topic:%s in non-public cluster: %+v", hisAppid, topic, params)

		this.writeBadRequest(w, "invalid cluster")
		return
	}

	ts := sla.DefaultSla()
	query := r.URL.Query()
	partitionsArg := query.Get(sla.SlaKeyPartitions)
	if partitionsArg != "" {
		ts.Partitions, _ = strconv.Atoi(partitionsArg)
	}
	replicasArg := query.Get(sla.SlaKeyReplicas)
	if replicasArg != "" {
		ts.Replicas, _ = strconv.Atoi(replicasArg)
	}
	retentionBytes := query.Get(sla.SlaKeyRetentionBytes)
	if retentionBytes != "" {
		ts.RetentionBytes, _ = strconv.Atoi(retentionBytes)
	}
	ts.ParseRetentionHours(query.Get(sla.SlaKeyRetentionHours))

	log.Info("app[%s] from %s(%s) add topic: {appid:%s, cluster:%s, topic:%s, ver:%s query:%s}",
		appid, r.RemoteAddr, getHttpRemoteIp(r), hisAppid, cluster, topic, ver, query.Encode())

	topic = meta.KafkaTopic(hisAppid, topic, ver)
	lines, err := zkcluster.AddTopic(topic, ts)
	if err != nil {
		log.Error("app[%s] %s add topic: %s", appid, r.RemoteAddr, err.Error())

		this.writeErrorResponse(w, err.Error(), http.StatusInternalServerError)
		return
	}

	ok := false
	for _, l := range lines {
		log.Trace("app[%s] add topic[%s] in cluster %s: %s", appid, topic, cluster, l)

		if strings.Contains(l, "Created topic") {
			ok = true
		}
	}

	if ok {
		w.Write(ResponseOk)
	} else {
		this.writeErrorResponse(w, strings.Join(lines, ";"), http.StatusInternalServerError)
	}
}
<|endoftext|>