go/{mirko,statusz}: better status, kubernetes client

Change-Id: I66753a79eaf36529aee508d2b7782aab00de1498
diff --git a/go/mirko/mirko.go b/go/mirko/mirko.go
index 01766db..78dbda1 100644
--- a/go/mirko/mirko.go
+++ b/go/mirko/mirko.go
@@ -8,14 +8,18 @@
 	"net/http"
 	"os"
 	"os/signal"
+	"sort"
+	"strings"
 	"time"
 
-	"code.hackerspace.pl/hscloud/go/pki"
-	"code.hackerspace.pl/hscloud/go/statusz"
 	"github.com/golang/glog"
 	"golang.org/x/net/trace"
 	"google.golang.org/grpc"
 	"google.golang.org/grpc/reflection"
+	"k8s.io/client-go/kubernetes"
+
+	"code.hackerspace.pl/hscloud/go/pki"
+	"code.hackerspace.pl/hscloud/go/statusz"
 )
 
 var (
@@ -38,6 +42,8 @@
 	httpServer *http.Server
 	httpMux    *http.ServeMux
 
+	kubernetesCS *kubernetes.Clientset
+
 	ctx    context.Context
 	cancel context.CancelFunc
 }
@@ -113,6 +119,12 @@
 		http.Redirect(w, r, "/debug/status", http.StatusSeeOther)
 	})
 
+	m.kubernetesConnect()
+
+	debugParts := strings.Split(flagDebugAddress, ":")
+	debugPort := debugParts[len(debugParts)-1]
+	statusz.PublicAddress = fmt.Sprintf("http://%s:%s/", m.Address().String(), debugPort)
+
 	m.httpListen = httpLis
 	m.httpServer = &http.Server{
 		Addr:    flagDebugAddress,
@@ -196,3 +208,98 @@
 		return err
 	}
 }
+
+// Address returns a linkable address where this service is running, sans port.
+// If running within kubernetes, this will return the pod IP.
+// Otherwise, this will guess the main, 'external' IP address of the machine it's running on.
+// On failures, returns loopback address.
+func (m *Mirko) Address() net.IP {
+	// If we're not running in Kubernetes and binding to 127.0.0.1, return loopback.
+	if m.kubernetesCS == nil && strings.HasPrefix(flagListenAddress, "127.0.0.1:") {
+		return net.ParseIP("127.0.0.1")
+	}
+
+	ifaces, err := net.Interfaces()
+	if err != nil {
+		glog.Errorf("net.Interface(): %v", err)
+		return net.ParseIP("127.0.0.1")
+	}
+
+	addrmap := make(map[string]net.IP)
+
+	for _, iface := range ifaces {
+		addrs, err := iface.Addrs()
+		if err != nil {
+			glog.Errorf("iface(%q).Addrs(): %v", iface.Name, err)
+			continue
+		}
+
+		for _, addr := range addrs {
+			var ip net.IP
+			switch v := addr.(type) {
+			case *net.IPNet:
+				ip = v.IP
+			case *net.IPAddr:
+				ip = v.IP
+			default:
+				continue
+			}
+
+			if strings.HasPrefix(ip.String(), "fe80:") {
+				continue
+			}
+			addrmap[iface.Name] = ip
+		}
+	}
+
+	if m.kubernetesCS != nil {
+		addr, ok := addrmap["eth0"]
+		if !ok {
+			glog.Errorf("Running on Kubernetes but no eth0! Available interfaces: %v", addrmap)
+			return net.ParseIP("127.0.0.1")
+		}
+
+		return addr
+	}
+
+	if len(addrmap) == 0 {
+		glog.Errorf("No interfaces found!")
+		return net.ParseIP("127.0.0.1")
+	}
+
+	// Heuristics ahoy!
+	prioritized := []*ifaceWithPriority{}
+	for iface, addr := range addrmap {
+		prio := &ifaceWithPriority{
+			iface: iface,
+			addr:  addr,
+		}
+		switch {
+		case strings.HasPrefix(iface, "lo"):
+			prio.priority = -10
+		case strings.HasPrefix(iface, "tap"):
+			prio.priority = -5
+		case strings.HasPrefix(iface, "tun"):
+			prio.priority = -5
+		case strings.HasPrefix(iface, "veth"):
+			prio.priority = 5
+		case strings.HasPrefix(iface, "wl"):
+			prio.priority = 5
+		case strings.HasPrefix(iface, "enp"):
+			prio.priority = 10
+		case strings.HasPrefix(iface, "eth"):
+			prio.priority = 10
+		}
+
+		prioritized = append(prioritized, prio)
+	}
+
+	sort.Slice(prioritized, func(i, j int) bool { return prioritized[i].priority > prioritized[j].priority })
+	return prioritized[0].addr
+}
+
+type ifaceWithPriority struct {
+	iface    string
+	addr     net.IP
+	priority int
+}