feat(LXC): add networks; closes #2
Some checks failed
CI / build (push) Failing after 3m42s
CI / lint (push) Failing after 3m8s
CI / pkl-validate (push) Successful in 13s
CI / integration-tests (push) Has been skipped
CI / conformance-tests (latest) (push) Has been skipped

This commit is contained in:
2026-02-17 23:13:27 +01:00
parent 48451c6717
commit b8b1bbcdf7
6 changed files with 75 additions and 0 deletions

50
lxc.go
View File

@@ -13,6 +13,8 @@ import (
"github.com/platform-engineering-labs/formae/pkg/plugin/resource" "github.com/platform-engineering-labs/formae/pkg/plugin/resource"
) )
const MAX_NETWORK_COUNT = 10
func parseLXCProperties(data json.RawMessage) (*LXCProperties, error) { func parseLXCProperties(data json.RawMessage) (*LXCProperties, error) {
var props LXCProperties var props LXCProperties
if err := json.Unmarshal(data, &props); err != nil { if err := json.Unmarshal(data, &props); err != nil {
@@ -83,6 +85,10 @@ func (p *Plugin) CreateLXC(ctx context.Context, req *resource.CreateRequest) (*r
urlparams.Add("onboot", strconv.Itoa(props.OnBoot)) urlparams.Add("onboot", strconv.Itoa(props.OnBoot))
} }
for i := range min(MAX_NETWORK_COUNT, len(props.Networks)) {
urlparams.Add(fmt.Sprintf("net%d", i), props.Networks[i])
}
data, err := authenticatedRequest(http.MethodPost, config.URL+"/api2/json/nodes/"+config.NODE+"/lxc", createAuthorizationString(username, token), urlparams) data, err := authenticatedRequest(http.MethodPost, config.URL+"/api2/json/nodes/"+config.NODE+"/lxc", createAuthorizationString(username, token), urlparams)
if err != nil { if err != nil {
@@ -158,6 +164,39 @@ func (p *Plugin) ReadLXC(ctx context.Context, req *resource.ReadRequest) (*resou
lxcdata := props.Data lxcdata := props.Data
networks := []string{}
if len(lxcdata.Net0) > 0 {
networks = append(networks, lxcdata.Net0)
}
if len(lxcdata.Net1) > 0 {
networks = append(networks, lxcdata.Net1)
}
if len(lxcdata.Net2) > 0 {
networks = append(networks, lxcdata.Net2)
}
if len(lxcdata.Net3) > 0 {
networks = append(networks, lxcdata.Net3)
}
if len(lxcdata.Net4) > 0 {
networks = append(networks, lxcdata.Net4)
}
if len(lxcdata.Net5) > 0 {
networks = append(networks, lxcdata.Net5)
}
if len(lxcdata.Net6) > 0 {
networks = append(networks, lxcdata.Net6)
}
if len(lxcdata.Net7) > 0 {
networks = append(networks, lxcdata.Net7)
}
if len(lxcdata.Net8) > 0 {
networks = append(networks, lxcdata.Net8)
}
if len(lxcdata.Net9) > 0 {
networks = append(networks, lxcdata.Net9)
}
properties := LXCProperties{ properties := LXCProperties{
VMID: req.NativeID, VMID: req.NativeID,
Hostname: lxcdata.Hostname, Hostname: lxcdata.Hostname,
@@ -165,6 +204,7 @@ func (p *Plugin) ReadLXC(ctx context.Context, req *resource.ReadRequest) (*resou
Cores: lxcdata.Cores, Cores: lxcdata.Cores,
Memory: lxcdata.Memory, Memory: lxcdata.Memory,
OnBoot: lxcdata.OnBoot, OnBoot: lxcdata.OnBoot,
Networks: networks,
} }
propsJSON, err := json.Marshal(properties) propsJSON, err := json.Marshal(properties)
@@ -264,6 +304,16 @@ func (p *Plugin) UpdateLXC(ctx context.Context, req *resource.UpdateRequest) (*r
urlparams.Add("onboot", strconv.Itoa(desir.OnBoot)) urlparams.Add("onboot", strconv.Itoa(desir.OnBoot))
} }
toDelete := []string{}
for i := range min(MAX_NETWORK_COUNT, len(desir.Networks)) {
if i < len(desir.Networks) {
urlparams.Add(fmt.Sprintf("net%d", i), desir.Networks[i])
} else {
toDelete = append(toDelete, fmt.Sprintf("net%d", i))
}
}
urlparams.Add("delete", strings.Join(toDelete, ","))
_, err = authenticatedRequest("PUT", config.URL+"/api2/json/nodes/"+config.NODE+"/lxc/"+desir.VMID+"/config", createAuthorizationString(username, token), urlparams) _, err = authenticatedRequest("PUT", config.URL+"/api2/json/nodes/"+config.NODE+"/lxc/"+desir.VMID+"/config", createAuthorizationString(username, token), urlparams)
if err != nil { if err != nil {

View File

@@ -34,6 +34,7 @@ func TestCreate(t *testing.T) {
"password": "password", "password": "password",
"cores": 1, "cores": 1,
"memory": 512, "memory": 512,
"networks": []string{"name=test,hwaddr=BC:24:11:FD:90:BF,bridge=internal,firewall=1"},
} }
propertiesJSON, err := json.Marshal(properties) propertiesJSON, err := json.Marshal(properties)
@@ -103,6 +104,7 @@ func TestRead(t *testing.T) {
require.Equal(t, mem_num, props["memory"], "memory should match") require.Equal(t, mem_num, props["memory"], "memory should match")
const onboot float64 = 0 const onboot float64 = 0
require.Equal(t, onboot, props["onboot"], "memory should match") require.Equal(t, onboot, props["onboot"], "memory should match")
require.NotEqual(t, nil, props["networks"], "network should be defined")
} }
func TestUpdate(t *testing.T) { func TestUpdate(t *testing.T) {
@@ -116,6 +118,7 @@ func TestUpdate(t *testing.T) {
"ostemplate": "local:vztmpl/alpine-3.22-default_20250617_amd64.tar.xz", "ostemplate": "local:vztmpl/alpine-3.22-default_20250617_amd64.tar.xz",
"cores": 1, "cores": 1,
"memory": 512, "memory": 512,
"networks": []string{"name=test,hwaddr=BC:24:11:FD:90:BF,bridge=internal,firewall=1"},
}) })
desiredProperties, _ := json.Marshal(map[string]any{ desiredProperties, _ := json.Marshal(map[string]any{
@@ -126,6 +129,7 @@ func TestUpdate(t *testing.T) {
"cores": 2, "cores": 2,
"memory": 1024, "memory": 1024,
"onboot": 1, "onboot": 1,
"networks": []string{"name=test1,hwaddr=BC:24:11:FD:90:BF,bridge=internal,firewall=1"},
}) })
req := &resource.UpdateRequest{ req := &resource.UpdateRequest{

View File

@@ -49,4 +49,7 @@ class LXC extends formae.Resource {
@formae.FieldHint {} @formae.FieldHint {}
sshkeys: Listing<String>? sshkeys: Listing<String>?
@formae.FieldHint {}
networks: Listing<String>?
} }

View File

@@ -34,5 +34,8 @@ forma {
cores = 2 cores = 2
memory = 1024 memory = 1024
onboot = 1 onboot = 1
networks = new Listing<String> {
"name=first,hwaddr=BC:24:11:FD:90:BF,bridge=internal"
}
} }
} }

View File

@@ -36,5 +36,9 @@ forma {
password = "abcd" password = "abcd"
cores = 1 cores = 1
memory = 512 memory = 512
networks = new Listing<String> {
"name=first,hwaddr=BC:24:11:FD:90:BF,bridge=internal"
"name=second,hwaddr=BC:24:11:FD:90:BF,bridge=internal"
}
} }
} }

View File

@@ -17,6 +17,7 @@ type LXCProperties struct {
Memory int `json:"memory"` Memory int `json:"memory"`
OnBoot int `json:"onboot"` OnBoot int `json:"onboot"`
SSHKeys []string `json:"sshkeys"` SSHKeys []string `json:"sshkeys"`
Networks []string `json:"networks"`
} }
type ReadRequest struct { type ReadRequest struct {
@@ -75,6 +76,16 @@ type StatusLXCConfig struct {
Description string `json:"description"` Description string `json:"description"`
Digest string `json:"digest"` Digest string `json:"digest"`
OnBoot int `json:"onboot"` OnBoot int `json:"onboot"`
Net0 string `json:"net0,omitempty"`
Net1 string `json:"net1,omitempty"`
Net2 string `json:"net2,omitempty"`
Net3 string `json:"net3,omitempty"`
Net4 string `json:"net4,omitempty"`
Net5 string `json:"net5,omitempty"`
Net6 string `json:"net6,omitempty"`
Net7 string `json:"net7,omitempty"`
Net8 string `json:"net8,omitempty"`
Net9 string `json:"net9,omitempty"`
} }
type StatusLXCConfigResponse struct { type StatusLXCConfigResponse struct {