package config import ( "fmt" "regexp" "strings" ) // StaticKind is the kind of hosts defined under hosts: instead of NetBox. const StaticKind = "static" // StaticHost is a host that is not in NetBox, defined in the config. type StaticHost struct { Name string `mapstructure:"name"` Address string `mapstructure:"address"` // IP address or DNS name User string `mapstructure:"user"` Port int `mapstructure:"port"` Tags []string `mapstructure:"tags"` SSH SSHOptions `mapstructure:"ssh"` } var staticHostName = regexp.MustCompile(`^[A-Za-z0-9][A-Za-z0-9._-]*$`) // ValidateHosts keeps static host names usable as selectors and addresses // safe to pass to ssh. func (c *Config) ValidateHosts() error { seen := make(map[string]bool) for i, host := range c.Hosts { scope := fmt.Sprintf("hosts[%d]", i) if !staticHostName.MatchString(host.Name) { return fmt.Errorf("%s: name %q must start with a letter or digit and contain only letters, digits, dots, underscores, or hyphens", scope, host.Name) } name := strings.ToLower(host.Name) if seen[name] { return fmt.Errorf("%s: duplicate host name %q", scope, host.Name) } seen[name] = true if host.Address == "" || strings.HasPrefix(host.Address, "-") || strings.ContainsAny(host.Address, " \t\n\r/@") { return fmt.Errorf("%s: address %q must be an IP address or hostname", scope, host.Address) } if host.Port < 0 || host.Port > 65535 { return fmt.Errorf("%s: port must be between 1 and 65535", scope) } if strings.HasPrefix(host.User, "-") || strings.ContainsAny(host.User, " \t\n\r@") { return fmt.Errorf("%s: invalid user %q", scope, host.User) } if err := host.SSH.Validate(scope + ".ssh"); err != nil { return err } } return nil } // StaticHost returns the static host with the given name, or nil. func (c *Config) StaticHost(name string) *StaticHost { for i := range c.Hosts { if strings.EqualFold(c.Hosts[i].Name, name) { return &c.Hosts[i] } } return nil }