Skip to content
Commits on Source (1)
  • JonasS's avatar
    Version 3.9.0 · d1bec1ad
    JonasS authored
    - added support for user-specified primary IPs (#88, thanks @ItsReddi)
    d1bec1ad
......@@ -15,8 +15,8 @@ You can find sources and pre-compiled binaries [here](https://github.com/JonasPr
```bash
# Download the binary (this example downloads the binary for linux amd64)
$ wget https://github.com/JonasProgrammer/docker-machine-driver-hetzner/releases/download/3.8.1/docker-machine-driver-hetzner_3.8.1_linux_amd64.tar.gz
$ tar -xvf docker-machine-driver-hetzner_3.8.1_linux_amd64.tar.gz
$ wget https://github.com/JonasProgrammer/docker-machine-driver-hetzner/releases/download/3.9.0/docker-machine-driver-hetzner_3.9.0_linux_amd64.tar.gz
$ tar -xvf docker-machine-driver-hetzner_3.9.0_linux_amd64.tar.gz
# Make it executable and copy the binary in a directory accessible with your $PATH
$ chmod +x docker-machine-driver-hetzner
......@@ -110,6 +110,7 @@ $ docker-machine create \
- `--hetzner-auto-spread`: Add to a `docker-machine` provided `spread` group (mutually exclusive with `--hetzner-placement-group`)
- `--hetzner-ssh-user`: Change the default SSH-User
- `--hetzner-ssh-port`: Change the default SSH-Port
- `--hetzner-primary-ipv4/6`: Sets an existing primary IP (v4 or v6 respectively) for the server, as documented in [Networking](#networking)
#### Existing SSH keys
......@@ -152,8 +153,24 @@ was used during creation.
| `--hetzner-auto-spread` | `HETZNER_AUTO_SPREAD` | false |
| `--hetzner-ssh-user` | `HETZNER_SSH_USER` | root |
| `--hetzner-ssh-port` | `HETZNER_SSH_PORT` | 22 |
| `--hetzner-primary-ipv4` | `HETZNER_PRIMARY_IPV4` | |
| `--hetzner-primary-ipv6` | `HETZNER_PRIMARY_IPV6` | |
**Networking hint:** When disabling all public IPs, `--hetzner-use-private-network` must be given.
#### Networking
Given `--hetzner-primary-ipv4` or `--hetzner-primary-ipv6`, the driver
attempts to set up machine creation with an existing [primary IP](https://docs.hetzner.com/cloud/servers/primary-ips/overview/)
as follows: If the passed argument parses to a valid IP address, the primary IP is resolved via address.
Otherwise, it is resolved in the default Hetzner Cloud API way (i.e. via ID and name as a fallback).
No address family validation is performed, so when specifying an IP address it is the user's responsibility to pass the
appropriate type. This also applies to any given preconditions regarding the state of the address being attached.
If no existing primary IPs are specified and public address creation is not disabled for a given address family, a new
primary IP will be auto-generated by default. Primary IPs created in that fashion will exhibit whatever default behavior
Hetzner assigns them at the given time, so users should take care what retention flags etc. are being set.
When disabling all public IPs, `--hetzner-use-private-network` must be given.
`--hetzner-disable-public` will take care of that, and behaves as if
`--hetzner-disable-public-4 --hetzner-disable-public-6 --hetzner-use-private-network`
were given.
......
......@@ -44,6 +44,10 @@ type Driver struct {
UsePrivateNetwork bool
DisablePublic4 bool
DisablePublic6 bool
PrimaryIPv4 string
cachedPrimaryIPv4 *hcloud.PrimaryIP
PrimaryIPv6 string
cachedPrimaryIPv6 *hcloud.PrimaryIP
Firewalls []string
ServerLabels map[string]string
keyLabels map[string]string
......@@ -72,6 +76,8 @@ const (
flagUsePrivateNetwork = "hetzner-use-private-network"
flagDisablePublic4 = "hetzner-disable-public-4"
flagDisablePublic6 = "hetzner-disable-public-6"
flagPrimary4 = "hetzner-primary-ipv4"
flagPrimary6 = "hetzner-primary-ipv6"
flagDisablePublic = "hetzner-disable-public"
flagFirewalls = "hetzner-firewalls"
flagAdditionalKeys = "hetzner-additional-key"
......@@ -189,6 +195,18 @@ func (d *Driver) GetCreateFlags() []mcnflag.Flag {
Name: flagDisablePublic,
Usage: "Disable public ip (v4 & v6)",
},
mcnflag.StringFlag{
EnvVar: "HETZNER_PRIMARY_IPV4",
Name: flagPrimary4,
Usage: "Existing primary IPv4 address",
Value: "",
},
mcnflag.StringFlag{
EnvVar: "HETZNER_PRIMARY_IPV6",
Name: flagPrimary6,
Usage: "Existing primary IPv6 address",
Value: "",
},
mcnflag.StringSliceFlag{
EnvVar: "HETZNER_FIREWALLS",
Name: flagFirewalls,
......@@ -261,6 +279,8 @@ func (d *Driver) setConfigFromFlagsImpl(opts drivers.DriverOptions) error {
d.UsePrivateNetwork = opts.Bool(flagUsePrivateNetwork) || disablePublic
d.DisablePublic4 = opts.Bool(flagDisablePublic4) || disablePublic
d.DisablePublic6 = opts.Bool(flagDisablePublic6) || disablePublic
d.PrimaryIPv4 = opts.String(flagPrimary4)
d.PrimaryIPv6 = opts.String(flagPrimary6)
d.Firewalls = opts.StringSlice(flagFirewalls)
d.AdditionalKeys = opts.StringSlice(flagAdditionalKeys)
......@@ -297,6 +317,14 @@ func (d *Driver) setConfigFromFlagsImpl(opts drivers.DriverOptions) error {
flagUsePrivateNetwork, flagDisablePublic)
}
if d.DisablePublic4 && d.PrimaryIPv4 != "" {
return d.flagFailure("--%v and --%v are mutually exclusive", flagPrimary4, flagDisablePublic4)
}
if d.DisablePublic6 && d.PrimaryIPv6 != "" {
return d.flagFailure("--%v and --%v are mutually exclusive", flagPrimary6, flagDisablePublic6)
}
return nil
}
......@@ -375,6 +403,14 @@ func (d *Driver) PreCreateCheck() error {
return fmt.Errorf("could not create placement group: %w", err)
}
if _, err := d.getPrimaryIPv4(); err != nil {
return fmt.Errorf("could not resolve primary IPv4: %w", err)
}
if _, err := d.getPrimaryIPv6(); err != nil {
return fmt.Errorf("could not resolve primary IPv6: %w", err)
}
if d.UsePrivateNetwork && len(d.Networks) == 0 {
return errors.Errorf("No private network attached.")
}
......@@ -495,11 +531,9 @@ func (d *Driver) makeCreateServerOptions() (*hcloud.ServerCreateOpts, error) {
PlacementGroup: pgrp,
}
if d.DisablePublic4 || d.DisablePublic6 {
srvopts.PublicNet = &hcloud.ServerCreatePublicNet{
EnableIPv4: !d.DisablePublic4,
EnableIPv6: !d.DisablePublic6,
}
err = d.setPublicNetIfRequired(srvopts)
if err != nil {
return nil, err
}
networks, err := d.createNetworks()
......@@ -537,6 +571,27 @@ func (d *Driver) makeCreateServerOptions() (*hcloud.ServerCreateOpts, error) {
return &srvopts, nil
}
func (d *Driver) setPublicNetIfRequired(srvopts hcloud.ServerCreateOpts) error {
pip4, err := d.getPrimaryIPv4()
if err != nil {
return err
}
pip6, err := d.getPrimaryIPv6()
if err != nil {
return err
}
if d.DisablePublic4 || d.DisablePublic6 || pip4 != nil || pip6 != nil {
srvopts.PublicNet = &hcloud.ServerCreatePublicNet{
EnableIPv4: !d.DisablePublic4,
EnableIPv6: !d.DisablePublic6,
IPv4: pip4,
IPv6: pip6,
}
}
return nil
}
func (d *Driver) createNetworks() ([]*hcloud.Network, error) {
networks := []*hcloud.Network{}
for _, networkIDorName := range d.Networks {
......@@ -1094,3 +1149,52 @@ func (d *Driver) removeEmptyServerPlacementGroup(srv *hcloud.Server) error {
return nil
}
}
func (d *Driver) getPrimaryIPv4() (*hcloud.PrimaryIP, error) {
raw := d.PrimaryIPv4
if raw == "" {
return nil, nil
} else if d.cachedPrimaryIPv4 != nil {
return d.cachedPrimaryIPv4, nil
}
ip, err := d.resolvePrimaryIP(raw)
d.cachedPrimaryIPv4 = ip
return ip, err
}
func (d *Driver) getPrimaryIPv6() (*hcloud.PrimaryIP, error) {
raw := d.PrimaryIPv6
if raw == "" {
return nil, nil
} else if d.cachedPrimaryIPv6 != nil {
return d.cachedPrimaryIPv6, nil
}
ip, err := d.resolvePrimaryIP(raw)
d.cachedPrimaryIPv6 = ip
return ip, err
}
func (d *Driver) resolvePrimaryIP(raw string) (*hcloud.PrimaryIP, error) {
client := d.getClient().PrimaryIP
var getter func(context.Context, string) (*hcloud.PrimaryIP, *hcloud.Response, error)
if net.ParseIP(raw) != nil {
getter = client.GetByIP
} else {
getter = client.Get
}
ip, _, err := getter(context.Background(), raw)
if err != nil {
return nil, fmt.Errorf("could not get primary IP: %w", err)
}
if ip != nil {
return ip, nil
}
return nil, fmt.Errorf("primary IP not found: %v", raw)
}