Skip to content
Merged
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
37 changes: 37 additions & 0 deletions pkg/v2/boot_manager.models.go
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,26 @@ type InstallNewOSPayload struct {
Password string `json:"password,omitempty"`
PartitionsConfig PartitionsConfig `json:"partitions_config,omitempty"`
UserData *string `json:"cloud_init_user_data"`

// Public IPv4 fields
IPv4Address string `json:"ipv4_address,omitempty"`
IPv4Netmask string `json:"ipv4_netmask,omitempty"`
IPv4Gateway string `json:"ipv4_gateway,omitempty"`

// Local/Private IPv4 fields — nullable; omitempty omits nil from JSON payload
LocalIPv4Address *string `json:"local_ipv4_address,omitempty"`
LocalIPv4Netmask *string `json:"local_ipv4_netmask,omitempty"`
LocalIPv4Gateway *string `json:"local_ipv4_gateway,omitempty"`

// Public IPv6 fields
IPv6Address string `json:"ipv6_address,omitempty"`
IPv6Netmask string `json:"ipv6_netmask,omitempty"`
IPv6Gateway string `json:"ipv6_gateway,omitempty"`

// Local/Private IPv6 fields — nullable; omitempty omits nil from JSON payload
LocalIPv6Address *string `json:"local_ipv6_address,omitempty"`
LocalIPv6Netmask *string `json:"local_ipv6_netmask,omitempty"`
LocalIPv6Gateway *string `json:"local_ipv6_gateway,omitempty"`
}

func (p *InstallNewOSPayload) CopyWithoutSensitiveData() *InstallNewOSPayload {
Expand All @@ -155,9 +175,26 @@ func (p *InstallNewOSPayload) CopyWithoutSensitiveData() *InstallNewOSPayload {
UserHostname: p.UserHostname,
PartitionsConfig: p.PartitionsConfig,
UserData: p.UserData,
IPv4Address: p.IPv4Address,
IPv4Netmask: p.IPv4Netmask,
IPv4Gateway: p.IPv4Gateway,
LocalIPv4Address: p.LocalIPv4Address,
LocalIPv4Netmask: p.LocalIPv4Netmask,
LocalIPv4Gateway: p.LocalIPv4Gateway,
IPv6Address: p.IPv6Address,
IPv6Netmask: p.IPv6Netmask,
IPv6Gateway: p.IPv6Gateway,
LocalIPv6Address: p.LocalIPv6Address,
LocalIPv6Netmask: p.LocalIPv6Netmask,
LocalIPv6Gateway: p.LocalIPv6Gateway,
}
}

// StringPtr returns a pointer to the given string value.
func StringPtr(s string) *string {
return &s
}

const (
NoOSValue = "noos"
)
236 changes: 236 additions & 0 deletions pkg/v2/boot_manager.models_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package v2

import (
"encoding/json"
"testing"

"github.com/stretchr/testify/require"
Expand Down Expand Up @@ -142,3 +143,238 @@ func TestOperatingSystem_IsPrivateNetworkAvailable(t *testing.T) {
})
}
}

func TestInstallNewOSPayload_JSONWithLocalIPv4Fields(t *testing.T) {
t.Run("LocalIPv4FieldsSet", func(t *testing.T) {
payload := &InstallNewOSPayload{
OSVersion: "20.04",
OSTemplate: "ubuntu",
OSArch: "x86_64",
UserHostname: "test-host",
LocalIPv4Address: StringPtr("192.168.1.10"),
LocalIPv4Netmask: StringPtr("255.255.255.0"),
LocalIPv4Gateway: StringPtr("192.168.1.1"),
}

data, err := json.Marshal(payload)
require.NoError(t, err)

var result map[string]interface{}
err = json.Unmarshal(data, &result)
require.NoError(t, err)

require.Equal(t, "192.168.1.10", result["local_ipv4_address"])
require.Equal(t, "255.255.255.0", result["local_ipv4_netmask"])
require.Equal(t, "192.168.1.1", result["local_ipv4_gateway"])
})

t.Run("LocalIPv4FieldsNil_SendsNull", func(t *testing.T) {
payload := &InstallNewOSPayload{
OSVersion: "20.04",
OSTemplate: "ubuntu",
OSArch: "x86_64",
UserHostname: "test-host",
LocalIPv4Address: nil,
LocalIPv4Netmask: nil,
LocalIPv4Gateway: nil,
}

data, err := json.Marshal(payload)
require.NoError(t, err)

var result map[string]interface{}
err = json.Unmarshal(data, &result)
require.NoError(t, err)

require.Nil(t, result["local_ipv4_address"])
require.Nil(t, result["local_ipv4_netmask"])
require.Nil(t, result["local_ipv4_gateway"])
})

t.Run("PublicIPv4FieldsOmittedWhenEmpty", func(t *testing.T) {
payload := &InstallNewOSPayload{
OSVersion: "20.04",
OSTemplate: "ubuntu",
OSArch: "x86_64",
UserHostname: "test-host",
}

data, err := json.Marshal(payload)
require.NoError(t, err)

var result map[string]interface{}
err = json.Unmarshal(data, &result)
require.NoError(t, err)

_, hasIPv4Address := result["ipv4_address"]
_, hasIPv4Netmask := result["ipv4_netmask"]
_, hasIPv4Gateway := result["ipv4_gateway"]
require.False(t, hasIPv4Address, "ipv4_address should be omitted when empty")
require.False(t, hasIPv4Netmask, "ipv4_netmask should be omitted when empty")
require.False(t, hasIPv4Gateway, "ipv4_gateway should be omitted when empty")
})

t.Run("PublicIPv4FieldsSet", func(t *testing.T) {
payload := &InstallNewOSPayload{
OSVersion: "20.04",
OSTemplate: "ubuntu",
OSArch: "x86_64",
UserHostname: "test-host",
IPv4Address: "1.2.3.4",
IPv4Netmask: "255.255.255.248",
IPv4Gateway: "1.2.3.1",
}

data, err := json.Marshal(payload)
require.NoError(t, err)

var result map[string]interface{}
err = json.Unmarshal(data, &result)
require.NoError(t, err)

require.Equal(t, "1.2.3.4", result["ipv4_address"])
require.Equal(t, "255.255.255.248", result["ipv4_netmask"])
require.Equal(t, "1.2.3.1", result["ipv4_gateway"])
})
}

