| package devices |
| |
| import ( |
| "context" |
| "encoding/json" |
| "errors" |
| "fmt" |
| "io/ioutil" |
| |
| "golang.org/x/crypto/ssh" |
| ) |
| |
| // DeviceConfig contains the static properties of a target device. |
| type DeviceConfig struct { |
| // Type tells us what kind of machine this is. |
| Type string `json:"type"` |
| |
| // Network is the network properties of the target. |
| Network *NetworkProperties `json:"network"` |
| |
| // Power is the attached power management configuration. |
| Power *PowerClient `json:"power,omitempty"` |
| |
| // SSHKeys are the default system keys to be used with the device. |
| SSHKeys []string `json:"keys,omitempty"` |
| |
| // Serial is the path to the device file for serial i/o. |
| Serial string `json:"serial,omitempty"` |
| } |
| |
| // NetworkProperties are the static network properties of a target. |
| type NetworkProperties struct { |
| // Nodename is the hostname of the device that we want to boot on. |
| Nodename string `json:"nodename"` |
| |
| // IPv4Addr is the IPv4 address, if statically given. If not provided, it may be |
| // resolved via the netstack's MDNS server. |
| IPv4Addr string `json:"ipv4"` |
| |
| // Mac is the mac address of the device |
| Mac string `json:"mac"` |
| |
| // The network interface used to communicate with this device. |
| Interface string `json:"interface"` |
| } |
| |
| // PowerClient represents a power management configuration for a particular device. |
| type PowerClient struct { |
| // Type is the type of manager to use. |
| Type string `json:"type"` |
| |
| // Host is the network hostname of the manager |
| Host string `json:"host"` |
| |
| // Username is the username used to log in to the manager. |
| Username string `json:"username"` |
| |
| // Password is the password used to log in to the manager.. |
| Password string `json:"password"` |
| |
| // PDUIp is the IP address of the pdu |
| PDUIp string `json:"pduIp"` |
| |
| // PDUPort is the port the PDU uses to connect to this device. |
| PDUPort string `json:"pduPort"` |
| } |
| |
| // Device represents a generic device in our infrastructure. |
| // TODO(rudymathu) This interface should eventually be pared down to: |
| // State() |
| // Nodename() |
| // Transition() |
| type Device interface { |
| State() DeviceState |
| Nodename() string |
| Mac() string |
| Interface() string |
| HasSerial() bool |
| Transition(context.Context) error |
| ToTaskState(context.Context) error |
| Powercycle(context.Context) error |
| SoftReboot(context.Context, string, string) error |
| SendSerialCommand(context.Context, string) error |
| ReadSerialData(context.Context, []byte) error |
| } |
| |
| // LoadDeviceConfigs unmarshalls a slice of device configs from a given file. |
| func LoadDeviceConfigs(path string) ([]DeviceConfig, error) { |
| data, err := ioutil.ReadFile(path) |
| if err != nil { |
| return nil, fmt.Errorf("failed to read device properties file %q", path) |
| } |
| |
| var configs []DeviceConfig |
| if err := json.Unmarshal(data, &configs); err != nil { |
| return nil, fmt.Errorf("failed to unmarshal configs: %v", err) |
| } |
| return configs, nil |
| } |
| |
| // CreateDevices loads configs from the given file and creates an appropriate Device for each one. |
| func CreateDevices(ctx context.Context, deviceConfigs []DeviceConfig, bootserverCmdStub []string) ([]Device, error) { |
| var devices []Device |
| for _, deviceConfig := range deviceConfigs { |
| var device Device |
| var err error |
| switch deviceConfig.Type { |
| case "Intel NUC Kit NUC7i5DNHE": |
| device, err = NewNuc(deviceConfig, bootserverCmdStub) |
| case "Khadas Vim2 Max": |
| device, err = NewVim(deviceConfig, bootserverCmdStub) |
| case "Astro": |
| fallthrough |
| case "Sherlock": |
| fallthrough |
| case "Nelson": |
| device, err = NewArmCDCether(deviceConfig, bootserverCmdStub) |
| } |
| if err != nil { |
| return nil, err |
| } |
| devices = append(devices, device) |
| } |
| return devices, nil |
| } |
| |
| // parseOutSigners parses out ssh signers from the given key files. |
| func parseOutSigners(keyPaths []string) ([]ssh.Signer, error) { |
| if len(keyPaths) == 0 { |
| return nil, errors.New("must supply SSH keys in the config") |
| } |
| var keys [][]byte |
| for _, keyPath := range keyPaths { |
| p, err := ioutil.ReadFile(keyPath) |
| if err != nil { |
| return nil, fmt.Errorf("could not read SSH key file %q: %v", keyPath, err) |
| } |
| keys = append(keys, p) |
| } |
| |
| var signers []ssh.Signer |
| for _, p := range keys { |
| signer, err := ssh.ParsePrivateKey(p) |
| if err != nil { |
| return nil, err |
| } |
| signers = append(signers, signer) |
| } |
| return signers, nil |
| } |