package main import ( "encoding/json" "errors" "fmt" "isle/daemon/network" "os" ) var subCmdHostCreate = subCmd{ name: "create", descr: "Creates a new host in the network, writing its new bootstrap.json to stdout", do: func(ctx subCmdCtx) error { var ( hostName hostNameFlag ip ipFlag ) hostNameF := ctx.flags.VarPF( &hostName, "hostname", "n", "Name of the host to generate bootstrap.json for", ) ctx.flags.VarP(&ip, "ip", "i", "IP of the new host. An available IP will be chosen if none is given.") canCreateHosts := ctx.flags.Bool( "can-create-hosts", false, "The new host should have the ability to create hosts too", ) ctx, err := ctx.withParsedFlags(nil) if err != nil { return fmt.Errorf("parsing flags: %w", err) } if !hostNameF.Changed { return errors.New("--hostname is required") } daemonRPC, err := ctx.newDaemonRPC() if err != nil { return fmt.Errorf("creating daemon RPC client: %w", err) } defer daemonRPC.Close() res, err := daemonRPC.CreateHost( ctx, hostName.V, network.CreateHostOpts{ IP: ip.V, CanCreateHosts: *canCreateHosts, }, ) if err != nil { return fmt.Errorf("calling CreateHost: %w", err) } return json.NewEncoder(os.Stdout).Encode(res) }, } var subCmdHostList = subCmd{ name: "list", descr: "Lists all hosts in the network, and their IPs", do: doWithOutput(func(ctx subCmdCtx) (any, error) { ctx, err := ctx.withParsedFlags(nil) if err != nil { return nil, fmt.Errorf("parsing flags: %w", err) } daemonRPC, err := ctx.newDaemonRPC() if err != nil { return nil, fmt.Errorf("creating daemon RPC client: %w", err) } defer daemonRPC.Close() currBoostrap, err := daemonRPC.GetBootstrap(ctx) if err != nil { return nil, fmt.Errorf("calling GetBootstrap: %w", err) } hosts := currBoostrap.HostsOrdered() type storageAllocationView struct { ID string `yaml:"id"` RPCPort int `yaml:"rpc_port"` S3APIPort int `yaml:"s3_api_port"` } type hostView struct { Name string `yaml:"name"` VPN struct { IP string `yaml:"ip"` PublicAddr string `yaml:"public_addr,omitempty"` } Storage struct { Allocations []storageAllocationView `yaml:"allocations"` } `yaml:",omitempty"` } hostViews := make([]hostView, len(hosts)) for i, host := range hosts { storageAllocViews := make([]storageAllocationView, len(host.Garage.Instances)) for i := range host.Garage.Instances { storageAllocViews[i] = storageAllocationView(host.Garage.Instances[i]) } hostView := hostView{ Name: string(host.Name), } hostView.VPN.IP = host.IP().String() hostView.VPN.PublicAddr = host.Nebula.PublicAddr hostView.Storage.Allocations = storageAllocViews hostViews[i] = hostView } return hostViews, nil }), } var subCmdHostRemove = subCmd{ name: "remove", descr: "Removes a host from the network", do: func(ctx subCmdCtx) error { var ( hostName hostNameFlag ) hostNameF := ctx.flags.VarPF( &hostName, "hostname", "n", "Name of the host to remove", ) ctx, err := ctx.withParsedFlags(nil) if err != nil { return fmt.Errorf("parsing flags: %w", err) } if !hostNameF.Changed { return errors.New("--hostname is required") } daemonRPC, err := ctx.newDaemonRPC() if err != nil { return fmt.Errorf("creating daemon RPC client: %w", err) } defer daemonRPC.Close() if err := daemonRPC.RemoveHost(ctx, hostName.V); err != nil { return fmt.Errorf("calling RemoveHost: %w", err) } return nil }, } var subCmdHost = subCmd{ name: "host", plural: "s", descr: "Sub-commands having to do with configuration of hosts in the network", do: func(ctx subCmdCtx) error { return ctx.doSubCmd( subCmdHostCreate, subCmdHostRemove, subCmdHostList, ) }, }