blob: 2d51ea223c453c0d72ff86e10096e517d46cdd13 [file] [log] [blame]
// The censustaker executable combines data from multiple sources to generate a list of devices
// which are attached to a given Ubiquiti EdgeSwitch. The switch can only tell us which mac
// addresses are attached to which ports, so we need another source of data to give us a list of
// hostnames and ip addresses to be able to generate the mapping of hostname to port number needed
// by powercycle.
package main
import (
"context"
"encoding/json"
"flag"
"fmt"
"os"
"regexp"
"go.skia.org/infra/go/skerr"
"go.skia.org/infra/skolo/go/powercycle"
)
type poeDevice struct {
Hostname string
POEPort int
MACAddress string
}
// nameAddressGetter abstracts the logic to collect all information about the machines except
// for which EdgeSwitch ports they are attached to.
type nameAddressGetter interface {
// GetDeviceNamesAddresses returns a []poeDevice with the names and mac addresses filled out.
GetDeviceNamesAddresses(context.Context) ([]poeDevice, error)
}
// addressPortGetter abstracts the logic to collect the EdgeSwitch ports to which our devices
// are connected.
type addressPortGetter interface {
// GetDevicePortsAddresses returns a []poeDevice with the mac addresses and ports filled out.
GetDevicePortsAddresses(context.Context) ([]poeDevice, error)
}
func combineSources(ctx context.Context, names nameAddressGetter, ports addressPortGetter) ([]poeDevice, error) {
nameList, err := names.GetDeviceNamesAddresses(ctx)
if err != nil {
return nil, skerr.Wrapf(err, "fetching device names and mac addresses")
}
portList, err := ports.GetDevicePortsAddresses(ctx)
if err != nil {
return nil, skerr.Wrapf(err, "fetching device ports and mac addresses")
}
const sentinelPort = -1
byMacAddress := map[string]poeDevice{}
for _, b := range nameList {
if b.MACAddress != "" {
b.POEPort = sentinelPort
byMacAddress[b.MACAddress] = b
}
}
for _, b := range portList {
if _, ok := byMacAddress[b.MACAddress]; ok && b.MACAddress != "" {
a := byMacAddress[b.MACAddress]
a.POEPort = b.POEPort
byMacAddress[b.MACAddress] = a
}
}
var devices []poeDevice
for _, b := range byMacAddress {
if b.POEPort != sentinelPort {
devices = append(devices, b)
}
}
return devices, nil
}
func makeConfig(ctx context.Context, address, user, password string, hostnameMatcher *regexp.Regexp) (powercycle.EdgeSwitchConfig, error) {
output := powercycle.EdgeSwitchConfig{
Address: address,
User: user,
Password: "", // leave this blank so as not to leak it
DevPortMap: map[powercycle.DeviceID]int{},
}
arp := newArpNameGetter()
edgeswitch := newSwitchPortGetter(address, user, password)
devices, err := combineSources(ctx, arp, edgeswitch)
if err != nil {
return output, skerr.Wrap(err)
}
for _, device := range devices {
if hostnameMatcher.MatchString(device.Hostname) {
output.DevPortMap[powercycle.DeviceID(device.Hostname)] = device.POEPort
}
}
return output, nil
}
func main() {
var (
switchAddress = flag.String("switch_address", "", "The IP address of the switch to pull the port numbers from.")
switchUser = flag.String("switch_user", "power", "Username of the switch")
switchPassword = flag.String("switch_password", "", "password for the switch user")
hostnameRegex = flag.String("hostname_regex", "rpi", "Regex to match hostnames for")
)
flag.Parse()
ctx := context.Background()
out, err := makeConfig(ctx, *switchAddress, *switchUser, *switchPassword, regexp.MustCompile(*hostnameRegex))
if err != nil {
fmt.Printf("Error: %s\n", err)
os.Exit(1)
}
b, err := json.MarshalIndent(out, "", " ")
if err != nil {
fmt.Printf("Error making JSON: %s\n", err)
os.Exit(2)
}
fmt.Println(string(b))
}