isle/go/cmd/entrypoint/hosts.go

157 lines
3.3 KiB
Go

package main
import (
"errors"
"fmt"
"isle/bootstrap"
"isle/daemon"
"isle/jsonutil"
"isle/nebula"
"net/netip"
"os"
"sort"
)
var subCmdHostsCreate = subCmd{
name: "create",
descr: "Creates a new host in the network, writing its new bootstrap.json to stdout",
do: func(subCmdCtx subCmdCtx) error {
var (
flags = subCmdCtx.flagSet(false)
hostName nebula.HostName
ip netip.Addr
)
hostNameF := flags.VarPF(
textUnmarshalerFlag{&hostName},
"hostname", "h",
"Name of the host to generate bootstrap.json for",
)
ipF := flags.VarPF(
textUnmarshalerFlag{&ip}, "ip", "i", "IP of the new host",
)
adminPath := flags.StringP(
"admin-path", "a", "",
`Path to admin.json file. If the given path is "-" then stdin is used.`,
)
if err := flags.Parse(subCmdCtx.args); err != nil {
return fmt.Errorf("parsing flags: %w", err)
}
if !hostNameF.Changed ||
!ipF.Changed ||
*adminPath == "" {
return errors.New("--hostname, --ip, and --admin-path are required")
}
adm, err := readAdmin(*adminPath)
if err != nil {
return fmt.Errorf("reading admin.json with --admin-path of %q: %w", *adminPath, err)
}
var res daemon.CreateHostResult
err = subCmdCtx.daemonRCPClient.Call(
subCmdCtx.ctx,
&res,
"CreateHost",
daemon.CreateHostRequest{
CASigningPrivateKey: adm.Nebula.CACredentials.SigningPrivateKey,
HostName: hostName,
IP: ip,
},
)
if err != nil {
return fmt.Errorf("calling CreateHost: %w", err)
}
return res.HostBootstrap.WriteTo(os.Stdout)
},
}
var subCmdHostsList = subCmd{
name: "list",
descr: "Lists all hosts in the network, and their IPs",
do: func(subCmdCtx subCmdCtx) error {
hostsRes, err := subCmdCtx.getHosts()
if err != nil {
return fmt.Errorf("calling GetHosts: %w", err)
}
type host struct {
Name string
VPN struct {
IP string
}
Storage bootstrap.GarageHost `json:",omitempty"`
}
hosts := make([]host, 0, len(hostsRes.Hosts))
for _, h := range hostsRes.Hosts {
host := host{
Name: string(h.Name),
Storage: h.Garage,
}
host.VPN.IP = h.IP().String()
hosts = append(hosts, host)
}
sort.Slice(hosts, func(i, j int) bool { return hosts[i].Name < hosts[j].Name })
return jsonutil.WriteIndented(os.Stdout, hosts)
},
}
var subCmdHostsRemove = subCmd{
name: "remove",
descr: "Removes a host from the network",
do: func(subCmdCtx subCmdCtx) error {
var (
flags = subCmdCtx.flagSet(false)
hostName nebula.HostName
)
hostNameF := flags.VarPF(
textUnmarshalerFlag{&hostName},
"hostname", "h",
"Name of the host to remove",
)
if err := flags.Parse(subCmdCtx.args); err != nil {
return fmt.Errorf("parsing flags: %w", err)
}
if !hostNameF.Changed {
return errors.New("--hostname is required")
}
err := subCmdCtx.daemonRCPClient.Call(
subCmdCtx.ctx, nil, "RemoveHost", daemon.RemoveHostRequest{
HostName: hostName,
},
)
if err != nil {
return fmt.Errorf("calling RemoveHost: %w", err)
}
return nil
},
}
var subCmdHosts = subCmd{
name: "hosts",
descr: "Sub-commands having to do with configuration of hosts in the network",
do: func(subCmdCtx subCmdCtx) error {
return subCmdCtx.doSubCmd(
subCmdHostsCreate,
subCmdHostsRemove,
subCmdHostsList,
)
},
}