package entrypoint import ( "cryptic-net/bootstrap" "errors" "fmt" "net" "os" "regexp" "sort" "gopkg.in/yaml.v3" ) var hostNameRegexp = regexp.MustCompile(`^[a-z][a-z0-9\-]*$`) func validateHostName(name string) error { if !hostNameRegexp.MatchString(name) { return errors.New("a host's name must start with a letter and only contain letters, numbers, and dashes") } return nil } var subCmdHostsAdd = subCmd{ name: "add", descr: "Adds a host to the network", checkLock: true, do: func(subCmdCtx subCmdCtx) error { flags := subCmdCtx.flagSet(false) name := flags.StringP( "name", "n", "", "Name of the new host", ) ip := flags.StringP( "ip", "i", "", "IP of the new host", ) if err := flags.Parse(subCmdCtx.args); err != nil { return fmt.Errorf("parsing flags: %w", err) } if *name == "" || *ip == "" { return errors.New("--name and --ip are required") } if err := validateHostName(*name); err != nil { return fmt.Errorf("invalid hostname %q: %w", *name, err) } if net.ParseIP(*ip) == nil { return fmt.Errorf("invalid ip %q", *ip) } // TODO validate that the IP is in the correct CIDR env := subCmdCtx.env client, err := env.Bootstrap.GlobalBucketS3APIClient() if err != nil { return fmt.Errorf("creating client for global bucket: %w", err) } host := bootstrap.Host{ Name: *name, Nebula: bootstrap.NebulaHost{ IP: *ip, }, } return bootstrap.PutGarageBoostrapHost(env.Context, client, host) }, } var subCmdHostsList = subCmd{ name: "list", descr: "Lists all hosts in the network, and their IPs", checkLock: true, do: func(subCmdCtx subCmdCtx) error { env := subCmdCtx.env client, err := env.Bootstrap.GlobalBucketS3APIClient() if err != nil { return fmt.Errorf("creating client for global bucket: %w", err) } hostsMap, err := bootstrap.GetGarageBootstrapHosts(env.Context, client) if err != nil { return fmt.Errorf("retrieving hosts from garage: %w", err) } hosts := make([]bootstrap.Host, 0, len(hostsMap)) for _, host := range hostsMap { hosts = append(hosts, host) } sort.Slice(hosts, func(i, j int) bool { return hosts[i].Name < hosts[j].Name }) return yaml.NewEncoder(os.Stdout).Encode(hosts) }, } var subCmdHostsDelete = subCmd{ name: "delete", descr: "Deletes a host from the network", checkLock: true, do: func(subCmdCtx subCmdCtx) error { flags := subCmdCtx.flagSet(false) name := flags.StringP( "name", "n", "", "Name of the host to delete", ) if err := flags.Parse(subCmdCtx.args); err != nil { return fmt.Errorf("parsing flags: %w", err) } if *name == "" { return errors.New("--name is required") } env := subCmdCtx.env client, err := env.Bootstrap.GlobalBucketS3APIClient() if err != nil { return fmt.Errorf("creating client for global bucket: %w", err) } return bootstrap.RemoveGarageBootstrapHost(env.Context, client, *name) }, } 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( subCmdHostsAdd, subCmdHostsDelete, subCmdHostsList, ) }, }