Skip to content

Commit d1bec1a

Browse files
Version 3.9.0
- added support for user-specified primary IPs (#88, thanks @ItsReddi)
1 parent cc0acad commit d1bec1a

File tree

2 files changed

+129
-8
lines changed

2 files changed

+129
-8
lines changed

README.md

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ You can find sources and pre-compiled binaries [here](https://github.com/JonasPr
1515

1616
```bash
1717
# Download the binary (this example downloads the binary for linux amd64)
18-
$ 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
19-
$ tar -xvf docker-machine-driver-hetzner_3.8.1_linux_amd64.tar.gz
18+
$ 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
19+
$ tar -xvf docker-machine-driver-hetzner_3.9.0_linux_amd64.tar.gz
2020

2121
# Make it executable and copy the binary in a directory accessible with your $PATH
2222
$ chmod +x docker-machine-driver-hetzner
@@ -110,6 +110,7 @@ $ docker-machine create \
110110
- `--hetzner-auto-spread`: Add to a `docker-machine` provided `spread` group (mutually exclusive with `--hetzner-placement-group`)
111111
- `--hetzner-ssh-user`: Change the default SSH-User
112112
- `--hetzner-ssh-port`: Change the default SSH-Port
113+
- `--hetzner-primary-ipv4/6`: Sets an existing primary IP (v4 or v6 respectively) for the server, as documented in [Networking](#networking)
113114

114115
#### Existing SSH keys
115116

@@ -152,8 +153,24 @@ was used during creation.
152153
| `--hetzner-auto-spread` | `HETZNER_AUTO_SPREAD` | false |
153154
| `--hetzner-ssh-user` | `HETZNER_SSH_USER` | root |
154155
| `--hetzner-ssh-port` | `HETZNER_SSH_PORT` | 22 |
156+
| `--hetzner-primary-ipv4` | `HETZNER_PRIMARY_IPV4` | |
157+
| `--hetzner-primary-ipv6` | `HETZNER_PRIMARY_IPV6` | |
155158

156-
**Networking hint:** When disabling all public IPs, `--hetzner-use-private-network` must be given.
159+
#### Networking
160+
161+
Given `--hetzner-primary-ipv4` or `--hetzner-primary-ipv6`, the driver
162+
attempts to set up machine creation with an existing [primary IP](https://docs.hetzner.com/cloud/servers/primary-ips/overview/)
163+
as follows: If the passed argument parses to a valid IP address, the primary IP is resolved via address.
164+
Otherwise, it is resolved in the default Hetzner Cloud API way (i.e. via ID and name as a fallback).
165+
166+
No address family validation is performed, so when specifying an IP address it is the user's responsibility to pass the
167+
appropriate type. This also applies to any given preconditions regarding the state of the address being attached.
168+
169+
If no existing primary IPs are specified and public address creation is not disabled for a given address family, a new
170+
primary IP will be auto-generated by default. Primary IPs created in that fashion will exhibit whatever default behavior
171+
Hetzner assigns them at the given time, so users should take care what retention flags etc. are being set.
172+
173+
When disabling all public IPs, `--hetzner-use-private-network` must be given.
157174
`--hetzner-disable-public` will take care of that, and behaves as if
158175
`--hetzner-disable-public-4 --hetzner-disable-public-6 --hetzner-use-private-network`
159176
were given.

driver.go

Lines changed: 109 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,10 @@ type Driver struct {
4444
UsePrivateNetwork bool
4545
DisablePublic4 bool
4646
DisablePublic6 bool
47+
PrimaryIPv4 string
48+
cachedPrimaryIPv4 *hcloud.PrimaryIP
49+
PrimaryIPv6 string
50+
cachedPrimaryIPv6 *hcloud.PrimaryIP
4751
Firewalls []string
4852
ServerLabels map[string]string
4953
keyLabels map[string]string
@@ -72,6 +76,8 @@ const (
7276
flagUsePrivateNetwork = "hetzner-use-private-network"
7377
flagDisablePublic4 = "hetzner-disable-public-4"
7478
flagDisablePublic6 = "hetzner-disable-public-6"
79+
flagPrimary4 = "hetzner-primary-ipv4"
80+
flagPrimary6 = "hetzner-primary-ipv6"
7581
flagDisablePublic = "hetzner-disable-public"
7682
flagFirewalls = "hetzner-firewalls"
7783
flagAdditionalKeys = "hetzner-additional-key"
@@ -189,6 +195,18 @@ func (d *Driver) GetCreateFlags() []mcnflag.Flag {
189195
Name: flagDisablePublic,
190196
Usage: "Disable public ip (v4 & v6)",
191197
},
198+
mcnflag.StringFlag{
199+
EnvVar: "HETZNER_PRIMARY_IPV4",
200+
Name: flagPrimary4,
201+
Usage: "Existing primary IPv4 address",
202+
Value: "",
203+
},
204+
mcnflag.StringFlag{
205+
EnvVar: "HETZNER_PRIMARY_IPV6",
206+
Name: flagPrimary6,
207+
Usage: "Existing primary IPv6 address",
208+
Value: "",
209+
},
192210
mcnflag.StringSliceFlag{
193211
EnvVar: "HETZNER_FIREWALLS",
194212
Name: flagFirewalls,
@@ -261,6 +279,8 @@ func (d *Driver) setConfigFromFlagsImpl(opts drivers.DriverOptions) error {
261279
d.UsePrivateNetwork = opts.Bool(flagUsePrivateNetwork) || disablePublic
262280
d.DisablePublic4 = opts.Bool(flagDisablePublic4) || disablePublic
263281
d.DisablePublic6 = opts.Bool(flagDisablePublic6) || disablePublic
282+
d.PrimaryIPv4 = opts.String(flagPrimary4)
283+
d.PrimaryIPv6 = opts.String(flagPrimary6)
264284
d.Firewalls = opts.StringSlice(flagFirewalls)
265285
d.AdditionalKeys = opts.StringSlice(flagAdditionalKeys)
266286

@@ -297,6 +317,14 @@ func (d *Driver) setConfigFromFlagsImpl(opts drivers.DriverOptions) error {
297317
flagUsePrivateNetwork, flagDisablePublic)
298318
}
299319

320+
if d.DisablePublic4 && d.PrimaryIPv4 != "" {
321+
return d.flagFailure("--%v and --%v are mutually exclusive", flagPrimary4, flagDisablePublic4)
322+
}
323+
324+
if d.DisablePublic6 && d.PrimaryIPv6 != "" {
325+
return d.flagFailure("--%v and --%v are mutually exclusive", flagPrimary6, flagDisablePublic6)
326+
}
327+
300328
return nil
301329
}
302330

@@ -375,6 +403,14 @@ func (d *Driver) PreCreateCheck() error {
375403
return fmt.Errorf("could not create placement group: %w", err)
376404
}
377405

406+
if _, err := d.getPrimaryIPv4(); err != nil {
407+
return fmt.Errorf("could not resolve primary IPv4: %w", err)
408+
}
409+
410+
if _, err := d.getPrimaryIPv6(); err != nil {
411+
return fmt.Errorf("could not resolve primary IPv6: %w", err)
412+
}
413+
378414
if d.UsePrivateNetwork && len(d.Networks) == 0 {
379415
return errors.Errorf("No private network attached.")
380416
}
@@ -495,11 +531,9 @@ func (d *Driver) makeCreateServerOptions() (*hcloud.ServerCreateOpts, error) {
495531
PlacementGroup: pgrp,
496532
}
497533

498-
if d.DisablePublic4 || d.DisablePublic6 {
499-
srvopts.PublicNet = &hcloud.ServerCreatePublicNet{
500-
EnableIPv4: !d.DisablePublic4,
501-
EnableIPv6: !d.DisablePublic6,
502-
}
534+
err = d.setPublicNetIfRequired(srvopts)
535+
if err != nil {
536+
return nil, err
503537
}
504538

505539
networks, err := d.createNetworks()
@@ -537,6 +571,27 @@ func (d *Driver) makeCreateServerOptions() (*hcloud.ServerCreateOpts, error) {
537571
return &srvopts, nil
538572
}
539573

574+
func (d *Driver) setPublicNetIfRequired(srvopts hcloud.ServerCreateOpts) error {
575+
pip4, err := d.getPrimaryIPv4()
576+
if err != nil {
577+
return err
578+
}
579+
pip6, err := d.getPrimaryIPv6()
580+
if err != nil {
581+
return err
582+
}
583+
584+
if d.DisablePublic4 || d.DisablePublic6 || pip4 != nil || pip6 != nil {
585+
srvopts.PublicNet = &hcloud.ServerCreatePublicNet{
586+
EnableIPv4: !d.DisablePublic4,
587+
EnableIPv6: !d.DisablePublic6,
588+
IPv4: pip4,
589+
IPv6: pip6,
590+
}
591+
}
592+
return nil
593+
}
594+
540595
func (d *Driver) createNetworks() ([]*hcloud.Network, error) {
541596
networks := []*hcloud.Network{}
542597
for _, networkIDorName := range d.Networks {
@@ -1094,3 +1149,52 @@ func (d *Driver) removeEmptyServerPlacementGroup(srv *hcloud.Server) error {
10941149
return nil
10951150
}
10961151
}
1152+
1153+
func (d *Driver) getPrimaryIPv4() (*hcloud.PrimaryIP, error) {
1154+
raw := d.PrimaryIPv4
1155+
if raw == "" {
1156+
return nil, nil
1157+
} else if d.cachedPrimaryIPv4 != nil {
1158+
return d.cachedPrimaryIPv4, nil
1159+
}
1160+
1161+
ip, err := d.resolvePrimaryIP(raw)
1162+
d.cachedPrimaryIPv4 = ip
1163+
return ip, err
1164+
}
1165+
1166+
func (d *Driver) getPrimaryIPv6() (*hcloud.PrimaryIP, error) {
1167+
raw := d.PrimaryIPv6
1168+
if raw == "" {
1169+
return nil, nil
1170+
} else if d.cachedPrimaryIPv6 != nil {
1171+
return d.cachedPrimaryIPv6, nil
1172+
}
1173+
1174+
ip, err := d.resolvePrimaryIP(raw)
1175+
d.cachedPrimaryIPv6 = ip
1176+
return ip, err
1177+
}
1178+
1179+
func (d *Driver) resolvePrimaryIP(raw string) (*hcloud.PrimaryIP, error) {
1180+
client := d.getClient().PrimaryIP
1181+
1182+
var getter func(context.Context, string) (*hcloud.PrimaryIP, *hcloud.Response, error)
1183+
if net.ParseIP(raw) != nil {
1184+
getter = client.GetByIP
1185+
} else {
1186+
getter = client.Get
1187+
}
1188+
1189+
ip, _, err := getter(context.Background(), raw)
1190+
1191+
if err != nil {
1192+
return nil, fmt.Errorf("could not get primary IP: %w", err)
1193+
}
1194+
1195+
if ip != nil {
1196+
return ip, nil
1197+
}
1198+
1199+
return nil, fmt.Errorf("primary IP not found: %v", raw)
1200+
}

0 commit comments

Comments
 (0)