func TestInstallNewOSPayload_JSONWithIPv6Fields(t *testing.T) {
t.Run("LocalIPv6FieldsSet", func(t *testing.T) {
payload := &InstallNewOSPayload{
OSVersion: "20.04",
OSTemplate: "ubuntu",
OSArch: "x86_64",
UserHostname: "test-host",
LocalIPv6Address: StringPtr("fd00::10"),
LocalIPv6Netmask: StringPtr("ffff:ffff:ffff::"),
LocalIPv6Gateway: StringPtr("fd00::1"),
}

data, err := json.Marshal(payload)
require.NoError(t, err)

var result map[string]interface{}
err = json.Unmarshal(data, &result)
require.NoError(t, err)

require.Equal(t, "fd00::10", result["local_ipv6_address"])
require.Equal(t, "ffff:ffff:ffff::", result["local_ipv6_netmask"])
require.Equal(t, "fd00::1", result["local_ipv6_gateway"])
})

t.Run("LocalIPv6FieldsNil_SendsNull", func(t *testing.T) {
payload := &InstallNewOSPayload{
OSVersion: "20.04",
OSTemplate: "ubuntu",
OSArch: "x86_64",
UserHostname: "test-host",
LocalIPv6Address: nil,
LocalIPv6Netmask: nil,
LocalIPv6Gateway: nil,
}

data, err := json.Marshal(payload)
require.NoError(t, err)

var result map[string]interface{}
err = json.Unmarshal(data, &result)
require.NoError(t, err)

require.Nil(t, result["local_ipv6_address"])
require.Nil(t, result["local_ipv6_netmask"])
require.Nil(t, result["local_ipv6_gateway"])
})

t.Run("PublicIPv6FieldsSet", func(t *testing.T) {
payload := &InstallNewOSPayload{
OSVersion: "20.04",
OSTemplate: "ubuntu",
OSArch: "x86_64",
UserHostname: "test-host",
IPv6Address: "2001:db8::10",
IPv6Netmask: "ffff:ffff:ffff::",
IPv6Gateway: "2001:db8::1",
}

data, err := json.Marshal(payload)
require.NoError(t, err)

var result map[string]interface{}
err = json.Unmarshal(data, &result)
require.NoError(t, err)

require.Equal(t, "2001:db8::10", result["ipv6_address"])
require.Equal(t, "ffff:ffff:ffff::", result["ipv6_netmask"])
require.Equal(t, "2001:db8::1", result["ipv6_gateway"])
})
}

