[botanist] Add IPv4 and ssh key target getters

-This sets the stage for the following change in which `botanist run` is
moved over to using DeviceTarget and is made to use this addition to the
API.
-This change also makes a modification to the schema of the botanist
config, adding a a `network` substruct.

Change-Id: Icb7e797fa4795e84d3f8bf832160bc6a09e5bd01
diff --git a/botanist/target/device.go b/botanist/target/device.go
index 5df0ae3..ac446c7 100644
--- a/botanist/target/device.go
+++ b/botanist/target/device.go
@@ -22,10 +22,15 @@
 	"golang.org/x/crypto/ssh"
 )
 
+const (
+	// The duration we allow for the netstack to come up when booting.
+	netstackTimeout = 90 * time.Second
+)
+
 // DeviceConfig contains the static properties of a target device.
 type DeviceConfig struct {
-	// Nodename is the hostname of the device that we want to boot on.
-	Nodename string `json:"nodename"`
+	// Network is the network properties of the target.
+	Network NetworkProperties `json:"network"`
 
 	// Power is the attached power management configuration.
 	Power *power.Client `json:"power,omitempty"`
@@ -34,6 +39,16 @@
 	SSHKeys []string `json:"keys,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"`
+}
+
 // DeviceOptions represents lifecycle options for a target.
 type DeviceOptions struct {
 	// Netboot gives whether to netboot or pave. Netboot here is being used in the
@@ -92,7 +107,7 @@
 
 // Nodename returns the name of the node.
 func (t *DeviceTarget) Nodename() string {
-	return t.config.Nodename
+	return t.config.Network.Nodename
 }
 
 // IPv6 returns the link-local IPv6 address of the node.
@@ -101,6 +116,21 @@
 	return addr, err
 }
 
+// IPv4Addr returns the IPv4 address of the node. If not provided in the config, then it
+// will be resolved against the target-side MDNS server.
+func (t *DeviceTarget) IPv4Addr() (net.IP, error) {
+	if t.config.Network.IPv4Addr != "" {
+		return net.ParseIP(t.config.Network.IPv4Addr), nil
+	}
+	addr, err := botanist.ResolveIPv4(context.Background(), t.Nodename(), netstackTimeout)
+	return addr, err
+}
+
+// SSHKey returns the private SSH key path associated with the authorized key to be paved.
+func (t *DeviceTarget) SSHKey() string {
+	return t.config.SSHKeys[0]
+}
+
 // Start starts the device target.
 func (t *DeviceTarget) Start(ctx context.Context, images build.Images, args []string) error {
 	if t.opts.Fastboot != "" {
diff --git a/cmd/botanist/run.go b/cmd/botanist/run.go
index de36f70..cd111eb 100644
--- a/cmd/botanist/run.go
+++ b/cmd/botanist/run.go
@@ -254,10 +254,10 @@
 
 	if config.Power != nil {
 		defer func() {
-			logger.Debugf(ctx, "rebooting the node %q\n", config.Nodename)
+			logger.Debugf(ctx, "rebooting the node %q\n", config.Network.Nodename)
 
-			if err := config.Power.RebootDevice(signers, config.Nodename); err != nil {
-				logger.Errorf(ctx, "failed to reboot %q: %v\n", config.Nodename, err)
+			if err := config.Power.RebootDevice(signers, config.Network.Nodename); err != nil {
+				logger.Errorf(ctx, "failed to reboot %q: %v\n", config.Network.Nodename, err)
 			}
 		}()
 	}
@@ -285,7 +285,7 @@
 				return
 			}
 		}
-		errs <- r.runCmd(ctx, imgs, config.Nodename, args, config.SSHKeys[0], signers, syslog)
+		errs <- r.runCmd(ctx, imgs, config.Network.Nodename, args, config.SSHKeys[0], signers, syslog)
 	}()
 
 	select {