Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions docs/changelog.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## \[Unreleased]
### Added
* `ionoscloud-additional-lans-ids` flag to attach additional LANs to the machine by numeric ID. Values are merged with any IDs resolved from `ionoscloud-additional-lans`.
### Fixed
* `ionoscloud-additional-lans` is no longer silently ignored when the primary NIC is configured via `ionoscloud-lan-id`. Name-to-ID resolution now runs regardless of how the primary LAN is selected.

## \[7.1.1]
### Fixed
* Ensure support for child locations for ipblock reservation and image aliases
Expand Down
2 changes: 2 additions & 0 deletions docs/usage/options.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ Available Options for the IONOS Cloud Docker Machine Driver:
| `--ionoscloud-lan-id` | Existing Ionos Cloud LAN ID (numeric) in which to create the Docker Host |
| `--ionoscloud-lan-name` | Existing Ionos Cloud LAN Name (string) in which to create the Docker Host |
| `--ionoscloud-additional-lans` | Names of existing IONOS Lans to connect the machine to. Names that are not found are ignored |
| `--ionoscloud-additional-lans-ids` | Numeric IDs of existing IONOS LANs to connect the machine to. Merged with any IDs resolved from `--ionoscloud-additional-lans` |
| `--ionoscloud-additional-disks` | A list of disk types and sizes for additional volumes to be created on the machine, the format is DISK_TYPE:DISK_SIZE |
| `--ionoscloud-disk-size` | Ionos Cloud Volume Disk-Size in GB \(10, 50, 100, 200, 400\) |
| `--ionoscloud-disk-type` | Ionos Cloud Volume Disk-Type \(HDD, SSD, SSD Standard, SSD Premium, DAS\). If server type is CUBE this value is ignored and "DAS" is used, "DAS" cannot be used with ENTERPRISE servers |
Expand Down Expand Up @@ -94,6 +95,7 @@ Environment variables are also supported for setting options. This is a list of
| `--ionoscloud-lan-id` | `IONOSCLOUD_LAN_ID` |
| `--ionoscloud-lan-name` | `IONOSCLOUD_LAN_NAME` |
| `--ionoscloud-additional-lans` | `IONOSCLOUD_ADDITIONAL_LANS` |
| `--ionoscloud-additional-lans-ids` | `IONOSCLOUD_ADDITIONAL_LANS_IDS` |
| `--ionoscloud-additional-disks` | `IONOSCLOUD_ADDITIONAL_DISKS` |
| `--ionoscloud-disk-size` | `IONOSCLOUD_DISK_SIZE` |
| `--ionoscloud-disk-type` | `IONOSCLOUD_DISK_TYPE` |
Expand Down
22 changes: 19 additions & 3 deletions ionoscloud.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ const (
flagNatLansToGateways = "ionoscloud-nat-lans-to-gateways"
flagPrivateLan = "ionoscloud-private-lan"
flagAdditionalLans = "ionoscloud-additional-lans"
flagAdditionalLansIds = "ionoscloud-additional-lans-ids"
flagCreateNat = "ionoscloud-create-nat"
flagRKEProvisionUserData = "ionoscloud-rancher-provision-user-data"
flagAppendRKECloudInit = "ionoscloud-append-rke-cloud-init"
Expand Down Expand Up @@ -259,6 +260,11 @@ func (d *Driver) GetCreateFlags() []mcnflag.Flag {
EnvVar: extflag.KebabCaseToEnvVarCase(flagAdditionalLans),
Usage: "Names of existing IONOS Lans to connect the machine to. Names that are not found are ignored",
},
mcnflag.StringSliceFlag{
Name: flagAdditionalLansIds,
EnvVar: extflag.KebabCaseToEnvVarCase(flagAdditionalLansIds),
Usage: "Numeric IDs of existing IONOS LANs to connect the machine to. Merged with any IDs resolved from --ionoscloud-additional-lans",
},
mcnflag.BoolFlag{
Name: flagWaitForIpChange,
EnvVar: extflag.KebabCaseToEnvVarCase(flagWaitForIpChange),
Expand Down Expand Up @@ -480,6 +486,14 @@ func (d *Driver) SetConfigFromFlags(opts drivers.DriverOptions) error {
d.CloudInitB64 = opts.String(flagCloudInitB64)
d.PrivateLan = opts.Bool(flagPrivateLan)
d.AdditionalLans = opts.StringSlice(flagAdditionalLans)
d.AdditionalLansIds = nil
for _, raw := range opts.StringSlice(flagAdditionalLansIds) {
id, err := strconv.Atoi(strings.TrimSpace(raw))
if err != nil {
return fmt.Errorf("invalid value for %s: %q must be a numeric LAN id", flagAdditionalLansIds, raw)
}
d.AdditionalLansIds = append(d.AdditionalLansIds, id)
}

d.SwarmMaster = opts.Bool("swarm-master")
d.SwarmHost = opts.String("swarm-host")
Expand Down Expand Up @@ -585,15 +599,15 @@ func (d *Driver) PreCreateCheck() error {
if d.DatacenterId != "" {
d.DCExists = true

if d.LanId == "" {
if d.LanId == "" || len(d.AdditionalLans) > 0 {
lans, err := d.client().GetLans(d.DatacenterId)
if err != nil {
return err
}

foundLan := false
for _, lan := range *lans.Items {
if *lan.Properties.Name == d.LanName {
if d.LanId == "" && *lan.Properties.Name == d.LanName {
if foundLan {
return fmt.Errorf("multiple LANs with name %v found", d.LanName)
}
Expand All @@ -607,7 +621,9 @@ func (d *Driver) PreCreateCheck() error {
if err != nil {
return fmt.Errorf("invalid LAN ID found: %v", *lanId)
}
d.AdditionalLansIds = append(d.AdditionalLansIds, lanIdInt)
if !slices.Contains(d.AdditionalLansIds, lanIdInt) {
d.AdditionalLansIds = append(d.AdditionalLansIds, lanIdInt)
}
}
}
}
Expand Down
59 changes: 59 additions & 0 deletions ionoscloud_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -536,6 +536,65 @@ func TestPreCreateLans(t *testing.T) {
assert.NoError(t, err)
}

// Regression: when the primary NIC is selected by --ionoscloud-lan-id,
// any names listed in --ionoscloud-additional-lans must still be resolved
// to LAN ids instead of being silently dropped.
func TestPreCreateAdditionalLansResolvedWhenLanIdSet(t *testing.T) {
driver, clientMock := NewTestDriverFlagsSet(t, authFlagsSet)
driver.DatacenterId = "test"
driver.LanId = "100"
driver.AdditionalLans = []string{lanName1, lanName2}
clientMock.EXPECT().GetLans(driver.DatacenterId).Return(&additionalLans, nil)
clientMock.EXPECT().GetLan(driver.DatacenterId, driver.LanId).Return(privateLan, nil)
clientMock.EXPECT().GetDatacenter(driver.DatacenterId).Return(dc, nil)
clientMock.EXPECT().GetImageById(defaultImageAlias).Return(&sdkgo.Image{}, fmt.Errorf("no image found with this id"))
clientMock.EXPECT().GetImages().Return(&images, nil)
clientMock.EXPECT().GetNats(driver.DatacenterId).Return(nats, nil)
err := driver.PreCreateCheck()
assert.NoError(t, err)
assert.Equal(t, "100", driver.LanId, "primary LanId must remain untouched")
assert.ElementsMatch(t, []int{lanId1Int, 5}, driver.AdditionalLansIds)
}

// --ionoscloud-additional-lans-ids must populate AdditionalLansIds directly
// and merge with any ids resolved from --ionoscloud-additional-lans (no dupes).
func TestPreCreateAdditionalLansIdsFromFlag(t *testing.T) {
flags := map[string]interface{}{
flagUsername: "IONOSCLOUD_USERNAME",
flagPassword: "IONOSCLOUD_PASSWORD",
flagAdditionalLans: []string{lanName1},
flagAdditionalLansIds: []string{"2", "7"},
}
driver, clientMock := NewTestDriverFlagsSet(t, flags)
driver.DatacenterId = "test"
driver.LanId = "100"
clientMock.EXPECT().GetLans(driver.DatacenterId).Return(&additionalLans, nil)
clientMock.EXPECT().GetLan(driver.DatacenterId, driver.LanId).Return(privateLan, nil)
clientMock.EXPECT().GetDatacenter(driver.DatacenterId).Return(dc, nil)
clientMock.EXPECT().GetImageById(defaultImageAlias).Return(&sdkgo.Image{}, fmt.Errorf("no image found with this id"))
clientMock.EXPECT().GetImages().Return(&images, nil)
clientMock.EXPECT().GetNats(driver.DatacenterId).Return(nats, nil)
err := driver.PreCreateCheck()
assert.NoError(t, err)
assert.ElementsMatch(t, []int{2, 7}, driver.AdditionalLansIds)
}

// A non-numeric value for --ionoscloud-additional-lans-ids must fail fast
// during flag parsing rather than silently dropping the entry.
func TestSetConfigFromFlagsAdditionalLansIdsInvalid(t *testing.T) {
driver, _ := NewTestDriver(t, defaultHostName, defaultStorePath)
checkFlags := &drivers.CheckDriverOptions{
FlagsValues: map[string]interface{}{
flagUsername: "IONOSCLOUD_USERNAME",
flagPassword: "IONOSCLOUD_PASSWORD",
flagAdditionalLansIds: []string{"not-a-number"},
},
CreateFlags: driver.GetCreateFlags(),
}
err := driver.SetConfigFromFlags(checkFlags)
assert.Error(t, err)
}

func TestCreateSSHKeyErr(t *testing.T) {
driver, _ := NewTestDriverFlagsSet(t, authFlagsSet)
driver.SSHKey = ""
Expand Down
Loading