func TestInstallNewOSPayload_CopyWithoutSensitiveData(t *testing.T) {
userData := "#cloud-config"
payload := &InstallNewOSPayload{
OSVersion: "20.04",
OSTemplate: "ubuntu",
OSArch: "x86_64",
UserSSHKey: "ssh-rsa AAAAB3...",
UserHostname: "test-host",
Password: "secret-password",
PartitionsConfig: PartitionsConfig{},
UserData: &userData,
IPv4Address: "1.2.3.4",
IPv4Netmask: "255.255.255.248",
IPv4Gateway: "1.2.3.1",
LocalIPv4Address: StringPtr("192.168.1.10"),
LocalIPv4Netmask: StringPtr("255.255.255.0"),
LocalIPv4Gateway: StringPtr("192.168.1.1"),
IPv6Address: "2001:db8::10",
IPv6Netmask: "ffff:ffff:ffff::",
IPv6Gateway: "2001:db8::1",
LocalIPv6Address: StringPtr("fd00::10"),
LocalIPv6Netmask: StringPtr("ffff:ffff:ffff::"),
LocalIPv6Gateway: StringPtr("fd00::1"),
}

copied := payload.CopyWithoutSensitiveData()

// Non-sensitive fields should be copied
require.Equal(t, "20.04", copied.OSVersion)
require.Equal(t, "ubuntu", copied.OSTemplate)
require.Equal(t, "x86_64", copied.OSArch)
require.Equal(t, "test-host", copied.UserHostname)
require.Equal(t, payload.PartitionsConfig, copied.PartitionsConfig)
require.Equal(t, &userData, copied.UserData)

// IPv4 fields should be copied
require.Equal(t, "1.2.3.4", copied.IPv4Address)
require.Equal(t, "255.255.255.248", copied.IPv4Netmask)
require.Equal(t, "1.2.3.1", copied.IPv4Gateway)
require.Equal(t, "192.168.1.10", *copied.LocalIPv4Address)
require.Equal(t, "255.255.255.0", *copied.LocalIPv4Netmask)
require.Equal(t, "192.168.1.1", *copied.LocalIPv4Gateway)

// IPv6 fields should be copied
require.Equal(t, "2001:db8::10", copied.IPv6Address)
require.Equal(t, "ffff:ffff:ffff::", copied.IPv6Netmask)
require.Equal(t, "2001:db8::1", copied.IPv6Gateway)
require.Equal(t, "fd00::10", *copied.LocalIPv6Address)
require.Equal(t, "ffff:ffff:ffff::", *copied.LocalIPv6Netmask)
require.Equal(t, "fd00::1", *copied.LocalIPv6Gateway)

// Sensitive fields should NOT be copied
require.Empty(t, copied.UserSSHKey)
require.Empty(t, copied.Password)
}

func TestStringPtr(t *testing.T) {
t.Run("ReturnsPointerToString", func(t *testing.T) {
ptr := StringPtr("hello")
require.NotNil(t, ptr)
require.Equal(t, "hello", *ptr)
})

t.Run("EmptyString", func(t *testing.T) {
ptr := StringPtr("")
require.NotNil(t, ptr)
require.Equal(t, "", *ptr)
})
}
60 changes: 57 additions & 3 deletions pkg/v2/boot_manager_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ import (
"github.com/stretchr/testify/require"
)

const emptyResultBody = `{
"result": {}
}`

func TestServiceClient_OperatingSystems(t *testing.T) {
t.Run("Success", func(t *testing.T) {
// Prepare
Expand Down Expand Up @@ -303,9 +307,7 @@ func TestServiceClient_PartitionsValidate(t *testing.T) {
func TestServiceClient_ReinstallOS(t *testing.T) {
t.Run("Success", func(t *testing.T) {
// Prepare
body := `{
"result": {}
}`
body := emptyResultBody
fakeResp := httptest.NewFakeResponse(200, body) //nolint:bodyclose
fakeTransport := httptest.NewFakeTransport(fakeResp, nil)
client := newFakeClient("http://fake", fakeTransport)
Expand All @@ -328,6 +330,58 @@ func TestServiceClient_ReinstallOS(t *testing.T) {
require.Equal(t, 200, respRes.StatusCode)
})

t.Run("SuccessWithLocalIPv4Fields", func(t *testing.T) {
// Prepare
body := emptyResultBody
fakeResp := httptest.NewFakeResponse(200, body) //nolint:bodyclose
fakeTransport := httptest.NewFakeTransport(fakeResp, nil)
client := newFakeClient("http://fake", fakeTransport)

payload := &InstallNewOSPayload{
OSVersion: "20.04",
OSTemplate: "ubuntu",
OSArch: "x86_64",
UserHostname: "test-host",
LocalIPv4Address: StringPtr("192.168.1.10"),
LocalIPv4Netmask: StringPtr("255.255.255.0"),
LocalIPv4Gateway: StringPtr("192.168.1.1"),
}

// Execute
respRes, err := client.InstallNewOS(context.Background(), payload, "resourceid")

// Analyse
require.NoError(t, err)
require.NotNil(t, respRes)
require.Equal(t, 200, respRes.StatusCode)
})

t.Run("SuccessWithNilLocalIPv4Fields", func(t *testing.T) {
// Prepare — nil local IPv4 fields send null to clear the private subnet
body := emptyResultBody
fakeResp := httptest.NewFakeResponse(200, body) //nolint:bodyclose
fakeTransport := httptest.NewFakeTransport(fakeResp, nil)
client := newFakeClient("http://fake", fakeTransport)

payload := &InstallNewOSPayload{
OSVersion: "20.04",
OSTemplate: "ubuntu",
OSArch: "x86_64",
UserHostname: "test-host",
LocalIPv4Address: nil,
LocalIPv4Netmask: nil,
LocalIPv4Gateway: nil,
}

// Execute
respRes, err := client.InstallNewOS(context.Background(), payload, "resourceid")

// Analyse
require.NoError(t, err)
require.NotNil(t, respRes)
require.Equal(t, 200, respRes.StatusCode)
})

t.Run("HTTPError", func(t *testing.T) {
// Prepare
body := httpErrorBody
Expand Down
Loading