mirror of
https://github.com/bpg/terraform-provider-proxmox.git
synced 2025-06-30 02:31:10 +00:00
feat: Extract PVE client to a separate repo
This commit is contained in:
parent
6a3bc03470
commit
98bc511140
@ -1,6 +0,0 @@
|
|||||||
since_tag=v0.4.4
|
|
||||||
exclude-labels=duplicate,question,invalid,wontfix,dependencies
|
|
||||||
pr-wo-labels=false
|
|
||||||
bugs-label=BUG FIXES:
|
|
||||||
breaking-label=BREAKING CHANGES:
|
|
||||||
enhancement-label=ENHANCEMENTS:
|
|
@ -1 +0,0 @@
|
|||||||
1.18.0
|
|
14
go.mod
14
go.mod
@ -2,8 +2,11 @@ module github.com/bpg/terraform-provider-proxmox
|
|||||||
|
|
||||||
go 1.20
|
go 1.20
|
||||||
|
|
||||||
|
replace github.com/bpg/proxmox-api => ../proxmox-api
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/google/go-querystring v1.1.0
|
github.com/bpg/proxmox-api v0.0.0-20230715175825-fd46d7fc2f0c
|
||||||
|
github.com/google/go-querystring v1.1.0 // indirect
|
||||||
github.com/google/uuid v1.3.0
|
github.com/google/uuid v1.3.0
|
||||||
github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320
|
github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320
|
||||||
github.com/hashicorp/terraform-plugin-framework v1.3.2
|
github.com/hashicorp/terraform-plugin-framework v1.3.2
|
||||||
@ -12,10 +15,10 @@ require (
|
|||||||
github.com/hashicorp/terraform-plugin-log v0.9.0
|
github.com/hashicorp/terraform-plugin-log v0.9.0
|
||||||
github.com/hashicorp/terraform-plugin-mux v0.11.1
|
github.com/hashicorp/terraform-plugin-mux v0.11.1
|
||||||
github.com/hashicorp/terraform-plugin-sdk/v2 v2.27.0
|
github.com/hashicorp/terraform-plugin-sdk/v2 v2.27.0
|
||||||
github.com/pkg/sftp v1.13.5
|
github.com/pkg/sftp v1.13.5 // indirect
|
||||||
github.com/skeema/knownhosts v1.1.1
|
github.com/skeema/knownhosts v1.1.1 // indirect
|
||||||
github.com/stretchr/testify v1.8.4
|
github.com/stretchr/testify v1.8.4
|
||||||
golang.org/x/crypto v0.11.0
|
golang.org/x/crypto v0.11.0 // indirect
|
||||||
golang.org/x/exp v0.0.0-20230315142452-642cacee5cc0
|
golang.org/x/exp v0.0.0-20230315142452-642cacee5cc0
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -45,7 +48,7 @@ require (
|
|||||||
github.com/hashicorp/terraform-svchost v0.1.1 // indirect
|
github.com/hashicorp/terraform-svchost v0.1.1 // indirect
|
||||||
github.com/hashicorp/yamux v0.1.1 // indirect
|
github.com/hashicorp/yamux v0.1.1 // indirect
|
||||||
github.com/kr/fs v0.1.0 // indirect
|
github.com/kr/fs v0.1.0 // indirect
|
||||||
github.com/kr/pretty v0.3.0 // indirect
|
github.com/kr/text v0.2.0 // indirect
|
||||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.17 // indirect
|
github.com/mattn/go-isatty v0.0.17 // indirect
|
||||||
github.com/mitchellh/copystructure v1.2.0 // indirect
|
github.com/mitchellh/copystructure v1.2.0 // indirect
|
||||||
@ -55,6 +58,7 @@ require (
|
|||||||
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
||||||
github.com/oklog/run v1.1.0 // indirect
|
github.com/oklog/run v1.1.0 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
|
github.com/rogpeppe/go-internal v1.6.1 // indirect
|
||||||
github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect
|
github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect
|
||||||
github.com/vmihailenco/msgpack/v5 v5.3.5 // indirect
|
github.com/vmihailenco/msgpack/v5 v5.3.5 // indirect
|
||||||
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
|
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
|
||||||
|
3
go.sum
3
go.sum
@ -7,6 +7,8 @@ github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki
|
|||||||
github.com/apparentlymart/go-textseg/v12 v12.0.0/go.mod h1:S/4uRK2UtaQttw1GenVJEynmyUenKwP++x/+DdGV/Ec=
|
github.com/apparentlymart/go-textseg/v12 v12.0.0/go.mod h1:S/4uRK2UtaQttw1GenVJEynmyUenKwP++x/+DdGV/Ec=
|
||||||
github.com/apparentlymart/go-textseg/v13 v13.0.0 h1:Y+KvPE1NYz0xl601PVImeQfFyEy6iT90AvPUL1NNfNw=
|
github.com/apparentlymart/go-textseg/v13 v13.0.0 h1:Y+KvPE1NYz0xl601PVImeQfFyEy6iT90AvPUL1NNfNw=
|
||||||
github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo=
|
github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo=
|
||||||
|
github.com/bpg/proxmox-api v0.0.0-20230715175825-fd46d7fc2f0c h1:cPvk1HoTGJBesKwcs8bYa+pPA5Kudgt9NYFCYdtelBc=
|
||||||
|
github.com/bpg/proxmox-api v0.0.0-20230715175825-fd46d7fc2f0c/go.mod h1:oWrrYJWZZqBOqHrAqaUxZpf0w6RQR2l1nxDy/QKkLHo=
|
||||||
github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
|
github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
|
||||||
github.com/cloudflare/circl v1.1.0/go.mod h1:prBCrKB9DV4poKZY1l9zBXg2QJY7mvgRvtMxxK7fi4I=
|
github.com/cloudflare/circl v1.1.0/go.mod h1:prBCrKB9DV4poKZY1l9zBXg2QJY7mvgRvtMxxK7fi4I=
|
||||||
github.com/cloudflare/circl v1.3.3 h1:fE/Qz0QdIGqeWfnwq0RE0R7MI51s0M2E4Ga9kq5AEMs=
|
github.com/cloudflare/circl v1.3.3 h1:fE/Qz0QdIGqeWfnwq0RE0R7MI51s0M2E4Ga9kq5AEMs=
|
||||||
@ -94,7 +96,6 @@ github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8=
|
|||||||
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
|
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
|
||||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
||||||
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
|
||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
|
8
go.work.sum
Normal file
8
go.work.sum
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
github.com/apparentlymart/go-textseg v1.0.0 h1:rRmlIsPEEhUTIKQb7T++Nz/A5Q6C9IuX2wFoYVvnCs0=
|
||||||
|
github.com/apparentlymart/go-textseg v1.0.0/go.mod h1:z96Txxhf3xSFMPmb5X/1W05FF/Nj9VFpLOpjS5yuumk=
|
||||||
|
github.com/sebdah/goldie v1.0.0/go.mod h1:jXP4hmWywNEwZzhMuv2ccnqTSFpuq8iyQhtQdkkZBH4=
|
||||||
|
github.com/vmihailenco/tagparser v0.1.2 h1:gnjoVuB/kljJ5wICEEOpx98oXMWPLj22G67Vbd1qPqc=
|
||||||
|
github.com/zclconf/go-cty v1.2.0/go.mod h1:hOPWgoHbaTUnI5k4D2ld+GRpFJSCe6bCM7m1q/N4PQ8=
|
||||||
|
github.com/zclconf/go-cty-debug v0.0.0-20191215020915-b22d67c1ba0b/go.mod h1:ZRKQfBXbGkpdV6QMzT3rU1kSTAnfu1dO8dPKjYprgj8=
|
||||||
|
golang.org/x/net v0.0.0-20180811021610-c39426892332/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o=
|
@ -24,9 +24,12 @@ import (
|
|||||||
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
|
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
|
||||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||||
|
|
||||||
pvetypes "github.com/bpg/terraform-provider-proxmox/internal/types"
|
"github.com/bpg/proxmox-api"
|
||||||
"github.com/bpg/terraform-provider-proxmox/proxmox"
|
"github.com/bpg/proxmox-api/nodes"
|
||||||
"github.com/bpg/terraform-provider-proxmox/proxmox/nodes"
|
|
||||||
|
pvetypes "github.com/bpg/proxmox-api/types"
|
||||||
|
|
||||||
|
nettypes "github.com/bpg/terraform-provider-proxmox/internal/network/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -40,10 +43,10 @@ type linuxBridgeResourceModel struct {
|
|||||||
ID types.String `tfsdk:"id"`
|
ID types.String `tfsdk:"id"`
|
||||||
NodeName types.String `tfsdk:"node_name"`
|
NodeName types.String `tfsdk:"node_name"`
|
||||||
Name types.String `tfsdk:"name"`
|
Name types.String `tfsdk:"name"`
|
||||||
Address pvetypes.IPCIDRValue `tfsdk:"address"`
|
Address nettypes.IPCIDRValue `tfsdk:"address"`
|
||||||
Gateway pvetypes.IPAddrValue `tfsdk:"gateway"`
|
Gateway nettypes.IPAddrValue `tfsdk:"gateway"`
|
||||||
Address6 pvetypes.IPCIDRValue `tfsdk:"address6"`
|
Address6 nettypes.IPCIDRValue `tfsdk:"address6"`
|
||||||
Gateway6 pvetypes.IPAddrValue `tfsdk:"gateway6"`
|
Gateway6 nettypes.IPAddrValue `tfsdk:"gateway6"`
|
||||||
Autostart types.Bool `tfsdk:"autostart"`
|
Autostart types.Bool `tfsdk:"autostart"`
|
||||||
MTU types.Int64 `tfsdk:"mtu"`
|
MTU types.Int64 `tfsdk:"mtu"`
|
||||||
Comment types.String `tfsdk:"comment"`
|
Comment types.String `tfsdk:"comment"`
|
||||||
@ -95,10 +98,10 @@ func (m *linuxBridgeResourceModel) importFromNetworkInterfaceList(
|
|||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
iface *nodes.NetworkInterfaceListResponseData,
|
iface *nodes.NetworkInterfaceListResponseData,
|
||||||
) error {
|
) error {
|
||||||
m.Address = pvetypes.NewIPCIDRPointerValue(iface.CIDR)
|
m.Address = nettypes.NewIPCIDRPointerValue(iface.CIDR)
|
||||||
m.Gateway = pvetypes.NewIPAddrPointerValue(iface.Gateway)
|
m.Gateway = nettypes.NewIPAddrPointerValue(iface.Gateway)
|
||||||
m.Address6 = pvetypes.NewIPCIDRPointerValue(iface.CIDR6)
|
m.Address6 = nettypes.NewIPCIDRPointerValue(iface.CIDR6)
|
||||||
m.Gateway6 = pvetypes.NewIPAddrPointerValue(iface.Gateway6)
|
m.Gateway6 = nettypes.NewIPAddrPointerValue(iface.Gateway6)
|
||||||
m.Autostart = types.BoolPointerValue(iface.Autostart.PointerBool())
|
m.Autostart = types.BoolPointerValue(iface.Autostart.PointerBool())
|
||||||
|
|
||||||
if iface.MTU != nil {
|
if iface.MTU != nil {
|
||||||
@ -189,22 +192,22 @@ func (r *linuxBridgeResource) Schema(
|
|||||||
},
|
},
|
||||||
"address": schema.StringAttribute{
|
"address": schema.StringAttribute{
|
||||||
Description: "The interface IPv4/CIDR address.",
|
Description: "The interface IPv4/CIDR address.",
|
||||||
CustomType: pvetypes.IPCIDRType{},
|
CustomType: nettypes.IPCIDRType{},
|
||||||
Optional: true,
|
Optional: true,
|
||||||
},
|
},
|
||||||
"gateway": schema.StringAttribute{
|
"gateway": schema.StringAttribute{
|
||||||
Description: "Default gateway address.",
|
Description: "Default gateway address.",
|
||||||
CustomType: pvetypes.IPAddrType{},
|
CustomType: nettypes.IPAddrType{},
|
||||||
Optional: true,
|
Optional: true,
|
||||||
},
|
},
|
||||||
"address6": schema.StringAttribute{
|
"address6": schema.StringAttribute{
|
||||||
Description: "The interface IPv6/CIDR address.",
|
Description: "The interface IPv6/CIDR address.",
|
||||||
CustomType: pvetypes.IPCIDRType{},
|
CustomType: nettypes.IPCIDRType{},
|
||||||
Optional: true,
|
Optional: true,
|
||||||
},
|
},
|
||||||
"gateway6": schema.StringAttribute{
|
"gateway6": schema.StringAttribute{
|
||||||
Description: "Default IPv6 gateway address.",
|
Description: "Default IPv6 gateway address.",
|
||||||
CustomType: pvetypes.IPAddrType{},
|
CustomType: nettypes.IPAddrType{},
|
||||||
Optional: true,
|
Optional: true,
|
||||||
},
|
},
|
||||||
"autostart": schema.BoolAttribute{
|
"autostart": schema.BoolAttribute{
|
||||||
|
@ -22,9 +22,12 @@ import (
|
|||||||
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
|
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
|
||||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||||
|
|
||||||
pvetypes "github.com/bpg/terraform-provider-proxmox/internal/types"
|
"github.com/bpg/proxmox-api"
|
||||||
"github.com/bpg/terraform-provider-proxmox/proxmox"
|
"github.com/bpg/proxmox-api/nodes"
|
||||||
"github.com/bpg/terraform-provider-proxmox/proxmox/nodes"
|
|
||||||
|
pvetypes "github.com/bpg/proxmox-api/types"
|
||||||
|
|
||||||
|
nettypes "github.com/bpg/terraform-provider-proxmox/internal/network/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -38,10 +41,10 @@ type linuxVLANResourceModel struct {
|
|||||||
ID types.String `tfsdk:"id"`
|
ID types.String `tfsdk:"id"`
|
||||||
NodeName types.String `tfsdk:"node_name"`
|
NodeName types.String `tfsdk:"node_name"`
|
||||||
Name types.String `tfsdk:"name"`
|
Name types.String `tfsdk:"name"`
|
||||||
Address pvetypes.IPCIDRValue `tfsdk:"address"`
|
Address nettypes.IPCIDRValue `tfsdk:"address"`
|
||||||
Gateway pvetypes.IPAddrValue `tfsdk:"gateway"`
|
Gateway nettypes.IPAddrValue `tfsdk:"gateway"`
|
||||||
Address6 pvetypes.IPCIDRValue `tfsdk:"address6"`
|
Address6 nettypes.IPCIDRValue `tfsdk:"address6"`
|
||||||
Gateway6 pvetypes.IPAddrValue `tfsdk:"gateway6"`
|
Gateway6 nettypes.IPAddrValue `tfsdk:"gateway6"`
|
||||||
Autostart types.Bool `tfsdk:"autostart"`
|
Autostart types.Bool `tfsdk:"autostart"`
|
||||||
MTU types.Int64 `tfsdk:"mtu"`
|
MTU types.Int64 `tfsdk:"mtu"`
|
||||||
Comment types.String `tfsdk:"comment"`
|
Comment types.String `tfsdk:"comment"`
|
||||||
@ -80,10 +83,10 @@ func (m *linuxVLANResourceModel) exportToNetworkInterfaceCreateUpdateBody() *nod
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m *linuxVLANResourceModel) importFromNetworkInterfaceList(iface *nodes.NetworkInterfaceListResponseData) {
|
func (m *linuxVLANResourceModel) importFromNetworkInterfaceList(iface *nodes.NetworkInterfaceListResponseData) {
|
||||||
m.Address = pvetypes.NewIPCIDRPointerValue(iface.CIDR)
|
m.Address = nettypes.NewIPCIDRPointerValue(iface.CIDR)
|
||||||
m.Gateway = pvetypes.NewIPAddrPointerValue(iface.Gateway)
|
m.Gateway = nettypes.NewIPAddrPointerValue(iface.Gateway)
|
||||||
m.Address6 = pvetypes.NewIPCIDRPointerValue(iface.CIDR6)
|
m.Address6 = nettypes.NewIPCIDRPointerValue(iface.CIDR6)
|
||||||
m.Gateway6 = pvetypes.NewIPAddrPointerValue(iface.Gateway6)
|
m.Gateway6 = nettypes.NewIPAddrPointerValue(iface.Gateway6)
|
||||||
m.Autostart = types.BoolPointerValue(iface.Autostart.PointerBool())
|
m.Autostart = types.BoolPointerValue(iface.Autostart.PointerBool())
|
||||||
|
|
||||||
if iface.MTU != nil {
|
if iface.MTU != nil {
|
||||||
@ -166,22 +169,22 @@ func (r *linuxVLANResource) Schema(
|
|||||||
},
|
},
|
||||||
"address": schema.StringAttribute{
|
"address": schema.StringAttribute{
|
||||||
Description: "The interface IPv4/CIDR address.",
|
Description: "The interface IPv4/CIDR address.",
|
||||||
CustomType: pvetypes.IPCIDRType{},
|
CustomType: nettypes.IPCIDRType{},
|
||||||
Optional: true,
|
Optional: true,
|
||||||
},
|
},
|
||||||
"gateway": schema.StringAttribute{
|
"gateway": schema.StringAttribute{
|
||||||
Description: "Default gateway address.",
|
Description: "Default gateway address.",
|
||||||
CustomType: pvetypes.IPAddrType{},
|
CustomType: nettypes.IPAddrType{},
|
||||||
Optional: true,
|
Optional: true,
|
||||||
},
|
},
|
||||||
"address6": schema.StringAttribute{
|
"address6": schema.StringAttribute{
|
||||||
Description: "The interface IPv6/CIDR address.",
|
Description: "The interface IPv6/CIDR address.",
|
||||||
CustomType: pvetypes.IPCIDRType{},
|
CustomType: nettypes.IPCIDRType{},
|
||||||
Optional: true,
|
Optional: true,
|
||||||
},
|
},
|
||||||
"gateway6": schema.StringAttribute{
|
"gateway6": schema.StringAttribute{
|
||||||
Description: "Default IPv6 gateway address.",
|
Description: "Default IPv6 gateway address.",
|
||||||
CustomType: pvetypes.IPAddrType{},
|
CustomType: nettypes.IPAddrType{},
|
||||||
Optional: true,
|
Optional: true,
|
||||||
},
|
},
|
||||||
"autostart": schema.BoolAttribute{
|
"autostart": schema.BoolAttribute{
|
||||||
|
@ -23,11 +23,12 @@ import (
|
|||||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||||
"github.com/hashicorp/terraform-plugin-log/tflog"
|
"github.com/hashicorp/terraform-plugin-log/tflog"
|
||||||
|
|
||||||
|
"github.com/bpg/proxmox-api"
|
||||||
|
"github.com/bpg/proxmox-api/nodes"
|
||||||
|
"github.com/bpg/proxmox-api/rest"
|
||||||
|
"github.com/bpg/proxmox-api/ssh"
|
||||||
|
|
||||||
"github.com/bpg/terraform-provider-proxmox/internal/network"
|
"github.com/bpg/terraform-provider-proxmox/internal/network"
|
||||||
"github.com/bpg/terraform-provider-proxmox/proxmox"
|
|
||||||
"github.com/bpg/terraform-provider-proxmox/proxmox/api"
|
|
||||||
"github.com/bpg/terraform-provider-proxmox/proxmox/nodes"
|
|
||||||
"github.com/bpg/terraform-provider-proxmox/proxmox/ssh"
|
|
||||||
"github.com/bpg/terraform-provider-proxmox/utils"
|
"github.com/bpg/terraform-provider-proxmox/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -255,7 +256,7 @@ func (p *proxmoxProvider) Configure(
|
|||||||
|
|
||||||
// Create the Proxmox VE API client
|
// Create the Proxmox VE API client
|
||||||
|
|
||||||
creds, err := api.NewCredentials(username, password, "", apiToken)
|
creds, err := rest.NewCredentials(username, password, "", apiToken)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
resp.Diagnostics.AddError(
|
resp.Diagnostics.AddError(
|
||||||
"Unable to create Proxmox VE API credentials",
|
"Unable to create Proxmox VE API credentials",
|
||||||
@ -263,7 +264,7 @@ func (p *proxmoxProvider) Configure(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
conn, err := api.NewConnection(
|
conn, err := rest.NewConnection(
|
||||||
endpoint,
|
endpoint,
|
||||||
insecure,
|
insecure,
|
||||||
)
|
)
|
||||||
@ -278,7 +279,7 @@ func (p *proxmoxProvider) Configure(
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
apiClient, err := api.NewClient(creds, conn)
|
restClient, err := rest.NewClient(creds, conn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
resp.Diagnostics.AddError(
|
resp.Diagnostics.AddError(
|
||||||
"Unable to create Proxmox VE API client",
|
"Unable to create Proxmox VE API client",
|
||||||
@ -325,7 +326,7 @@ func (p *proxmoxProvider) Configure(
|
|||||||
sshClient, err := ssh.NewClient(
|
sshClient, err := ssh.NewClient(
|
||||||
sshUsername, sshPassword, sshAgent, sshAgentSocket,
|
sshUsername, sshPassword, sshAgent, sshAgentSocket,
|
||||||
&apiResolverWithOverrides{
|
&apiResolverWithOverrides{
|
||||||
ar: apiResolver{c: apiClient},
|
ar: apiResolver{c: restClient},
|
||||||
overrides: nodeOverrides,
|
overrides: nodeOverrides,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@ -340,7 +341,7 @@ func (p *proxmoxProvider) Configure(
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
client := proxmox.NewClient(apiClient, sshClient)
|
client := proxmox.NewClient(restClient, sshClient)
|
||||||
|
|
||||||
resp.ResourceData = client
|
resp.ResourceData = client
|
||||||
resp.DataSourceData = client
|
resp.DataSourceData = client
|
||||||
@ -358,7 +359,7 @@ func (p *proxmoxProvider) DataSources(_ context.Context) []func() datasource.Dat
|
|||||||
}
|
}
|
||||||
|
|
||||||
type apiResolver struct {
|
type apiResolver struct {
|
||||||
c api.Client
|
c rest.Client
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *apiResolver) Resolve(ctx context.Context, nodeName string) (string, error) {
|
func (r *apiResolver) Resolve(ctx context.Context, nodeName string) (string, error) {
|
||||||
|
@ -1,185 +0,0 @@
|
|||||||
/*
|
|
||||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package types
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CustomBool allows a JSON boolean value to also be an integer.
|
|
||||||
type CustomBool bool
|
|
||||||
|
|
||||||
// CustomCommaSeparatedList allows a JSON string to also be a string array.
|
|
||||||
type CustomCommaSeparatedList []string
|
|
||||||
|
|
||||||
// CustomInt allows a JSON integer value to also be a string.
|
|
||||||
type CustomInt int
|
|
||||||
|
|
||||||
// CustomLineBreakSeparatedList allows a multiline JSON string to also be a string array.
|
|
||||||
type CustomLineBreakSeparatedList []string
|
|
||||||
|
|
||||||
// CustomPrivileges allows a JSON object of privileges to also be a string array.
|
|
||||||
type CustomPrivileges []string
|
|
||||||
|
|
||||||
// CustomTimestamp allows a JSON boolean value to also be a unix timestamp.
|
|
||||||
type CustomTimestamp time.Time
|
|
||||||
|
|
||||||
// MarshalJSON converts a boolean to a JSON value.
|
|
||||||
func (r CustomBool) MarshalJSON() ([]byte, error) {
|
|
||||||
buffer := new(bytes.Buffer)
|
|
||||||
|
|
||||||
if r {
|
|
||||||
buffer.WriteString("1")
|
|
||||||
} else {
|
|
||||||
buffer.WriteString("0")
|
|
||||||
}
|
|
||||||
|
|
||||||
return buffer.Bytes(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalJSON converts a JSON value to a boolean.
|
|
||||||
func (r *CustomBool) UnmarshalJSON(b []byte) error {
|
|
||||||
s := string(b)
|
|
||||||
*r = s == "1" || s == "true"
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pointer returns a pointers.
|
|
||||||
func (r CustomBool) Pointer() *CustomBool {
|
|
||||||
return &r
|
|
||||||
}
|
|
||||||
|
|
||||||
// PointerBool returns a pointer to a boolean.
|
|
||||||
func (r *CustomBool) PointerBool() *bool {
|
|
||||||
return (*bool)(r)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarshalJSON converts a boolean to a JSON value.
|
|
||||||
func (r *CustomCommaSeparatedList) MarshalJSON() ([]byte, error) {
|
|
||||||
s := strings.Join(*r, ",")
|
|
||||||
|
|
||||||
return json.Marshal(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalJSON converts a JSON value to a boolean.
|
|
||||||
func (r *CustomCommaSeparatedList) UnmarshalJSON(b []byte) error {
|
|
||||||
var s string
|
|
||||||
|
|
||||||
err := json.Unmarshal(b, &s)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
*r = strings.Split(s, ",")
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalJSON converts a JSON value to an integer.
|
|
||||||
func (r *CustomInt) UnmarshalJSON(b []byte) error {
|
|
||||||
s := string(b)
|
|
||||||
|
|
||||||
if strings.HasPrefix(s, "\"") && strings.HasSuffix(s, "\"") {
|
|
||||||
s = s[1 : len(s)-1]
|
|
||||||
}
|
|
||||||
|
|
||||||
i, err := strconv.ParseInt(s, 10, 32)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
*r = CustomInt(i)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarshalJSON converts a boolean to a JSON value.
|
|
||||||
func (r *CustomLineBreakSeparatedList) MarshalJSON() ([]byte, error) {
|
|
||||||
s := strings.Join(*r, "\n")
|
|
||||||
|
|
||||||
return json.Marshal(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalJSON converts a JSON value to a boolean.
|
|
||||||
func (r *CustomLineBreakSeparatedList) UnmarshalJSON(b []byte) error {
|
|
||||||
var s string
|
|
||||||
|
|
||||||
err := json.Unmarshal(b, &s)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
*r = strings.Split(s, "\n")
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarshalJSON converts a boolean to a JSON value.
|
|
||||||
func (r *CustomPrivileges) MarshalJSON() ([]byte, error) {
|
|
||||||
privileges := map[string]CustomBool{}
|
|
||||||
|
|
||||||
for _, v := range *r {
|
|
||||||
privileges[v] = true
|
|
||||||
}
|
|
||||||
|
|
||||||
return json.Marshal(privileges)
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalJSON converts a JSON value to a boolean.
|
|
||||||
func (r *CustomPrivileges) UnmarshalJSON(b []byte) error {
|
|
||||||
var privileges interface{}
|
|
||||||
|
|
||||||
err := json.Unmarshal(b, &privileges)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
switch s := privileges.(type) {
|
|
||||||
case string:
|
|
||||||
if s != "" {
|
|
||||||
*r = strings.Split(s, ",")
|
|
||||||
} else {
|
|
||||||
*r = CustomPrivileges{}
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
*r = CustomPrivileges{}
|
|
||||||
|
|
||||||
for k, v := range privileges.(map[string]interface{}) {
|
|
||||||
if v.(float64) >= 1 {
|
|
||||||
*r = append(*r, k)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarshalJSON converts a timestamp to a JSON value.
|
|
||||||
func (r CustomTimestamp) MarshalJSON() ([]byte, error) {
|
|
||||||
timestamp := time.Time(r)
|
|
||||||
buffer := bytes.NewBufferString(strconv.FormatInt(timestamp.Unix(), 10))
|
|
||||||
|
|
||||||
return buffer.Bytes(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalJSON converts a JSON value to a timestamp.
|
|
||||||
func (r *CustomTimestamp) UnmarshalJSON(b []byte) error {
|
|
||||||
s := string(b)
|
|
||||||
i, err := strconv.ParseInt(s, 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
*r = CustomTimestamp(time.Unix(i, 0).UTC())
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
@ -1,122 +0,0 @@
|
|||||||
/*
|
|
||||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package types
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"math"
|
|
||||||
"regexp"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Regex used to identify size strings. Case-insensitive. Covers megabytes, gigabytes and terabytes.
|
|
||||||
var sizeRegex = regexp.MustCompile(`(?i)^(\d+(\.\d+)?)(k|kb|kib|m|mb|mib|g|gb|gib|t|tb|tib)?$`)
|
|
||||||
|
|
||||||
// DiskSize allows a JSON integer value to also be a string. This is mapped to `<DiskSize>` data type in Proxmox API.
|
|
||||||
// Represents a disk size in bytes.
|
|
||||||
type DiskSize int64
|
|
||||||
|
|
||||||
// String returns the string representation of the disk size.
|
|
||||||
func (r DiskSize) String() string {
|
|
||||||
return FormatDiskSize(r)
|
|
||||||
}
|
|
||||||
|
|
||||||
// InMegabytes returns the disk size in megabytes.
|
|
||||||
func (r DiskSize) InMegabytes() int {
|
|
||||||
return int(int64(r) / 1024 / 1024)
|
|
||||||
}
|
|
||||||
|
|
||||||
// InGigabytes returns the disk size in gigabytes.
|
|
||||||
func (r DiskSize) InGigabytes() int {
|
|
||||||
return int(int64(r) / 1024 / 1024 / 1024)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DiskSizeFromGigabytes creates a DiskSize from gigabytes.
|
|
||||||
func DiskSizeFromGigabytes(size int) DiskSize {
|
|
||||||
return DiskSize(size * 1024 * 1024 * 1024)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarshalJSON marshals a disk size into a Proxmox API `<DiskSize>` string.
|
|
||||||
func (r DiskSize) MarshalJSON() ([]byte, error) {
|
|
||||||
bytes, err := json.Marshal(FormatDiskSize(r))
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("cannot marshal disk size: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return bytes, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalJSON unmarshals a disk size from a Proxmox API `<DiskSize>` string.
|
|
||||||
func (r *DiskSize) UnmarshalJSON(b []byte) error {
|
|
||||||
s := string(b)
|
|
||||||
|
|
||||||
size, err := ParseDiskSize(s)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
*r = size
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParseDiskSize parses a disk size string into a number of bytes.
|
|
||||||
func ParseDiskSize(size string) (DiskSize, error) {
|
|
||||||
matches := sizeRegex.FindStringSubmatch(size)
|
|
||||||
if len(matches) > 0 {
|
|
||||||
fsize, err := strconv.ParseFloat(matches[1], 64)
|
|
||||||
if err != nil {
|
|
||||||
return -1, fmt.Errorf("cannot parse disk size \"%s\": %w", size, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
switch strings.ToLower(matches[3]) {
|
|
||||||
case "k", "kb", "kib":
|
|
||||||
fsize *= 1024
|
|
||||||
case "m", "mb", "mib":
|
|
||||||
fsize = fsize * 1024 * 1024
|
|
||||||
case "g", "gb", "gib":
|
|
||||||
fsize = fsize * 1024 * 1024 * 1024
|
|
||||||
case "t", "tb", "tib":
|
|
||||||
fsize = fsize * 1024 * 1024 * 1024 * 1024
|
|
||||||
}
|
|
||||||
|
|
||||||
return DiskSize(math.Ceil(fsize)), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return -1, fmt.Errorf("cannot parse disk size \"%s\"", size)
|
|
||||||
}
|
|
||||||
|
|
||||||
// FormatDiskSize turns a number of bytes into a disk size string.
|
|
||||||
func FormatDiskSize(size DiskSize) string {
|
|
||||||
if size < 0 {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
if size < 1024 {
|
|
||||||
return fmt.Sprintf("%d", size)
|
|
||||||
}
|
|
||||||
|
|
||||||
round := func(f float64) string {
|
|
||||||
return strconv.FormatFloat(math.Ceil(f*100)/100, 'f', -1, 64)
|
|
||||||
}
|
|
||||||
|
|
||||||
if size < 1024*1024 {
|
|
||||||
return round(float64(size)/1024) + "K"
|
|
||||||
}
|
|
||||||
|
|
||||||
if size < 1024*1024*1024 {
|
|
||||||
return round(float64(size)/1024/1024) + "M"
|
|
||||||
}
|
|
||||||
|
|
||||||
if size < 1024*1024*1024*1024 {
|
|
||||||
return round(float64(size)/1024/1024/1024) + "G"
|
|
||||||
}
|
|
||||||
|
|
||||||
return round(float64(size)/1024/1024/1024/1024) + "T"
|
|
||||||
}
|
|
@ -1,110 +0,0 @@
|
|||||||
/*
|
|
||||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package types
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestParseDiskSize(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
size string
|
|
||||||
want int64
|
|
||||||
wantErr bool
|
|
||||||
}{
|
|
||||||
{"parse TB", "2TB", 2199023255552, false},
|
|
||||||
{"parse T", "2T", 2199023255552, false},
|
|
||||||
{"parse fraction T", "2.2T", 2418925581108, false},
|
|
||||||
{"parse GB", "2GB", 2147483648, false},
|
|
||||||
{"parse G", "2G", 2147483648, false},
|
|
||||||
{"parse M", "2048M", 2147483648, false},
|
|
||||||
{"parse MB", "2048MB", 2147483648, false},
|
|
||||||
{"parse MiB", "2048MiB", 2147483648, false},
|
|
||||||
{"parse K", "1K", 1024, false},
|
|
||||||
{"parse KB", "2KB", 2048, false},
|
|
||||||
{"parse KiB", "4KiB", 4096, false},
|
|
||||||
{"parse no units as bytes", "12345", 12345, false},
|
|
||||||
{"error on bad format string", "20l8G", -1, true},
|
|
||||||
{"error on unknown unit string", "2048W", -1, true},
|
|
||||||
{"error on arbitrary string", "something", -1, true},
|
|
||||||
}
|
|
||||||
for _, test := range tests {
|
|
||||||
tt := test
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
got, err := ParseDiskSize(tt.size)
|
|
||||||
if (err != nil) != tt.wantErr {
|
|
||||||
t.Errorf("parseDiskSize() error = %v, wantErr %v", err, tt.wantErr)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if int64(got) != tt.want {
|
|
||||||
t.Errorf("parseDiskSize() got = %v, want %v", got, tt.want)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFormatDiskSize(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
size int64
|
|
||||||
want string
|
|
||||||
}{
|
|
||||||
{"handle 0 size", 0, "0"},
|
|
||||||
{"handle bytes", 1001, "1001"},
|
|
||||||
{"handle kilobytes", 1234, "1.21K"},
|
|
||||||
{"handle megabytes", 2097152, "2M"},
|
|
||||||
{"handle gigabytes", 2147483648, "2G"},
|
|
||||||
{"handle terabytes", 2199023255552, "2T"},
|
|
||||||
}
|
|
||||||
for _, test := range tests {
|
|
||||||
tt := test
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
if got := FormatDiskSize(DiskSize(tt.size)); got != tt.want {
|
|
||||||
t.Errorf("formatDiskSize() = %v, want %v", got, tt.want)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestToFromGigabytes(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
size int
|
|
||||||
want string
|
|
||||||
}{
|
|
||||||
{"handle 0 size", 0, "0"},
|
|
||||||
{"handle 99 GB", 99, "99G"},
|
|
||||||
{"handle 100 GB", 100, "100G"},
|
|
||||||
{"handle 101 GB", 101, "101G"},
|
|
||||||
{"handle 1023 GB", 1023, "1023G"},
|
|
||||||
{"handle 1024 GB", 1024, "1T"},
|
|
||||||
{"handle 1025 GB", 1025, "1.01T"},
|
|
||||||
}
|
|
||||||
for _, test := range tests {
|
|
||||||
tt := test
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
ds := DiskSizeFromGigabytes(tt.size)
|
|
||||||
gb := ds.InGigabytes()
|
|
||||||
assert.Equal(t, tt.size, gb)
|
|
||||||
if got := ds.String(); got != tt.want {
|
|
||||||
t.Errorf("DiskSize.String() = %v, want %v", got, tt.want)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,18 +0,0 @@
|
|||||||
/*
|
|
||||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package types
|
|
||||||
|
|
||||||
// StrPtr returns a pointer to a string.
|
|
||||||
func StrPtr(s string) *string {
|
|
||||||
return &s
|
|
||||||
}
|
|
||||||
|
|
||||||
// BoolPtr returns a pointer to a bool.
|
|
||||||
func BoolPtr(s bool) *CustomBool {
|
|
||||||
customBool := CustomBool(s)
|
|
||||||
return &customBool
|
|
||||||
}
|
|
@ -1,50 +0,0 @@
|
|||||||
/*
|
|
||||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package access
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"sort"
|
|
||||||
|
|
||||||
"github.com/bpg/terraform-provider-proxmox/proxmox/api"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (c *Client) aclPath() string {
|
|
||||||
return c.ExpandPath("acl")
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetACL retrieves the access control list.
|
|
||||||
func (c *Client) GetACL(ctx context.Context) ([]*ACLGetResponseData, error) {
|
|
||||||
resBody := &ACLGetResponseBody{}
|
|
||||||
|
|
||||||
err := c.DoRequest(ctx, http.MethodGet, c.aclPath(), nil, resBody)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to get access control list: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if resBody.Data == nil {
|
|
||||||
return nil, api.ErrNoDataObjectInResponse
|
|
||||||
}
|
|
||||||
|
|
||||||
sort.Slice(resBody.Data, func(i, j int) bool {
|
|
||||||
return resBody.Data[i].Path < resBody.Data[j].Path
|
|
||||||
})
|
|
||||||
|
|
||||||
return resBody.Data, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// UpdateACL updates the access control list.
|
|
||||||
func (c *Client) UpdateACL(ctx context.Context, d *ACLUpdateRequestBody) error {
|
|
||||||
err := c.DoRequest(ctx, http.MethodPut, c.aclPath(), d, nil)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to update access control list: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
@ -1,35 +0,0 @@
|
|||||||
/*
|
|
||||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package access
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/bpg/terraform-provider-proxmox/internal/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ACLGetResponseBody contains the body from an access control list response.
|
|
||||||
type ACLGetResponseBody struct {
|
|
||||||
Data []*ACLGetResponseData `json:"data,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// ACLGetResponseData contains the data from an access control list response.
|
|
||||||
type ACLGetResponseData struct {
|
|
||||||
Path string `json:"path"`
|
|
||||||
Propagate *types.CustomBool `json:"propagate,omitempty"`
|
|
||||||
RoleID string `json:"roleid"`
|
|
||||||
Type string `json:"type"`
|
|
||||||
UserOrGroupID string `json:"ugid"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// ACLUpdateRequestBody contains the data for an access control list update request.
|
|
||||||
type ACLUpdateRequestBody struct {
|
|
||||||
Delete *types.CustomBool `json:"delete,omitempty" url:"delete,omitempty,int"`
|
|
||||||
Groups []string `json:"groups,omitempty" url:"groups,omitempty,comma"`
|
|
||||||
Path string `json:"path" url:"path"`
|
|
||||||
Propagate *types.CustomBool `json:"propagate,omitempty" url:"propagate,omitempty,int"`
|
|
||||||
Roles []string `json:"roles" url:"roles,comma"`
|
|
||||||
Users []string `json:"users,omitempty" url:"users,omitempty,comma"`
|
|
||||||
}
|
|
@ -1,23 +0,0 @@
|
|||||||
/*
|
|
||||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package access
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/bpg/terraform-provider-proxmox/proxmox/api"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Client is an interface for performing requests against the Proxmox 'access' API.
|
|
||||||
type Client struct {
|
|
||||||
api.Client
|
|
||||||
}
|
|
||||||
|
|
||||||
// ExpandPath expands a path relative to the client's base path.
|
|
||||||
func (c *Client) ExpandPath(path string) string {
|
|
||||||
return fmt.Sprintf("access/%s", path)
|
|
||||||
}
|
|
@ -1,93 +0,0 @@
|
|||||||
/*
|
|
||||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package access
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"sort"
|
|
||||||
|
|
||||||
"github.com/bpg/terraform-provider-proxmox/proxmox/api"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (c *Client) groupsPath() string {
|
|
||||||
return c.ExpandPath("groups")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) groupPath(id string) string {
|
|
||||||
return fmt.Sprintf("%s/%s", c.groupsPath(), url.PathEscape(id))
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateGroup creates an access group.
|
|
||||||
func (c *Client) CreateGroup(ctx context.Context, d *GroupCreateRequestBody) error {
|
|
||||||
err := c.DoRequest(ctx, http.MethodPost, c.groupsPath(), d, nil)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to create access group: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteGroup deletes an access group.
|
|
||||||
func (c *Client) DeleteGroup(ctx context.Context, id string) error {
|
|
||||||
err := c.DoRequest(ctx, http.MethodDelete, c.groupPath(id), nil, nil)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to delete access group: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetGroup retrieves an access group.
|
|
||||||
func (c *Client) GetGroup(ctx context.Context, id string) (*GroupGetResponseData, error) {
|
|
||||||
resBody := &GroupGetResponseBody{}
|
|
||||||
|
|
||||||
err := c.DoRequest(ctx, http.MethodGet, c.groupPath(id), nil, resBody)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to get access group: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if resBody.Data == nil {
|
|
||||||
return nil, api.ErrNoDataObjectInResponse
|
|
||||||
}
|
|
||||||
|
|
||||||
sort.Strings(resBody.Data.Members)
|
|
||||||
|
|
||||||
return resBody.Data, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListGroups retrieves a list of access groups.
|
|
||||||
func (c *Client) ListGroups(ctx context.Context) ([]*GroupListResponseData, error) {
|
|
||||||
resBody := &GroupListResponseBody{}
|
|
||||||
|
|
||||||
err := c.DoRequest(ctx, http.MethodGet, c.groupsPath(), nil, resBody)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to list access groups: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if resBody.Data == nil {
|
|
||||||
return nil, api.ErrNoDataObjectInResponse
|
|
||||||
}
|
|
||||||
|
|
||||||
sort.Slice(resBody.Data, func(i, j int) bool {
|
|
||||||
return resBody.Data[i].ID < resBody.Data[j].ID
|
|
||||||
})
|
|
||||||
|
|
||||||
return resBody.Data, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// UpdateGroup updates an access group.
|
|
||||||
func (c *Client) UpdateGroup(ctx context.Context, id string, d *GroupUpdateRequestBody) error {
|
|
||||||
err := c.DoRequest(ctx, http.MethodPut, c.groupPath(id), d, nil)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to update access group: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
@ -1,40 +0,0 @@
|
|||||||
/*
|
|
||||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package access
|
|
||||||
|
|
||||||
// GroupCreateRequestBody contains the data for an access group create request.
|
|
||||||
type GroupCreateRequestBody struct {
|
|
||||||
Comment *string `json:"comment,omitempty" url:"comment,omitempty"`
|
|
||||||
ID string `json:"groupid" url:"groupid"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// GroupGetResponseBody contains the body from an access group get response.
|
|
||||||
type GroupGetResponseBody struct {
|
|
||||||
Data *GroupGetResponseData `json:"data,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// GroupGetResponseData contains the data from an access group get response.
|
|
||||||
type GroupGetResponseData struct {
|
|
||||||
Comment *string `json:"comment,omitempty"`
|
|
||||||
Members []string `json:"members"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// GroupListResponseBody contains the body from an access group list response.
|
|
||||||
type GroupListResponseBody struct {
|
|
||||||
Data []*GroupListResponseData `json:"data,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// GroupListResponseData contains the data from an access group list response.
|
|
||||||
type GroupListResponseData struct {
|
|
||||||
Comment *string `json:"comment,omitempty"`
|
|
||||||
ID string `json:"groupid"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// GroupUpdateRequestBody contains the data for an access group update request.
|
|
||||||
type GroupUpdateRequestBody struct {
|
|
||||||
Comment *string `json:"comment,omitempty" url:"comment,omitempty"`
|
|
||||||
}
|
|
@ -1,100 +0,0 @@
|
|||||||
/*
|
|
||||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package access
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"sort"
|
|
||||||
|
|
||||||
"github.com/bpg/terraform-provider-proxmox/internal/types"
|
|
||||||
"github.com/bpg/terraform-provider-proxmox/proxmox/api"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (c *Client) rolesPath() string {
|
|
||||||
return c.ExpandPath("roles")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) rolePath(id string) string {
|
|
||||||
return fmt.Sprintf("%s/%s", c.rolesPath(), url.PathEscape(id))
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateRole creates an access role.
|
|
||||||
func (c *Client) CreateRole(ctx context.Context, d *RoleCreateRequestBody) error {
|
|
||||||
err := c.DoRequest(ctx, http.MethodPost, c.rolesPath(), d, nil)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error creating role: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteRole deletes an access role.
|
|
||||||
func (c *Client) DeleteRole(ctx context.Context, id string) error {
|
|
||||||
err := c.DoRequest(ctx, http.MethodDelete, c.rolePath(id), nil, nil)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error deleting role: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetRole retrieves an access role.
|
|
||||||
func (c *Client) GetRole(ctx context.Context, id string) (*types.CustomPrivileges, error) {
|
|
||||||
resBody := &RoleGetResponseBody{}
|
|
||||||
|
|
||||||
err := c.DoRequest(ctx, http.MethodGet, c.rolePath(id), nil, resBody)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error getting role: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if resBody.Data == nil {
|
|
||||||
return nil, api.ErrNoDataObjectInResponse
|
|
||||||
}
|
|
||||||
|
|
||||||
sort.Strings(*resBody.Data)
|
|
||||||
|
|
||||||
return resBody.Data, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListRoles retrieves a list of access roles.
|
|
||||||
func (c *Client) ListRoles(ctx context.Context) ([]*RoleListResponseData, error) {
|
|
||||||
resBody := &RoleListResponseBody{}
|
|
||||||
|
|
||||||
err := c.DoRequest(ctx, http.MethodGet, c.rolesPath(), nil, resBody)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error listing roles: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if resBody.Data == nil {
|
|
||||||
return nil, api.ErrNoDataObjectInResponse
|
|
||||||
}
|
|
||||||
|
|
||||||
sort.Slice(resBody.Data, func(i, j int) bool {
|
|
||||||
return resBody.Data[i].ID < resBody.Data[j].ID
|
|
||||||
})
|
|
||||||
|
|
||||||
for i := range resBody.Data {
|
|
||||||
if resBody.Data[i].Privileges != nil {
|
|
||||||
sort.Strings(*resBody.Data[i].Privileges)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return resBody.Data, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// UpdateRole updates an access role.
|
|
||||||
func (c *Client) UpdateRole(ctx context.Context, id string, d *RoleUpdateRequestBody) error {
|
|
||||||
err := c.DoRequest(ctx, http.MethodPut, c.rolePath(id), d, nil)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error updating role: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
@ -1,39 +0,0 @@
|
|||||||
/*
|
|
||||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package access
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/bpg/terraform-provider-proxmox/internal/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
// RoleCreateRequestBody contains the data for an access group create request.
|
|
||||||
type RoleCreateRequestBody struct {
|
|
||||||
ID string `json:"roleid" url:"roleid"`
|
|
||||||
Privileges types.CustomPrivileges `json:"privs" url:"privs,comma"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// RoleGetResponseBody contains the body from an access group get response.
|
|
||||||
type RoleGetResponseBody struct {
|
|
||||||
Data *types.CustomPrivileges `json:"data,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// RoleListResponseBody contains the body from an access group list response.
|
|
||||||
type RoleListResponseBody struct {
|
|
||||||
Data []*RoleListResponseData `json:"data,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// RoleListResponseData contains the data from an access group list response.
|
|
||||||
type RoleListResponseData struct {
|
|
||||||
ID string `json:"roleid"`
|
|
||||||
Privileges *types.CustomPrivileges `json:"privs,omitempty"`
|
|
||||||
Special *types.CustomBool `json:"special,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// RoleUpdateRequestBody contains the data for an access group update request.
|
|
||||||
type RoleUpdateRequestBody struct {
|
|
||||||
Privileges types.CustomPrivileges `json:"privs" url:"privs,comma"`
|
|
||||||
}
|
|
@ -1,128 +0,0 @@
|
|||||||
/*
|
|
||||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package access
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"sort"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/bpg/terraform-provider-proxmox/internal/types"
|
|
||||||
"github.com/bpg/terraform-provider-proxmox/proxmox/api"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (c *Client) usersPath() string {
|
|
||||||
return c.ExpandPath("users")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) userPath(id string) string {
|
|
||||||
return fmt.Sprintf("%s/%s", c.usersPath(), url.PathEscape(id))
|
|
||||||
}
|
|
||||||
|
|
||||||
// ChangeUserPassword changes a user's password.
|
|
||||||
func (c *Client) ChangeUserPassword(ctx context.Context, id, password string) error {
|
|
||||||
d := UserChangePasswordRequestBody{
|
|
||||||
ID: id,
|
|
||||||
Password: password,
|
|
||||||
}
|
|
||||||
|
|
||||||
err := c.DoRequest(ctx, http.MethodPut, c.ExpandPath("password"), d, nil)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error changing user password: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateUser creates a user.
|
|
||||||
func (c *Client) CreateUser(ctx context.Context, d *UserCreateRequestBody) error {
|
|
||||||
err := c.DoRequest(ctx, http.MethodPost, c.usersPath(), d, nil)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error creating user: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteUser deletes an user.
|
|
||||||
func (c *Client) DeleteUser(ctx context.Context, id string) error {
|
|
||||||
err := c.DoRequest(ctx, http.MethodDelete, c.userPath(id), nil, nil)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error deleting user: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetUser retrieves a user.
|
|
||||||
func (c *Client) GetUser(ctx context.Context, id string) (*UserGetResponseData, error) {
|
|
||||||
resBody := &UserGetResponseBody{}
|
|
||||||
|
|
||||||
err := c.DoRequest(ctx, http.MethodGet, c.userPath(id), nil, resBody)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error retrieving user: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if resBody.Data == nil {
|
|
||||||
return nil, api.ErrNoDataObjectInResponse
|
|
||||||
}
|
|
||||||
|
|
||||||
if resBody.Data.ExpirationDate != nil {
|
|
||||||
expirationDate := types.CustomTimestamp(time.Time(*resBody.Data.ExpirationDate).UTC())
|
|
||||||
resBody.Data.ExpirationDate = &expirationDate
|
|
||||||
}
|
|
||||||
|
|
||||||
if resBody.Data.Groups != nil {
|
|
||||||
sort.Strings(*resBody.Data.Groups)
|
|
||||||
}
|
|
||||||
|
|
||||||
return resBody.Data, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListUsers retrieves a list of users.
|
|
||||||
func (c *Client) ListUsers(ctx context.Context) ([]*UserListResponseData, error) {
|
|
||||||
resBody := &UserListResponseBody{}
|
|
||||||
|
|
||||||
err := c.DoRequest(ctx, http.MethodGet, c.usersPath(), nil, resBody)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error listing users: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if resBody.Data == nil {
|
|
||||||
return nil, api.ErrNoDataObjectInResponse
|
|
||||||
}
|
|
||||||
|
|
||||||
sort.Slice(resBody.Data, func(i, j int) bool {
|
|
||||||
return resBody.Data[i].ID < resBody.Data[j].ID
|
|
||||||
})
|
|
||||||
|
|
||||||
for i := range resBody.Data {
|
|
||||||
if resBody.Data[i].ExpirationDate != nil {
|
|
||||||
expirationDate := types.CustomTimestamp(time.Time(*resBody.Data[i].ExpirationDate).UTC())
|
|
||||||
resBody.Data[i].ExpirationDate = &expirationDate
|
|
||||||
}
|
|
||||||
|
|
||||||
if resBody.Data[i].Groups != nil {
|
|
||||||
sort.Strings(*resBody.Data[i].Groups)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return resBody.Data, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// UpdateUser updates a user.
|
|
||||||
func (c *Client) UpdateUser(ctx context.Context, id string, d *UserUpdateRequestBody) error {
|
|
||||||
err := c.DoRequest(ctx, http.MethodPut, c.userPath(id), d, nil)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error updating user: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
@ -1,79 +0,0 @@
|
|||||||
/*
|
|
||||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package access
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/bpg/terraform-provider-proxmox/internal/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
// UserChangePasswordRequestBody contains the data for a user password change request.
|
|
||||||
type UserChangePasswordRequestBody struct {
|
|
||||||
ID string `json:"userid" url:"userid"`
|
|
||||||
Password string `json:"password" url:"password"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// UserCreateRequestBody contains the data for a user create request.
|
|
||||||
type UserCreateRequestBody struct {
|
|
||||||
Comment *string `json:"comment,omitempty" url:"comment,omitempty"`
|
|
||||||
Email *string `json:"email,omitempty" url:"email,omitempty"`
|
|
||||||
Enabled *types.CustomBool `json:"enable,omitempty" url:"enable,omitempty,int"`
|
|
||||||
ExpirationDate *types.CustomTimestamp `json:"expire,omitempty" url:"expire,omitempty,unix"`
|
|
||||||
FirstName *string `json:"firstname,omitempty" url:"firstname,omitempty"`
|
|
||||||
Groups []string `json:"groups,omitempty" url:"groups,omitempty,comma"`
|
|
||||||
ID string `json:"userid" url:"userid"`
|
|
||||||
Keys *string `json:"keys,omitempty" url:"keys,omitempty"`
|
|
||||||
LastName *string `json:"lastname,omitempty" url:"lastname,omitempty"`
|
|
||||||
Password string `json:"password" url:"password"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// UserGetResponseBody contains the body from a user get response.
|
|
||||||
type UserGetResponseBody struct {
|
|
||||||
Data *UserGetResponseData `json:"data,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// UserGetResponseData contains the data from an user get response.
|
|
||||||
type UserGetResponseData struct {
|
|
||||||
Comment *string `json:"comment,omitempty"`
|
|
||||||
Email *string `json:"email,omitempty"`
|
|
||||||
Enabled *types.CustomBool `json:"enable,omitempty"`
|
|
||||||
ExpirationDate *types.CustomTimestamp `json:"expire,omitempty"`
|
|
||||||
FirstName *string `json:"firstname,omitempty"`
|
|
||||||
Groups *[]string `json:"groups,omitempty"`
|
|
||||||
Keys *string `json:"keys,omitempty"`
|
|
||||||
LastName *string `json:"lastname,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// UserListResponseBody contains the body from a user list response.
|
|
||||||
type UserListResponseBody struct {
|
|
||||||
Data []*UserListResponseData `json:"data,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// UserListResponseData contains the data from an user list response.
|
|
||||||
type UserListResponseData struct {
|
|
||||||
Comment *string `json:"comment,omitempty"`
|
|
||||||
Email *string `json:"email,omitempty"`
|
|
||||||
Enabled *types.CustomBool `json:"enable,omitempty"`
|
|
||||||
ExpirationDate *types.CustomTimestamp `json:"expire,omitempty"`
|
|
||||||
FirstName *string `json:"firstname,omitempty"`
|
|
||||||
Groups *[]string `json:"groups,omitempty"`
|
|
||||||
ID string `json:"userid"`
|
|
||||||
Keys *string `json:"keys,omitempty"`
|
|
||||||
LastName *string `json:"lastname,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// UserUpdateRequestBody contains the data for an user update request.
|
|
||||||
type UserUpdateRequestBody struct {
|
|
||||||
Append *types.CustomBool `json:"append,omitempty" url:"append,omitempty"`
|
|
||||||
Comment *string `json:"comment,omitempty" url:"comment,omitempty"`
|
|
||||||
Email *string `json:"email,omitempty" url:"email,omitempty"`
|
|
||||||
Enabled *types.CustomBool `json:"enable,omitempty" url:"enable,omitempty,int"`
|
|
||||||
ExpirationDate *types.CustomTimestamp `json:"expire,omitempty" url:"expire,omitempty,unix"`
|
|
||||||
FirstName *string `json:"firstname,omitempty" url:"firstname,omitempty"`
|
|
||||||
Groups []string `json:"groups,omitempty" url:"groups,omitempty,comma"`
|
|
||||||
Keys *string `json:"keys,omitempty" url:"keys,omitempty"`
|
|
||||||
LastName *string `json:"lastname,omitempty" url:"lastname,omitempty"`
|
|
||||||
}
|
|
@ -1,27 +0,0 @@
|
|||||||
/*
|
|
||||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"net/http"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Authenticator is an interface for adding authentication data to a request.
|
|
||||||
// The authenticator is also aware of the authentication context, e.g. if it
|
|
||||||
// is configured to use the root user.
|
|
||||||
type Authenticator interface {
|
|
||||||
// IsRoot returns true if the authenticator is configured to use the root
|
|
||||||
IsRoot() bool
|
|
||||||
|
|
||||||
// IsRootTicket returns true if the authenticator is configured to use the root directly using a login ticket.
|
|
||||||
// (root using token is weaker, cannot change VM arch)
|
|
||||||
IsRootTicket() bool
|
|
||||||
|
|
||||||
// AuthenticateRequest adds authentication data to a new request.
|
|
||||||
AuthenticateRequest(ctx context.Context, req *http.Request) error
|
|
||||||
}
|
|
@ -1,318 +0,0 @@
|
|||||||
/*
|
|
||||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"context"
|
|
||||||
"crypto/tls"
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/google/go-querystring/query"
|
|
||||||
"github.com/hashicorp/terraform-plugin-log/tflog"
|
|
||||||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/logging"
|
|
||||||
|
|
||||||
"github.com/bpg/terraform-provider-proxmox/utils"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ErrNoDataObjectInResponse is returned when the server does not include a data object in the response.
|
|
||||||
var ErrNoDataObjectInResponse = errors.New("the server did not include a data object in the response")
|
|
||||||
|
|
||||||
const (
|
|
||||||
// the large timeout is to allow for large file uploads.
|
|
||||||
httpClientTimeout = 5 * time.Minute
|
|
||||||
basePathJSONAPI = "api2/json"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Client is an interface for performing requests against the Proxmox API.
|
|
||||||
type Client interface {
|
|
||||||
// DoRequest performs a request against the Proxmox API.
|
|
||||||
DoRequest(
|
|
||||||
ctx context.Context,
|
|
||||||
method, path string,
|
|
||||||
requestBody, responseBody interface{},
|
|
||||||
) error
|
|
||||||
|
|
||||||
// ExpandPath expands a path relative to the client's base path.
|
|
||||||
// For example, if the client is configured for a VM and the
|
|
||||||
// path is "firewall/options", the returned path will be
|
|
||||||
// "/nodes/<node>/qemu/<vmid>/firewall/options".
|
|
||||||
ExpandPath(path string) string
|
|
||||||
|
|
||||||
// IsRoot returns true if the client is configured with the root user.
|
|
||||||
IsRoot() bool
|
|
||||||
|
|
||||||
// IsRootTicket returns true if the authenticator is configured to use the root directly using a login ticket.
|
|
||||||
// (root using token is weaker, cannot change VM arch)
|
|
||||||
IsRootTicket() bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// Connection represents a connection to the Proxmox Virtual Environment API.
|
|
||||||
type Connection struct {
|
|
||||||
endpoint string
|
|
||||||
httpClient *http.Client
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewConnection creates and initializes a Connection instance.
|
|
||||||
func NewConnection(endpoint string, insecure bool) (*Connection, error) {
|
|
||||||
u, err := url.ParseRequestURI(endpoint)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.New(
|
|
||||||
"you must specify a valid endpoint for the Proxmox Virtual Environment API (valid: https://host:port/)",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if u.Scheme != "https" {
|
|
||||||
return nil, errors.New(
|
|
||||||
"you must specify a secure endpoint for the Proxmox Virtual Environment API (valid: https://host:port/)",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
var transport http.RoundTripper = &http.Transport{
|
|
||||||
TLSClientConfig: &tls.Config{
|
|
||||||
InsecureSkipVerify: insecure, //nolint:gosec
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
if logging.IsDebugOrHigher() {
|
|
||||||
transport = logging.NewLoggingHTTPTransport(transport)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &Connection{
|
|
||||||
endpoint: strings.TrimRight(u.String(), "/"),
|
|
||||||
httpClient: &http.Client{
|
|
||||||
Transport: transport,
|
|
||||||
Timeout: httpClientTimeout,
|
|
||||||
},
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// VirtualEnvironmentClient implements an API client for the Proxmox Virtual Environment API.
|
|
||||||
type client struct {
|
|
||||||
conn *Connection
|
|
||||||
auth Authenticator
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewClient creates and initializes a VirtualEnvironmentClient instance.
|
|
||||||
func NewClient(creds *Credentials, conn *Connection) (Client, error) {
|
|
||||||
if creds == nil {
|
|
||||||
return nil, errors.New("credentials must not be nil")
|
|
||||||
}
|
|
||||||
|
|
||||||
if conn == nil {
|
|
||||||
return nil, errors.New("connection must not be nil")
|
|
||||||
}
|
|
||||||
|
|
||||||
var auth Authenticator
|
|
||||||
|
|
||||||
var err error
|
|
||||||
|
|
||||||
if creds.APIToken != nil {
|
|
||||||
auth, err = NewTokenAuthenticator(*creds.APIToken)
|
|
||||||
} else {
|
|
||||||
auth, err = NewTicketAuthenticator(conn, creds)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &client{
|
|
||||||
conn: conn,
|
|
||||||
auth: auth,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// DoRequest performs a HTTP request against a JSON API endpoint.
|
|
||||||
func (c *client) DoRequest(
|
|
||||||
ctx context.Context,
|
|
||||||
method, path string,
|
|
||||||
requestBody, responseBody interface{},
|
|
||||||
) error {
|
|
||||||
var reqBodyReader io.Reader
|
|
||||||
|
|
||||||
var reqContentLength *int64
|
|
||||||
|
|
||||||
modifiedPath := path
|
|
||||||
reqBodyType := ""
|
|
||||||
|
|
||||||
//nolint:nestif
|
|
||||||
if requestBody != nil {
|
|
||||||
multipartData, multipart := requestBody.(*MultiPartData)
|
|
||||||
pipedBodyReader, pipedBody := requestBody.(*io.PipeReader)
|
|
||||||
|
|
||||||
switch {
|
|
||||||
case multipart:
|
|
||||||
reqBodyReader = multipartData.Reader
|
|
||||||
reqBodyType = fmt.Sprintf("multipart/form-data; boundary=%s", multipartData.Boundary)
|
|
||||||
reqContentLength = multipartData.Size
|
|
||||||
case pipedBody:
|
|
||||||
reqBodyReader = pipedBodyReader
|
|
||||||
default:
|
|
||||||
v, err := query.Values(requestBody)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to encode HTTP %s request (path: %s) - Reason: %w",
|
|
||||||
method,
|
|
||||||
modifiedPath,
|
|
||||||
err,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
encodedValues := v.Encode()
|
|
||||||
if encodedValues != "" {
|
|
||||||
if method == http.MethodDelete || method == http.MethodGet || method == http.MethodHead {
|
|
||||||
if !strings.Contains(modifiedPath, "?") {
|
|
||||||
modifiedPath = fmt.Sprintf("%s?%s", modifiedPath, encodedValues)
|
|
||||||
} else {
|
|
||||||
modifiedPath = fmt.Sprintf("%s&%s", modifiedPath, encodedValues)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
reqBodyReader = bytes.NewBufferString(encodedValues)
|
|
||||||
reqBodyType = "application/x-www-form-urlencoded"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
reqBodyReader = new(bytes.Buffer)
|
|
||||||
}
|
|
||||||
|
|
||||||
req, err := http.NewRequestWithContext(
|
|
||||||
ctx,
|
|
||||||
method,
|
|
||||||
fmt.Sprintf("%s/%s/%s", c.conn.endpoint, basePathJSONAPI, modifiedPath),
|
|
||||||
reqBodyReader,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf(
|
|
||||||
"failed to create HTTP %s request (path: %s) - Reason: %w",
|
|
||||||
method,
|
|
||||||
modifiedPath,
|
|
||||||
err,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
req.Header.Add("Accept", "application/json")
|
|
||||||
|
|
||||||
if reqContentLength != nil {
|
|
||||||
req.ContentLength = *reqContentLength
|
|
||||||
}
|
|
||||||
|
|
||||||
if reqBodyType != "" {
|
|
||||||
req.Header.Add("Content-Type", reqBodyType)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = c.auth.AuthenticateRequest(ctx, req)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to authenticate HTTP %s request (path: %s) - Reason: %w",
|
|
||||||
method,
|
|
||||||
modifiedPath,
|
|
||||||
err,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
//nolint:bodyclose
|
|
||||||
res, err := c.conn.httpClient.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to perform HTTP %s request (path: %s) - Reason: %w",
|
|
||||||
method,
|
|
||||||
modifiedPath,
|
|
||||||
err,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
defer utils.CloseOrLogError(ctx)(res.Body)
|
|
||||||
|
|
||||||
err = validateResponseCode(res)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
//nolint:nestif
|
|
||||||
if responseBody != nil {
|
|
||||||
err = json.NewDecoder(res.Body).Decode(responseBody)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf(
|
|
||||||
"failed to decode HTTP %s response (path: %s) - Reason: %w",
|
|
||||||
method,
|
|
||||||
modifiedPath,
|
|
||||||
err,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
data, err := io.ReadAll(res.Body)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf(
|
|
||||||
"failed to read HTTP %s response body (path: %s) - Reason: %w",
|
|
||||||
method,
|
|
||||||
modifiedPath,
|
|
||||||
err,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if len(data) > 0 {
|
|
||||||
dr := dataResponse{}
|
|
||||||
|
|
||||||
if err2 := json.NewDecoder(bytes.NewReader(data)).Decode(&dr); err2 == nil {
|
|
||||||
if dr.Data == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
tflog.Warn(ctx, "unhandled HTTP response body", map[string]interface{}{
|
|
||||||
"data": dr.Data,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type dataResponse struct {
|
|
||||||
Data interface{} `json:"data"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// ExpandPath expands the given path to an absolute path.
|
|
||||||
func (c *client) ExpandPath(path string) string {
|
|
||||||
return path
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *client) IsRoot() bool {
|
|
||||||
return c.auth.IsRoot()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *client) IsRootTicket() bool {
|
|
||||||
return c.auth.IsRootTicket()
|
|
||||||
}
|
|
||||||
|
|
||||||
// validateResponseCode ensures that a response is valid.
|
|
||||||
func validateResponseCode(res *http.Response) error {
|
|
||||||
if res.StatusCode < 200 || res.StatusCode >= 300 {
|
|
||||||
status := strings.TrimPrefix(res.Status, fmt.Sprintf("%d ", res.StatusCode))
|
|
||||||
|
|
||||||
errRes := &ErrorResponseBody{}
|
|
||||||
err := json.NewDecoder(res.Body).Decode(errRes)
|
|
||||||
|
|
||||||
if err == nil && errRes.Errors != nil {
|
|
||||||
var errList []string
|
|
||||||
|
|
||||||
for k, v := range *errRes.Errors {
|
|
||||||
errList = append(errList, fmt.Sprintf("%s: %s", k, strings.TrimRight(v, "\n\r")))
|
|
||||||
}
|
|
||||||
|
|
||||||
status = fmt.Sprintf("%s (%s)", status, strings.Join(errList, " - "))
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Errorf("received an HTTP %d response - Reason: %s", res.StatusCode, status)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
@ -1,32 +0,0 @@
|
|||||||
/*
|
|
||||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
|
|
||||||
// MultiPartData enables multipart uploads in DoRequest.
|
|
||||||
type MultiPartData struct {
|
|
||||||
Boundary string
|
|
||||||
Reader io.Reader
|
|
||||||
Size *int64
|
|
||||||
}
|
|
||||||
|
|
||||||
// ErrorResponseBody contains the body of an error response.
|
|
||||||
type ErrorResponseBody struct {
|
|
||||||
Data *string `json:"data"`
|
|
||||||
Errors *map[string]string `json:"errors"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// FileUploadRequest is a request for uploading a file.
|
|
||||||
type FileUploadRequest struct {
|
|
||||||
ContentType string
|
|
||||||
FileName string
|
|
||||||
File *os.File
|
|
||||||
}
|
|
@ -1,61 +0,0 @@
|
|||||||
/*
|
|
||||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
const rootUsername = "root@pam"
|
|
||||||
|
|
||||||
// Credentials is a struct that holds the credentials for the Proxmox Virtual
|
|
||||||
// Environment API.
|
|
||||||
type Credentials struct {
|
|
||||||
Username string
|
|
||||||
Password string
|
|
||||||
OTP *string
|
|
||||||
APIToken *string
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewCredentials creates a new Credentials struct.
|
|
||||||
func NewCredentials(username, password, otp, apiToken string) (*Credentials, error) {
|
|
||||||
if apiToken != "" {
|
|
||||||
return &Credentials{
|
|
||||||
APIToken: &apiToken,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if password == "" {
|
|
||||||
return nil, errors.New(
|
|
||||||
"you must specify a password for the Proxmox Virtual Environment API",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if username == "" {
|
|
||||||
return nil, errors.New(
|
|
||||||
"you must specify a username for the Proxmox Virtual Environment API",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !strings.Contains(username, "@") {
|
|
||||||
return nil, errors.New(
|
|
||||||
"make sure the username for the Proxmox Virtual Environment API ends in '@pve or @pam'",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
c := &Credentials{
|
|
||||||
Username: username,
|
|
||||||
Password: password,
|
|
||||||
}
|
|
||||||
|
|
||||||
if otp != "" {
|
|
||||||
c.OTP = &otp
|
|
||||||
}
|
|
||||||
|
|
||||||
return c, nil
|
|
||||||
}
|
|
@ -1,137 +0,0 @@
|
|||||||
/*
|
|
||||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
|
|
||||||
"github.com/hashicorp/terraform-plugin-log/tflog"
|
|
||||||
|
|
||||||
"github.com/bpg/terraform-provider-proxmox/utils"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ticketAuthenticator struct {
|
|
||||||
conn *Connection
|
|
||||||
authRequest string
|
|
||||||
authData *AuthenticationResponseData
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewTicketAuthenticator returns a new ticket authenticator.
|
|
||||||
func NewTicketAuthenticator(conn *Connection, creds *Credentials) (Authenticator, error) {
|
|
||||||
authRequest := fmt.Sprintf(
|
|
||||||
"username=%s&password=%s",
|
|
||||||
url.QueryEscape(creds.Username),
|
|
||||||
url.QueryEscape(creds.Password),
|
|
||||||
)
|
|
||||||
|
|
||||||
// OTP is optional, and probably doesn't make much sense for most provider users.
|
|
||||||
if creds.OTP != nil {
|
|
||||||
authRequest = fmt.Sprintf("%s&otp=%s", authRequest, url.QueryEscape(*creds.OTP))
|
|
||||||
}
|
|
||||||
|
|
||||||
return &ticketAuthenticator{
|
|
||||||
conn: conn,
|
|
||||||
authRequest: authRequest,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *ticketAuthenticator) authenticate(ctx context.Context) (*AuthenticationResponseData, error) {
|
|
||||||
if t.authData != nil {
|
|
||||||
return t.authData, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
req, err := http.NewRequestWithContext(
|
|
||||||
ctx,
|
|
||||||
http.MethodPost,
|
|
||||||
fmt.Sprintf("%s/%s/access/ticket", t.conn.endpoint, basePathJSONAPI),
|
|
||||||
bytes.NewBufferString(t.authRequest),
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to create authentication request: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
|
|
||||||
|
|
||||||
tflog.Debug(ctx, "Sending authentication request", map[string]interface{}{
|
|
||||||
"path": req.URL.Path,
|
|
||||||
})
|
|
||||||
|
|
||||||
//nolint:bodyclose
|
|
||||||
res, err := t.conn.httpClient.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to retrieve authentication response: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
defer utils.CloseOrLogError(ctx)(res.Body)
|
|
||||||
|
|
||||||
err = validateResponseCode(res)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to authenticate: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
resBody := AuthenticationResponseBody{}
|
|
||||||
|
|
||||||
err = json.NewDecoder(res.Body).Decode(&resBody)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to decode authentication response, %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if resBody.Data == nil {
|
|
||||||
return nil, errors.New("the server did not include a data object in the authentication response")
|
|
||||||
}
|
|
||||||
|
|
||||||
if resBody.Data.CSRFPreventionToken == nil {
|
|
||||||
return nil, errors.New(
|
|
||||||
"the server did not include a CSRF prevention token in the authentication response",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if resBody.Data.Ticket == nil {
|
|
||||||
return nil, errors.New("the server did not include a ticket in the authentication response")
|
|
||||||
}
|
|
||||||
|
|
||||||
if resBody.Data.Username == "" {
|
|
||||||
return nil, errors.New("the server did not include the username in the authentication response")
|
|
||||||
}
|
|
||||||
|
|
||||||
t.authData = resBody.Data
|
|
||||||
|
|
||||||
return resBody.Data, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *ticketAuthenticator) IsRoot() bool {
|
|
||||||
return t.authData != nil && t.authData.Username == rootUsername
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *ticketAuthenticator) IsRootTicket() bool {
|
|
||||||
return t.IsRoot()
|
|
||||||
}
|
|
||||||
|
|
||||||
// AuthenticateRequest adds authentication data to a new request.
|
|
||||||
func (t *ticketAuthenticator) AuthenticateRequest(ctx context.Context, req *http.Request) error {
|
|
||||||
a, err := t.authenticate(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to authenticate: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
req.AddCookie(&http.Cookie{
|
|
||||||
Name: "PVEAuthCookie",
|
|
||||||
Value: *a.Ticket,
|
|
||||||
})
|
|
||||||
|
|
||||||
if req.Method != http.MethodGet {
|
|
||||||
req.Header.Add("CSRFPreventionToken", *a.CSRFPreventionToken)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
@ -1,34 +0,0 @@
|
|||||||
/*
|
|
||||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/bpg/terraform-provider-proxmox/internal/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
// AuthenticationResponseBody contains the body from an authentication response.
|
|
||||||
type AuthenticationResponseBody struct {
|
|
||||||
Data *AuthenticationResponseData `json:"data,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// AuthenticationResponseCapabilities contains the supported capabilities for a session.
|
|
||||||
type AuthenticationResponseCapabilities struct {
|
|
||||||
Access *types.CustomPrivileges `json:"access,omitempty"`
|
|
||||||
Datacenter *types.CustomPrivileges `json:"dc,omitempty"`
|
|
||||||
Nodes *types.CustomPrivileges `json:"nodes,omitempty"`
|
|
||||||
Storage *types.CustomPrivileges `json:"storage,omitempty"`
|
|
||||||
VMs *types.CustomPrivileges `json:"vms,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// AuthenticationResponseData contains the data from an authentication response.
|
|
||||||
type AuthenticationResponseData struct {
|
|
||||||
ClusterName *string `json:"clustername,omitempty"`
|
|
||||||
CSRFPreventionToken *string `json:"CSRFPreventionToken,omitempty"`
|
|
||||||
Capabilities *AuthenticationResponseCapabilities `json:"cap,omitempty"`
|
|
||||||
Ticket *string `json:"ticket,omitempty"`
|
|
||||||
Username string `json:"username"`
|
|
||||||
}
|
|
@ -1,41 +0,0 @@
|
|||||||
/*
|
|
||||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"net/http"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
type tokenAuthenticator struct {
|
|
||||||
username string
|
|
||||||
token string
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewTokenAuthenticator creates a new authenticator that uses a PVE API Token
|
|
||||||
// for authentication.
|
|
||||||
func NewTokenAuthenticator(token string) (Authenticator, error) {
|
|
||||||
return &tokenAuthenticator{
|
|
||||||
username: strings.Split(token, "!")[0],
|
|
||||||
token: token,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *tokenAuthenticator) IsRoot() bool {
|
|
||||||
return t.username == rootUsername
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *tokenAuthenticator) IsRootTicket() bool {
|
|
||||||
// Logged using a token, therefore not a ticket login
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *tokenAuthenticator) AuthenticateRequest(_ context.Context, req *http.Request) error {
|
|
||||||
req.Header.Set("Authorization", "PVEAPIToken="+t.token)
|
|
||||||
return nil
|
|
||||||
}
|
|
@ -1,95 +0,0 @@
|
|||||||
/*
|
|
||||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package proxmox
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/bpg/terraform-provider-proxmox/proxmox/access"
|
|
||||||
"github.com/bpg/terraform-provider-proxmox/proxmox/api"
|
|
||||||
"github.com/bpg/terraform-provider-proxmox/proxmox/cluster"
|
|
||||||
"github.com/bpg/terraform-provider-proxmox/proxmox/nodes"
|
|
||||||
"github.com/bpg/terraform-provider-proxmox/proxmox/pools"
|
|
||||||
"github.com/bpg/terraform-provider-proxmox/proxmox/ssh"
|
|
||||||
"github.com/bpg/terraform-provider-proxmox/proxmox/storage"
|
|
||||||
"github.com/bpg/terraform-provider-proxmox/proxmox/version"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Client defines a client interface for the Proxmox Virtual Environment API.
|
|
||||||
type Client interface {
|
|
||||||
// Access returns a client for managing access control.
|
|
||||||
Access() *access.Client
|
|
||||||
|
|
||||||
// Cluster returns a client for managing the cluster.
|
|
||||||
Cluster() *cluster.Client
|
|
||||||
|
|
||||||
// Node returns a client for managing resources on a specific node.
|
|
||||||
Node(nodeName string) *nodes.Client
|
|
||||||
|
|
||||||
// Pool returns a client for managing resource pools.
|
|
||||||
Pool() *pools.Client
|
|
||||||
|
|
||||||
// Storage returns a client for managing storage.
|
|
||||||
Storage() *storage.Client
|
|
||||||
|
|
||||||
// Version returns a client for getting the version of the Proxmox Virtual Environment API.
|
|
||||||
Version() *version.Client
|
|
||||||
|
|
||||||
// API returns a lower-lever REST API client.
|
|
||||||
API() api.Client
|
|
||||||
|
|
||||||
// SSH returns a lower-lever SSH client.
|
|
||||||
SSH() ssh.Client
|
|
||||||
}
|
|
||||||
|
|
||||||
type client struct {
|
|
||||||
a api.Client
|
|
||||||
s ssh.Client
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewClient creates a new API client.
|
|
||||||
func NewClient(a api.Client, s ssh.Client) Client {
|
|
||||||
return &client{a: a, s: s}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Access returns a client for managing access control.
|
|
||||||
func (c *client) Access() *access.Client {
|
|
||||||
return &access.Client{Client: c.a}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cluster returns a client for managing the cluster.
|
|
||||||
func (c *client) Cluster() *cluster.Client {
|
|
||||||
return &cluster.Client{Client: c.a}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Node returns a client for managing resources on a specific node.
|
|
||||||
func (c *client) Node(nodeName string) *nodes.Client {
|
|
||||||
return &nodes.Client{Client: c.a, NodeName: nodeName}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pool returns a client for managing resource pools.
|
|
||||||
func (c *client) Pool() *pools.Client {
|
|
||||||
return &pools.Client{Client: c.a}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Storage returns a client for managing storage.
|
|
||||||
func (c *client) Storage() *storage.Client {
|
|
||||||
return &storage.Client{Client: c.a}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Version returns a client for getting the version of the Proxmox Virtual Environment API.
|
|
||||||
func (c *client) Version() *version.Client {
|
|
||||||
return &version.Client{Client: c.a}
|
|
||||||
}
|
|
||||||
|
|
||||||
// API returns a lower-lever REST API client.
|
|
||||||
func (c *client) API() api.Client {
|
|
||||||
return c.a
|
|
||||||
}
|
|
||||||
|
|
||||||
// SSH returns a lower-lever SSH client.s.
|
|
||||||
func (c *client) SSH() ssh.Client {
|
|
||||||
return c.s
|
|
||||||
}
|
|
@ -1,32 +0,0 @@
|
|||||||
/*
|
|
||||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package cluster
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/bpg/terraform-provider-proxmox/proxmox/api"
|
|
||||||
clusterfirewall "github.com/bpg/terraform-provider-proxmox/proxmox/cluster/firewall"
|
|
||||||
"github.com/bpg/terraform-provider-proxmox/proxmox/firewall"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Client is an interface for accessing the Proxmox cluster API.
|
|
||||||
type Client struct {
|
|
||||||
api.Client
|
|
||||||
}
|
|
||||||
|
|
||||||
// ExpandPath expands a relative path to a full cluster API path.
|
|
||||||
func (c *Client) ExpandPath(path string) string {
|
|
||||||
return fmt.Sprintf("cluster/%s", path)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Firewall returns a client for managing the cluster firewall.
|
|
||||||
func (c *Client) Firewall() clusterfirewall.API {
|
|
||||||
return &clusterfirewall.Client{
|
|
||||||
Client: firewall.Client{Client: c},
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,143 +0,0 @@
|
|||||||
/*
|
|
||||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package cluster
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/hashicorp/terraform-plugin-log/tflog"
|
|
||||||
|
|
||||||
"github.com/bpg/terraform-provider-proxmox/proxmox/api"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
getVMIDStep = 1
|
|
||||||
)
|
|
||||||
|
|
||||||
// ErrVMDoesNotExist is returned when the VM identifier cannot be found on any cluster node.
|
|
||||||
var ErrVMDoesNotExist = errors.New("unable to find VM identifier on any cluster node")
|
|
||||||
|
|
||||||
var (
|
|
||||||
//nolint:gochecknoglobals
|
|
||||||
getVMIDCounter = -1
|
|
||||||
//nolint:gochecknoglobals
|
|
||||||
getVMIDCounterMutex = &sync.Mutex{}
|
|
||||||
)
|
|
||||||
|
|
||||||
// GetNextID retrieves the next free VM identifier for the cluster.
|
|
||||||
func (c *Client) GetNextID(ctx context.Context, vmID *int) (*int, error) {
|
|
||||||
reqBody := &NextIDRequestBody{
|
|
||||||
VMID: vmID,
|
|
||||||
}
|
|
||||||
|
|
||||||
resBody := &NextIDResponseBody{}
|
|
||||||
|
|
||||||
err := c.DoRequest(ctx, http.MethodGet, "cluster/nextid", reqBody, resBody)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error retrieving next VM ID: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if resBody.Data == nil {
|
|
||||||
return nil, api.ErrNoDataObjectInResponse
|
|
||||||
}
|
|
||||||
|
|
||||||
return (*int)(resBody.Data), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetVMID retrieves the next available VM identifier.
|
|
||||||
func (c *Client) GetVMID(ctx context.Context) (*int, error) {
|
|
||||||
getVMIDCounterMutex.Lock()
|
|
||||||
defer getVMIDCounterMutex.Unlock()
|
|
||||||
|
|
||||||
if getVMIDCounter < 0 {
|
|
||||||
nextVMID, err := c.GetNextID(ctx, nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if nextVMID == nil {
|
|
||||||
return nil, errors.New("unable to retrieve the next available VM identifier")
|
|
||||||
}
|
|
||||||
|
|
||||||
getVMIDCounter = *nextVMID + getVMIDStep
|
|
||||||
|
|
||||||
tflog.Debug(ctx, "next VM identifier", map[string]interface{}{
|
|
||||||
"id": *nextVMID,
|
|
||||||
})
|
|
||||||
|
|
||||||
return nextVMID, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
vmID := getVMIDCounter
|
|
||||||
|
|
||||||
for vmID <= 2147483637 {
|
|
||||||
_, err := c.GetNextID(ctx, &vmID)
|
|
||||||
if err != nil {
|
|
||||||
vmID += getVMIDStep
|
|
||||||
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
getVMIDCounter = vmID + getVMIDStep
|
|
||||||
|
|
||||||
tflog.Debug(ctx, "next VM identifier", map[string]interface{}{
|
|
||||||
"id": vmID,
|
|
||||||
})
|
|
||||||
|
|
||||||
return &vmID, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, errors.New("unable to determine the next available VM identifier")
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetClusterResources retrieves current resources for cluster.
|
|
||||||
func (c *Client) GetClusterResources(ctx context.Context, resourceType string) ([]*ResourcesListResponseData, error) {
|
|
||||||
reqBody := &ResourcesListRequestBody{
|
|
||||||
Type: resourceType,
|
|
||||||
}
|
|
||||||
resBody := &ResourcesListBody{}
|
|
||||||
|
|
||||||
err := c.DoRequest(ctx, http.MethodGet, "cluster/resources", reqBody, resBody)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to get resources list of type (\"%s\") for cluster: %w", resourceType, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if resBody.Data == nil {
|
|
||||||
return nil, api.ErrNoDataObjectInResponse
|
|
||||||
}
|
|
||||||
|
|
||||||
return resBody.Data, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetClusterResourcesVM retrieves current VM resources for cluster.
|
|
||||||
func (c *Client) GetClusterResourcesVM(ctx context.Context) ([]*ResourcesListResponseData, error) {
|
|
||||||
return c.GetClusterResources(ctx, "vm")
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetVMNodeName gets node for specified vmID.
|
|
||||||
func (c *Client) GetVMNodeName(ctx context.Context, vmID int) (*string, error) {
|
|
||||||
allClusterVM, err := c.GetClusterResourcesVM(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if allClusterVM == nil {
|
|
||||||
return nil, api.ErrNoDataObjectInResponse
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, v := range allClusterVM {
|
|
||||||
if v.VMID == vmID {
|
|
||||||
return &v.NodeName, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, ErrVMDoesNotExist
|
|
||||||
}
|
|
@ -1,55 +0,0 @@
|
|||||||
/*
|
|
||||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package cluster
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/bpg/terraform-provider-proxmox/internal/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
// NextIDRequestBody contains the data for a cluster next id request.
|
|
||||||
type NextIDRequestBody struct {
|
|
||||||
VMID *int `json:"vmid,omitempty" url:"vmid,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// NextIDResponseBody contains the body from a cluster next id response.
|
|
||||||
type NextIDResponseBody struct {
|
|
||||||
Data *types.CustomInt `json:"data,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// ResourcesListBody contains the body from a cluste resource list response.
|
|
||||||
type ResourcesListBody struct {
|
|
||||||
Data []*ResourcesListResponseData `json:"data,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// ResourcesListRequestBody contains the body params to cluster resource list request.
|
|
||||||
type ResourcesListRequestBody struct {
|
|
||||||
Type string `json:"type" url:"type"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// ResourcesListResponseData contains the data from a cluster resource list body response.
|
|
||||||
type ResourcesListResponseData struct {
|
|
||||||
Type string `json:"type"`
|
|
||||||
ID string `json:"id"`
|
|
||||||
CgroupMode int `json:"cgroup-mode,omitempty"`
|
|
||||||
Content int `json:"content,omitempty"`
|
|
||||||
CPU float64 `json:"cpu,omitempty"`
|
|
||||||
Disk int `json:"disk,omitempty"`
|
|
||||||
HaState string `json:"hastate,omitempty"`
|
|
||||||
Level string `json:"level,omitempty"`
|
|
||||||
MaxCPU float64 `json:"maxcpu,omitempty"`
|
|
||||||
MaxDisk int `json:"maxdisk,omitempty"`
|
|
||||||
MaxMem int `json:"maxmem,omitempty"`
|
|
||||||
Mem int `json:"mem,omitempty"`
|
|
||||||
Name string `json:"name,omitempty"`
|
|
||||||
NodeName string `json:"node,omitempty"`
|
|
||||||
PluginType string `json:"plugintype,omitempty"`
|
|
||||||
PoolName string `json:"poolname,omitempty"`
|
|
||||||
Status string `json:"status,omitempty"`
|
|
||||||
Storage string `json:"storage,omitempty"`
|
|
||||||
Uptime int `json:"uptime,omitempty"`
|
|
||||||
VMID int `json:"vmid,omitempty"`
|
|
||||||
}
|
|
@ -1,50 +0,0 @@
|
|||||||
/*
|
|
||||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package firewall
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/bpg/terraform-provider-proxmox/proxmox/firewall"
|
|
||||||
)
|
|
||||||
|
|
||||||
// API is an interface for managing cluster firewall.
|
|
||||||
type API interface {
|
|
||||||
firewall.API
|
|
||||||
SecurityGroup
|
|
||||||
Options
|
|
||||||
SecurityGroup(group string) firewall.Rule
|
|
||||||
}
|
|
||||||
|
|
||||||
// Client is an interface for accessing the Proxmox cluster firewall API.
|
|
||||||
type Client struct {
|
|
||||||
firewall.Client
|
|
||||||
}
|
|
||||||
|
|
||||||
type groupClient struct {
|
|
||||||
firewall.Client
|
|
||||||
Group string
|
|
||||||
}
|
|
||||||
|
|
||||||
// SecurityGroup returns a client for managing a specific security group.
|
|
||||||
func (c *Client) SecurityGroup(group string) firewall.Rule {
|
|
||||||
// My head really hurts when I'm looking at this code
|
|
||||||
// I'm not sure if this is the best way to do the required
|
|
||||||
// interface composition and method "overrides", but it works.
|
|
||||||
return &Client{
|
|
||||||
Client: firewall.Client{
|
|
||||||
Client: &groupClient{
|
|
||||||
Client: c.Client,
|
|
||||||
Group: group,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *groupClient) ExpandPath(_ string) string {
|
|
||||||
return fmt.Sprintf("cluster/firewall/groups/%s", c.Group)
|
|
||||||
}
|
|
@ -1,47 +0,0 @@
|
|||||||
/*
|
|
||||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package firewall
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/bpg/terraform-provider-proxmox/proxmox/api"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Options is an interface for managing global firewall options.
|
|
||||||
type Options interface {
|
|
||||||
SetGlobalOptions(ctx context.Context, d *OptionsPutRequestBody) error
|
|
||||||
GetGlobalOptions(ctx context.Context) (*OptionsGetResponseData, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetGlobalOptions sets the global firewall options.
|
|
||||||
func (c *Client) SetGlobalOptions(ctx context.Context, d *OptionsPutRequestBody) error {
|
|
||||||
err := c.DoRequest(ctx, http.MethodPut, "cluster/firewall/options", d, nil)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error setting optionss: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetGlobalOptions retrieves the global firewall options.
|
|
||||||
func (c *Client) GetGlobalOptions(ctx context.Context) (*OptionsGetResponseData, error) {
|
|
||||||
resBody := &OptionsGetResponseBody{}
|
|
||||||
|
|
||||||
err := c.DoRequest(ctx, http.MethodGet, "cluster/firewall/options", nil, resBody)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error retrieving options: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if resBody.Data == nil {
|
|
||||||
return nil, api.ErrNoDataObjectInResponse
|
|
||||||
}
|
|
||||||
|
|
||||||
return resBody.Data, nil
|
|
||||||
}
|
|
@ -1,109 +0,0 @@
|
|||||||
/*
|
|
||||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package firewall
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"net/url"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/bpg/terraform-provider-proxmox/internal/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
// OptionsPutRequestBody is the request body for the PUT /cluster/firewall/options API call.
|
|
||||||
type OptionsPutRequestBody struct {
|
|
||||||
EBTables *types.CustomBool `json:"ebtables,omitempty" url:"ebtables,omitempty,int"`
|
|
||||||
Enable *types.CustomBool `json:"enable,omitempty" url:"enable,omitempty,int"`
|
|
||||||
LogRateLimit *CustomLogRateLimit `json:"log_ratelimit,omitempty" url:"log_ratelimit,omitempty"`
|
|
||||||
PolicyIn *string `json:"policy_in,omitempty" url:"policy_in,omitempty"`
|
|
||||||
PolicyOut *string `json:"policy_out,omitempty" url:"policy_out,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// CustomLogRateLimit is a custom type for the log_ratelimit field of the firewall optionss.
|
|
||||||
type CustomLogRateLimit struct {
|
|
||||||
Enable types.CustomBool `json:"enable,omitempty" url:"enable,omitempty,int"`
|
|
||||||
Burst *int `json:"burst,omitempty" url:"burst,omitempty,int"`
|
|
||||||
Rate *string `json:"rate,omitempty" url:"rate,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// OptionsGetResponseBody is the response body for the GET /cluster/firewall/options API call.
|
|
||||||
type OptionsGetResponseBody struct {
|
|
||||||
Data *OptionsGetResponseData `json:"data,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// OptionsGetResponseData is the data field of the response body for the GET /cluster/firewall/options API call.
|
|
||||||
type OptionsGetResponseData struct {
|
|
||||||
EBTables *types.CustomBool `json:"ebtables" url:"ebtables, int"`
|
|
||||||
Enable *types.CustomBool `json:"enable" url:"enable,int"`
|
|
||||||
LogRateLimit *CustomLogRateLimit `json:"log_ratelimit" url:"log_ratelimit"`
|
|
||||||
PolicyIn *string `json:"policy_in" url:"policy_in"`
|
|
||||||
PolicyOut *string `json:"policy_out" url:"policy_out"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// EncodeValues converts a CustomWatchdogDevice struct to a URL vlaue.
|
|
||||||
func (r *CustomLogRateLimit) EncodeValues(key string, v *url.Values) error {
|
|
||||||
var values []string
|
|
||||||
|
|
||||||
if r.Enable {
|
|
||||||
values = append(values, "enable=1")
|
|
||||||
} else {
|
|
||||||
values = append(values, "enable=0")
|
|
||||||
}
|
|
||||||
|
|
||||||
if r.Burst != nil {
|
|
||||||
values = append(values, fmt.Sprintf("burst=%d", *r.Burst))
|
|
||||||
}
|
|
||||||
|
|
||||||
if r.Rate != nil {
|
|
||||||
values = append(values, fmt.Sprintf("rate=%s", *r.Rate))
|
|
||||||
}
|
|
||||||
|
|
||||||
v.Add(key, strings.Join(values, ","))
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalJSON unmarshals a CustomLogRateLimit struct from JSON.
|
|
||||||
func (r *CustomLogRateLimit) UnmarshalJSON(b []byte) error {
|
|
||||||
var s string
|
|
||||||
|
|
||||||
err := json.Unmarshal(b, &s)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error unmarshaling json: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if s == "" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
pairs := strings.Split(s, ",")
|
|
||||||
|
|
||||||
for _, p := range pairs {
|
|
||||||
v := strings.Split(strings.TrimSpace(p), "=")
|
|
||||||
|
|
||||||
if len(v) == 1 {
|
|
||||||
r.Enable = v[0] == "1"
|
|
||||||
} else if len(v) == 2 {
|
|
||||||
switch v[0] {
|
|
||||||
case "enable":
|
|
||||||
r.Enable = v[1] == "1"
|
|
||||||
case "burst":
|
|
||||||
iv, err := strconv.Atoi(v[1])
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error converting burst to int: %w", err)
|
|
||||||
}
|
|
||||||
r.Burst = &iv
|
|
||||||
case "rate":
|
|
||||||
r.Rate = &v[1]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
@ -1,91 +0,0 @@
|
|||||||
/*
|
|
||||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package firewall
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"sort"
|
|
||||||
|
|
||||||
"github.com/bpg/terraform-provider-proxmox/proxmox/api"
|
|
||||||
)
|
|
||||||
|
|
||||||
// SecurityGroup is an interface for the Proxmox security group API.
|
|
||||||
type SecurityGroup interface {
|
|
||||||
CreateGroup(ctx context.Context, d *GroupCreateRequestBody) error
|
|
||||||
ListGroups(ctx context.Context) ([]*GroupListResponseData, error)
|
|
||||||
UpdateGroup(ctx context.Context, d *GroupUpdateRequestBody) error
|
|
||||||
DeleteGroup(ctx context.Context, group string) error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) securityGroupsPath() string {
|
|
||||||
return "cluster/firewall/groups"
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateGroup create new security group.
|
|
||||||
func (c *Client) CreateGroup(ctx context.Context, d *GroupCreateRequestBody) error {
|
|
||||||
err := c.DoRequest(ctx, http.MethodPost, c.securityGroupsPath(), d, nil)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error creating security group: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListGroups retrieve list of security groups.
|
|
||||||
func (c *Client) ListGroups(ctx context.Context) ([]*GroupListResponseData, error) {
|
|
||||||
resBody := &GroupListResponseBody{}
|
|
||||||
|
|
||||||
err := c.DoRequest(ctx, http.MethodGet, c.securityGroupsPath(), nil, resBody)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error retrieving security groups: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if resBody.Data == nil {
|
|
||||||
return nil, api.ErrNoDataObjectInResponse
|
|
||||||
}
|
|
||||||
|
|
||||||
sort.Slice(resBody.Data, func(i, j int) bool {
|
|
||||||
return resBody.Data[i].Group < resBody.Data[j].Group
|
|
||||||
})
|
|
||||||
|
|
||||||
return resBody.Data, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// UpdateGroup update security group.
|
|
||||||
func (c *Client) UpdateGroup(ctx context.Context, d *GroupUpdateRequestBody) error {
|
|
||||||
err := c.DoRequest(
|
|
||||||
ctx,
|
|
||||||
http.MethodPost,
|
|
||||||
c.securityGroupsPath(),
|
|
||||||
d,
|
|
||||||
nil,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error updating security group: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteGroup delete security group.
|
|
||||||
func (c *Client) DeleteGroup(ctx context.Context, group string) error {
|
|
||||||
err := c.DoRequest(
|
|
||||||
ctx,
|
|
||||||
http.MethodDelete,
|
|
||||||
fmt.Sprintf("%s/%s", c.securityGroupsPath(), url.PathEscape(group)),
|
|
||||||
nil,
|
|
||||||
nil,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error deleting security group '%s': %w", group, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
@ -1,35 +0,0 @@
|
|||||||
/*
|
|
||||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package firewall
|
|
||||||
|
|
||||||
// GroupCreateRequestBody contains the data for a security group create request.
|
|
||||||
type GroupCreateRequestBody struct {
|
|
||||||
Group string `json:"group" url:"group"`
|
|
||||||
Comment *string `json:"comment,omitempty" url:"comment,omitempty"`
|
|
||||||
Digest *string `json:"digest,omitempty" url:"digest,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// GroupListResponseData contains the data from a group list response.
|
|
||||||
type GroupListResponseData struct {
|
|
||||||
Comment *string `json:"comment,omitempty" url:"comment,omitempty"`
|
|
||||||
Group string `json:"group" url:"group"`
|
|
||||||
Digest string `json:"digest" url:"digest"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// GroupListResponseBody contains the data from a group get response.
|
|
||||||
type GroupListResponseBody struct {
|
|
||||||
Data []*GroupListResponseData `json:"data,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// GroupUpdateRequestBody contains the data for a group update request.
|
|
||||||
type GroupUpdateRequestBody struct {
|
|
||||||
Group string `json:"group" url:"group"`
|
|
||||||
|
|
||||||
Comment *string `json:"comment,omitempty" url:"comment,omitempty"`
|
|
||||||
ReName *string `json:"rename,omitempty" url:"rename,omitempty"`
|
|
||||||
Digest *string `json:"digest,omitempty" url:"digest,omitempty"`
|
|
||||||
}
|
|
@ -1,100 +0,0 @@
|
|||||||
/*
|
|
||||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package firewall
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"sort"
|
|
||||||
|
|
||||||
"github.com/bpg/terraform-provider-proxmox/proxmox/api"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Alias is an interface for managing firewall aliases.
|
|
||||||
type Alias interface {
|
|
||||||
CreateAlias(ctx context.Context, d *AliasCreateRequestBody) error
|
|
||||||
DeleteAlias(ctx context.Context, name string) error
|
|
||||||
GetAlias(ctx context.Context, name string) (*AliasGetResponseData, error)
|
|
||||||
ListAliases(ctx context.Context) ([]*AliasGetResponseData, error)
|
|
||||||
UpdateAlias(ctx context.Context, name string, d *AliasUpdateRequestBody) error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) aliasesPath() string {
|
|
||||||
return c.ExpandPath("firewall/aliases")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) aliasPath(name string) string {
|
|
||||||
return fmt.Sprintf("%s/%s", c.aliasesPath(), url.PathEscape(name))
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateAlias create an alias.
|
|
||||||
func (c *Client) CreateAlias(ctx context.Context, d *AliasCreateRequestBody) error {
|
|
||||||
err := c.DoRequest(ctx, http.MethodPost, c.aliasesPath(), d, nil)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error creating alias: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteAlias delete an alias.
|
|
||||||
func (c *Client) DeleteAlias(ctx context.Context, name string) error {
|
|
||||||
err := c.DoRequest(ctx, http.MethodDelete, c.aliasPath(name), nil, nil)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error deleting alias '%s': %w", name, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetAlias retrieves an alias.
|
|
||||||
func (c *Client) GetAlias(ctx context.Context, name string) (*AliasGetResponseData, error) {
|
|
||||||
resBody := &AliasGetResponseBody{}
|
|
||||||
|
|
||||||
err := c.DoRequest(ctx, http.MethodGet, c.aliasPath(name), nil, resBody)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error retrieving alias '%s': %w", name, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if resBody.Data == nil {
|
|
||||||
return nil, api.ErrNoDataObjectInResponse
|
|
||||||
}
|
|
||||||
|
|
||||||
return resBody.Data, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListAliases retrieves a list of aliases.
|
|
||||||
func (c *Client) ListAliases(ctx context.Context) ([]*AliasGetResponseData, error) {
|
|
||||||
resBody := &AliasListResponseBody{}
|
|
||||||
|
|
||||||
err := c.DoRequest(ctx, http.MethodGet, c.aliasesPath(), nil, resBody)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error retrieving aliases: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if resBody.Data == nil {
|
|
||||||
return nil, api.ErrNoDataObjectInResponse
|
|
||||||
}
|
|
||||||
|
|
||||||
sort.Slice(resBody.Data, func(i, j int) bool {
|
|
||||||
return resBody.Data[i].Name < resBody.Data[j].Name
|
|
||||||
})
|
|
||||||
|
|
||||||
return resBody.Data, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// UpdateAlias updates an alias.
|
|
||||||
func (c *Client) UpdateAlias(ctx context.Context, name string, d *AliasUpdateRequestBody) error {
|
|
||||||
err := c.DoRequest(ctx, http.MethodPut, c.aliasPath(name), d, nil)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error updating alias '%s': %w", name, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
@ -1,40 +0,0 @@
|
|||||||
/*
|
|
||||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package firewall
|
|
||||||
|
|
||||||
// AliasCreateRequestBody contains the data for an alias create request.
|
|
||||||
type AliasCreateRequestBody struct {
|
|
||||||
Comment *string `json:"comment,omitempty" url:"comment,omitempty"`
|
|
||||||
Name string `json:"name" url:"name"`
|
|
||||||
CIDR string `json:"cidr" url:"cidr"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// AliasGetResponseBody contains the body from an alias get response.
|
|
||||||
type AliasGetResponseBody struct {
|
|
||||||
Data *AliasGetResponseData `json:"data,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// AliasGetResponseData contains the data from an alias get response.
|
|
||||||
type AliasGetResponseData struct {
|
|
||||||
Comment *string `json:"comment,omitempty" url:"comment,omitempty"`
|
|
||||||
Name string `json:"name" url:"name"`
|
|
||||||
CIDR string `json:"cidr" url:"cidr"`
|
|
||||||
Digest *string `json:"digest" url:"digest"`
|
|
||||||
IPVersion int `json:"ipversion" url:"ipversion"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// AliasListResponseBody contains the data from an alias get response.
|
|
||||||
type AliasListResponseBody struct {
|
|
||||||
Data []*AliasGetResponseData `json:"data,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// AliasUpdateRequestBody contains the data for an alias update request.
|
|
||||||
type AliasUpdateRequestBody struct {
|
|
||||||
Comment *string `json:"comment,omitempty" url:"comment,omitempty"`
|
|
||||||
ReName string `json:"rename" url:"rename"`
|
|
||||||
CIDR string `json:"cidr" url:"cidr"`
|
|
||||||
}
|
|
@ -1,24 +0,0 @@
|
|||||||
/*
|
|
||||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package firewall
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/bpg/terraform-provider-proxmox/proxmox/api"
|
|
||||||
)
|
|
||||||
|
|
||||||
// API is an interface for the Proxmox firewall API.
|
|
||||||
type API interface {
|
|
||||||
Alias
|
|
||||||
IPSet
|
|
||||||
Rule
|
|
||||||
Options
|
|
||||||
}
|
|
||||||
|
|
||||||
// Client is an interface for accessing the Proxmox firewall API.
|
|
||||||
type Client struct {
|
|
||||||
api.Client
|
|
||||||
}
|
|
@ -1,146 +0,0 @@
|
|||||||
/*
|
|
||||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reference: https://pve.proxmox.com/pve-docs/api-viewer/#/cluster/firewall/ipset
|
|
||||||
*/
|
|
||||||
|
|
||||||
package firewall
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"sort"
|
|
||||||
|
|
||||||
"github.com/bpg/terraform-provider-proxmox/proxmox/api"
|
|
||||||
)
|
|
||||||
|
|
||||||
// IPSet is an interface for managing IP sets.
|
|
||||||
type IPSet interface {
|
|
||||||
CreateIPSet(ctx context.Context, d *IPSetCreateRequestBody) error
|
|
||||||
AddCIDRToIPSet(ctx context.Context, id string, d IPSetGetResponseData) error
|
|
||||||
UpdateIPSet(ctx context.Context, d *IPSetUpdateRequestBody) error
|
|
||||||
DeleteIPSet(ctx context.Context, id string) error
|
|
||||||
DeleteIPSetContent(ctx context.Context, id string, cidr string) error
|
|
||||||
GetIPSetContent(ctx context.Context, id string) ([]*IPSetGetResponseData, error)
|
|
||||||
ListIPSets(ctx context.Context) ([]*IPSetListResponseData, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) ipsetPath() string {
|
|
||||||
return c.ExpandPath("firewall/ipset")
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateIPSet create an IPSet.
|
|
||||||
func (c *Client) CreateIPSet(ctx context.Context, d *IPSetCreateRequestBody) error {
|
|
||||||
err := c.DoRequest(ctx, http.MethodPost, c.ipsetPath(), d, nil)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error creating IPSet: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddCIDRToIPSet adds IP or Network to IPSet.
|
|
||||||
func (c *Client) AddCIDRToIPSet(ctx context.Context, id string, d IPSetGetResponseData) error {
|
|
||||||
err := c.DoRequest(
|
|
||||||
ctx,
|
|
||||||
http.MethodPost,
|
|
||||||
fmt.Sprintf("%s/%s", c.ipsetPath(), url.PathEscape(id)),
|
|
||||||
&d,
|
|
||||||
nil,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error adding CIDR to IPSet: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// UpdateIPSet updates an IPSet.
|
|
||||||
func (c *Client) UpdateIPSet(ctx context.Context, d *IPSetUpdateRequestBody) error {
|
|
||||||
err := c.DoRequest(ctx, http.MethodPost, c.ipsetPath(), d, nil)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error updating IPSet: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteIPSet delete an IPSet.
|
|
||||||
func (c *Client) DeleteIPSet(ctx context.Context, id string) error {
|
|
||||||
err := c.DoRequest(
|
|
||||||
ctx,
|
|
||||||
http.MethodDelete,
|
|
||||||
fmt.Sprintf("%s/%s", c.ipsetPath(), url.PathEscape(id)),
|
|
||||||
nil,
|
|
||||||
nil,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error deleting IPSet %s: %w", id, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteIPSetContent remove IP or Network from IPSet.
|
|
||||||
func (c *Client) DeleteIPSetContent(ctx context.Context, id string, cidr string) error {
|
|
||||||
err := c.DoRequest(
|
|
||||||
ctx,
|
|
||||||
http.MethodDelete,
|
|
||||||
fmt.Sprintf("%s/%s/%s", c.ipsetPath(), url.PathEscape(id), url.PathEscape(cidr)),
|
|
||||||
nil,
|
|
||||||
nil,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error deleting IPSet content %s: %w", id, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetIPSetContent retrieve a list of IPSet content.
|
|
||||||
func (c *Client) GetIPSetContent(ctx context.Context, id string) ([]*IPSetGetResponseData, error) {
|
|
||||||
resBody := &IPSetGetResponseBody{}
|
|
||||||
|
|
||||||
err := c.DoRequest(
|
|
||||||
ctx,
|
|
||||||
http.MethodGet,
|
|
||||||
fmt.Sprintf("%s/%s", c.ipsetPath(), url.PathEscape(id)),
|
|
||||||
nil,
|
|
||||||
resBody,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error getting IPSet content: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if resBody.Data == nil {
|
|
||||||
return nil, api.ErrNoDataObjectInResponse
|
|
||||||
}
|
|
||||||
|
|
||||||
return resBody.Data, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListIPSets retrieves list of IPSets.
|
|
||||||
func (c *Client) ListIPSets(ctx context.Context) ([]*IPSetListResponseData, error) {
|
|
||||||
resBody := &IPSetListResponseBody{}
|
|
||||||
|
|
||||||
err := c.DoRequest(ctx, http.MethodGet, c.ipsetPath(), nil, resBody)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error getting IPSet list: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if resBody.Data == nil {
|
|
||||||
return nil, api.ErrNoDataObjectInResponse
|
|
||||||
}
|
|
||||||
|
|
||||||
sort.Slice(resBody.Data, func(i, j int) bool {
|
|
||||||
return resBody.Data[i].Name < resBody.Data[j].Name
|
|
||||||
})
|
|
||||||
|
|
||||||
return resBody.Data, nil
|
|
||||||
}
|
|
@ -1,50 +0,0 @@
|
|||||||
/*
|
|
||||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package firewall
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/bpg/terraform-provider-proxmox/internal/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
// IPSetListResponseBody contains the data from an IPSet get response.
|
|
||||||
type IPSetListResponseBody struct {
|
|
||||||
Data []*IPSetListResponseData `json:"data,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// IPSetCreateRequestBody contains the data for an IPSet create request.
|
|
||||||
type IPSetCreateRequestBody struct {
|
|
||||||
Comment string `json:"comment,omitempty" url:"comment,omitempty"`
|
|
||||||
Name string `json:"name" url:"name"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// IPSetGetResponseBody contains the body from an IPSet get response.
|
|
||||||
type IPSetGetResponseBody struct {
|
|
||||||
Data []*IPSetGetResponseData `json:"data,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// IPSetGetResponseData contains the data from an IPSet get response.
|
|
||||||
type IPSetGetResponseData struct {
|
|
||||||
CIDR string `json:"cidr" url:"cidr"`
|
|
||||||
NoMatch *types.CustomBool `json:"nomatch,omitempty" url:"nomatch,omitempty,int"`
|
|
||||||
Comment *string `json:"comment,omitempty" url:"comment,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// IPSetUpdateRequestBody contains the data for an IPSet update request.
|
|
||||||
type IPSetUpdateRequestBody struct {
|
|
||||||
ReName string `json:"rename,omitempty" url:"rename,omitempty"`
|
|
||||||
Comment *string `json:"comment,omitempty" url:"comment,omitempty"`
|
|
||||||
Name string `json:"name" url:"name"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// IPSetListResponseData contains list of IPSets from.
|
|
||||||
type IPSetListResponseData struct {
|
|
||||||
Comment *string `json:"comment,omitempty" url:"comment,omitempty"`
|
|
||||||
Name string `json:"name" url:"name"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// IPSetContent is an array of IPSetGetResponseData.
|
|
||||||
type IPSetContent []IPSetGetResponseData
|
|
@ -1,60 +0,0 @@
|
|||||||
/*
|
|
||||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package firewall
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
|
|
||||||
|
|
||||||
"github.com/bpg/terraform-provider-proxmox/proxmox/api"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Options is an interface for the Proxmox firewall options API.
|
|
||||||
type Options interface {
|
|
||||||
GetOptionsID() string
|
|
||||||
SetOptions(ctx context.Context, d *OptionsPutRequestBody) error
|
|
||||||
GetOptions(ctx context.Context) (*OptionsGetResponseData, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) optionsPath() string {
|
|
||||||
return c.ExpandPath("firewall/options")
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetOptionsID returns the ID of the options object.
|
|
||||||
func (c *Client) GetOptionsID() string {
|
|
||||||
return "options-" + strconv.Itoa(schema.HashString(c.optionsPath()))
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetOptions sets the options object.
|
|
||||||
func (c *Client) SetOptions(ctx context.Context, d *OptionsPutRequestBody) error {
|
|
||||||
err := c.DoRequest(ctx, http.MethodPut, c.optionsPath(), d, nil)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error setting optionss: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetOptions retrieves the options object.
|
|
||||||
func (c *Client) GetOptions(ctx context.Context) (*OptionsGetResponseData, error) {
|
|
||||||
resBody := &OptionsGetResponseBody{}
|
|
||||||
|
|
||||||
err := c.DoRequest(ctx, http.MethodGet, c.optionsPath(), nil, resBody)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error retrieving options: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if resBody.Data == nil {
|
|
||||||
return nil, api.ErrNoDataObjectInResponse
|
|
||||||
}
|
|
||||||
|
|
||||||
return resBody.Data, nil
|
|
||||||
}
|
|
@ -1,44 +0,0 @@
|
|||||||
/*
|
|
||||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package firewall
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/bpg/terraform-provider-proxmox/internal/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
// OptionsPutRequestBody is the request body for the PUT /cluster/firewall/options API call.
|
|
||||||
type OptionsPutRequestBody struct {
|
|
||||||
DHCP *types.CustomBool `json:"dhcp,omitempty" url:"dhcp,omitempty,int"`
|
|
||||||
Enable *types.CustomBool `json:"enable,omitempty" url:"enable,omitempty,int"`
|
|
||||||
IPFilter *types.CustomBool `json:"ipfilter,omitempty" url:"ipfilter,omitempty,int"`
|
|
||||||
LogLevelIN *string `json:"log_level_in,omitempty" url:"log_level_in,omitempty"`
|
|
||||||
LogLevelOUT *string `json:"log_level_out,omitempty" url:"log_level_out,omitempty"`
|
|
||||||
MACFilter *types.CustomBool `json:"macfilter,omitempty" url:"macfilter,omitempty,int"`
|
|
||||||
NDP *types.CustomBool `json:"ndp,omitempty" url:"ndp,omitempty,int"`
|
|
||||||
PolicyIn *string `json:"policy_in,omitempty" url:"policy_in,omitempty"`
|
|
||||||
PolicyOut *string `json:"policy_out,omitempty" url:"policy_out,omitempty"`
|
|
||||||
RAdv *types.CustomBool `json:"radv,omitempty" url:"radv,omitempty,int"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// OptionsGetResponseBody is the response body for the GET /cluster/firewall/options API call.
|
|
||||||
type OptionsGetResponseBody struct {
|
|
||||||
Data *OptionsGetResponseData `json:"data,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// OptionsGetResponseData is the data field of the response body for the GET /cluster/firewall/options API call.
|
|
||||||
type OptionsGetResponseData struct {
|
|
||||||
DHCP *types.CustomBool `json:"dhcp" url:"dhcp,int"`
|
|
||||||
Enable *types.CustomBool `json:"enable" url:"enable,int"`
|
|
||||||
IPFilter *types.CustomBool `json:"ipfilter" url:"ipfilter,int"`
|
|
||||||
LogLevelIN *string `json:"log_level_in" url:"log_level_in"`
|
|
||||||
LogLevelOUT *string `json:"log_level_out" url:"log_level_out"`
|
|
||||||
MACFilter *types.CustomBool `json:"macfilter" url:"macfilter,int"`
|
|
||||||
NDP *types.CustomBool `json:"ndp" url:"ndp,int"`
|
|
||||||
PolicyIn *string `json:"policy_in" url:"policy_in"`
|
|
||||||
PolicyOut *string `json:"policy_out" url:"policy_out"`
|
|
||||||
RAdv *types.CustomBool `json:"radv" url:"radv,int"`
|
|
||||||
}
|
|
@ -1,120 +0,0 @@
|
|||||||
/*
|
|
||||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package firewall
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
|
|
||||||
|
|
||||||
"github.com/bpg/terraform-provider-proxmox/internal/types"
|
|
||||||
"github.com/bpg/terraform-provider-proxmox/proxmox/api"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Rule is an interface for the Proxmox firewall rule API.
|
|
||||||
type Rule interface {
|
|
||||||
GetRulesID() string
|
|
||||||
CreateRule(ctx context.Context, d *RuleCreateRequestBody) error
|
|
||||||
GetRule(ctx context.Context, pos int) (*RuleGetResponseData, error)
|
|
||||||
ListRules(ctx context.Context) ([]*RuleListResponseData, error)
|
|
||||||
UpdateRule(ctx context.Context, pos int, d *RuleUpdateRequestBody) error
|
|
||||||
DeleteRule(ctx context.Context, pos int) error
|
|
||||||
}
|
|
||||||
|
|
||||||
// BaseRule is the base struct for firewall rules.
|
|
||||||
type BaseRule struct {
|
|
||||||
Comment *string `json:"comment,omitempty" url:"comment,omitempty"`
|
|
||||||
Dest *string `json:"dest,omitempty" url:"dest,omitempty"`
|
|
||||||
Digest *string `json:"digest,omitempty" url:"digest,omitempty"`
|
|
||||||
DPort *string `json:"dport,omitempty" url:"dport,omitempty"`
|
|
||||||
Enable *types.CustomBool `json:"enable,omitempty" url:"enable,omitempty,int"`
|
|
||||||
ICMPType *string `json:"icmp-type,omitempty" url:"icmp-type,omitempty"`
|
|
||||||
IFace *string `json:"iface,omitempty" url:"iface,omitempty"`
|
|
||||||
Log *string `json:"log,omitempty" url:"log,omitempty"`
|
|
||||||
Macro *string `json:"macro,omitempty" url:"macro,omitempty"`
|
|
||||||
Proto *string `json:"proto,omitempty" url:"proto,omitempty"`
|
|
||||||
Source *string `json:"source,omitempty" url:"source,omitempty"`
|
|
||||||
SPort *string `json:"sport,omitempty" url:"sport,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) rulesPath() string {
|
|
||||||
return c.ExpandPath("firewall/rules")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) rulePath(pos int) string {
|
|
||||||
return fmt.Sprintf("%s/%d", c.rulesPath(), pos)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetRulesID returns the ID of the rules object.
|
|
||||||
func (c *Client) GetRulesID() string {
|
|
||||||
return "rule-" + strconv.Itoa(schema.HashString(c.rulesPath()))
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateRule creates a firewall rule.
|
|
||||||
func (c *Client) CreateRule(ctx context.Context, d *RuleCreateRequestBody) error {
|
|
||||||
err := c.DoRequest(ctx, http.MethodPost, c.rulesPath(), d, nil)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error creating firewall rule: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetRule retrieves a firewall rule.
|
|
||||||
func (c *Client) GetRule(ctx context.Context, pos int) (*RuleGetResponseData, error) {
|
|
||||||
resBody := &RuleGetResponseBody{}
|
|
||||||
|
|
||||||
err := c.DoRequest(ctx, http.MethodGet, c.rulePath(pos), nil, resBody)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error retrieving firewall rule %d: %w", pos, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if resBody.Data == nil {
|
|
||||||
return nil, api.ErrNoDataObjectInResponse
|
|
||||||
}
|
|
||||||
|
|
||||||
return resBody.Data, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListRules retrieves a list of firewall rules.
|
|
||||||
func (c *Client) ListRules(ctx context.Context) ([]*RuleListResponseData, error) {
|
|
||||||
resBody := &RuleListResponseBody{}
|
|
||||||
|
|
||||||
err := c.DoRequest(ctx, http.MethodGet, c.rulesPath(), nil, resBody)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error retrieving firewall rules: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if resBody.Data == nil {
|
|
||||||
return nil, api.ErrNoDataObjectInResponse
|
|
||||||
}
|
|
||||||
|
|
||||||
return resBody.Data, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// UpdateRule updates a firewall rule.
|
|
||||||
func (c *Client) UpdateRule(ctx context.Context, pos int, d *RuleUpdateRequestBody) error {
|
|
||||||
err := c.DoRequest(ctx, http.MethodPut, c.rulePath(pos), d, nil)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error updating firewall rule %d: %w", pos, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteRule deletes a firewall rule.
|
|
||||||
func (c *Client) DeleteRule(ctx context.Context, pos int) error {
|
|
||||||
err := c.DoRequest(ctx, http.MethodDelete, c.rulePath(pos), nil, nil)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error deleting firewall rule %d: %w", pos, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
@ -1,53 +0,0 @@
|
|||||||
/*
|
|
||||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package firewall
|
|
||||||
|
|
||||||
// RuleCreateRequestBody contains the data for a firewall rule create request.
|
|
||||||
type RuleCreateRequestBody struct {
|
|
||||||
BaseRule
|
|
||||||
|
|
||||||
Action string `json:"action" url:"action"`
|
|
||||||
Type string `json:"type" url:"type"`
|
|
||||||
|
|
||||||
Group *string `json:"group,omitempty" url:"group,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// RuleGetResponseBody contains the body from a firewall rule get response.
|
|
||||||
type RuleGetResponseBody struct {
|
|
||||||
Data *RuleGetResponseData `json:"data,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// RuleGetResponseData contains the data from a firewall rule get response.
|
|
||||||
type RuleGetResponseData struct {
|
|
||||||
BaseRule
|
|
||||||
|
|
||||||
// NOTE: This is `int` in the PVE API docs, but it's actually a string in the response.
|
|
||||||
Pos string `json:"pos" url:"pos"`
|
|
||||||
Action string `json:"action" url:"action"`
|
|
||||||
Type string `json:"type" url:"type"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// RuleListResponseBody contains the data from a firewall rule get response.
|
|
||||||
type RuleListResponseBody struct {
|
|
||||||
Data []*RuleListResponseData `json:"data,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// RuleListResponseData contains the data from a firewall rule get response.
|
|
||||||
type RuleListResponseData struct {
|
|
||||||
Pos int `json:"pos" url:"pos"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// RuleUpdateRequestBody contains the data for a firewall rule update request.
|
|
||||||
type RuleUpdateRequestBody struct {
|
|
||||||
BaseRule
|
|
||||||
|
|
||||||
Pos *int `json:"pos,omitempty" url:"pos,omitempty"`
|
|
||||||
Action *string `json:"action,omitempty" url:"action,omitempty"`
|
|
||||||
Type *string `json:"type,omitempty" url:"type,omitempty"`
|
|
||||||
|
|
||||||
Group *string `json:"group,omitempty" url:"group,omitempty"`
|
|
||||||
}
|
|
@ -1,51 +0,0 @@
|
|||||||
/*
|
|
||||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package nodes
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/bpg/terraform-provider-proxmox/proxmox/api"
|
|
||||||
)
|
|
||||||
|
|
||||||
// DeleteCertificate deletes the custom certificate for a node.
|
|
||||||
func (c *Client) DeleteCertificate(ctx context.Context, d *CertificateDeleteRequestBody) error {
|
|
||||||
err := c.DoRequest(ctx, http.MethodDelete, c.ExpandPath("certificates/custom"), d, nil)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error deleting certificate: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListCertificates retrieves the list of certificates for a node.
|
|
||||||
func (c *Client) ListCertificates(ctx context.Context) (*[]CertificateListResponseData, error) {
|
|
||||||
resBody := &CertificateListResponseBody{}
|
|
||||||
|
|
||||||
err := c.DoRequest(ctx, http.MethodGet, c.ExpandPath("certificates/info"), nil, resBody)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error retrieving certificate list: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if resBody.Data == nil {
|
|
||||||
return nil, api.ErrNoDataObjectInResponse
|
|
||||||
}
|
|
||||||
|
|
||||||
return resBody.Data, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// UpdateCertificate updates the custom certificate for a node.
|
|
||||||
func (c *Client) UpdateCertificate(ctx context.Context, d *CertificateUpdateRequestBody) error {
|
|
||||||
err := c.DoRequest(ctx, http.MethodPost, c.ExpandPath("certificates/custom"), d, nil)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error updating certificate: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
@ -1,43 +0,0 @@
|
|||||||
/*
|
|
||||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package nodes
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/bpg/terraform-provider-proxmox/internal/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CertificateDeleteRequestBody contains the data for a custom certificate delete request.
|
|
||||||
type CertificateDeleteRequestBody struct {
|
|
||||||
Restart *types.CustomBool `json:"restart,omitempty" url:"restart,omitempty,int"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// CertificateListResponseBody contains the body from a certificate list response.
|
|
||||||
type CertificateListResponseBody struct {
|
|
||||||
Data *[]CertificateListResponseData `json:"data,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// CertificateListResponseData contains the data from a certificate list response.
|
|
||||||
type CertificateListResponseData struct {
|
|
||||||
Certificates *string `json:"pem,omitempty"`
|
|
||||||
FileName *string `json:"filename,omitempty"`
|
|
||||||
Fingerprint *string `json:"fingerprint,omitempty"`
|
|
||||||
Issuer *string `json:"issuer,omitempty"`
|
|
||||||
NotAfter *types.CustomTimestamp `json:"notafter,omitempty"`
|
|
||||||
NotBefore *types.CustomTimestamp `json:"notbefore,omitempty"`
|
|
||||||
PublicKeyBits *int `json:"public-key-bits,omitempty"`
|
|
||||||
PublicKeyType *string `json:"public-key-type,omitempty"`
|
|
||||||
Subject *string `json:"subject,omitempty"`
|
|
||||||
SubjectAlternativeNames *[]string `json:"san,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// CertificateUpdateRequestBody contains the body for a custom certificate update request.
|
|
||||||
type CertificateUpdateRequestBody struct {
|
|
||||||
Certificates string `json:"certificates" url:"certificates"`
|
|
||||||
Force *types.CustomBool `json:"force,omitempty" url:"force,omitempty,int"`
|
|
||||||
PrivateKey *string `json:"key,omitempty" url:"key,omitempty"`
|
|
||||||
Restart *types.CustomBool `json:"restart,omitempty" url:"restart,omitempty,int"`
|
|
||||||
}
|
|
@ -1,51 +0,0 @@
|
|||||||
/*
|
|
||||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package nodes
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net/url"
|
|
||||||
|
|
||||||
"github.com/bpg/terraform-provider-proxmox/proxmox/api"
|
|
||||||
"github.com/bpg/terraform-provider-proxmox/proxmox/nodes/containers"
|
|
||||||
"github.com/bpg/terraform-provider-proxmox/proxmox/nodes/tasks"
|
|
||||||
"github.com/bpg/terraform-provider-proxmox/proxmox/nodes/vms"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Client is an interface for accessing the Proxmox node API.
|
|
||||||
type Client struct {
|
|
||||||
api.Client
|
|
||||||
NodeName string
|
|
||||||
}
|
|
||||||
|
|
||||||
// ExpandPath expands a relative path to a full node API path.
|
|
||||||
func (c *Client) ExpandPath(path string) string {
|
|
||||||
return fmt.Sprintf("nodes/%s/%s", url.PathEscape(c.NodeName), path)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Container returns a client for managing a specific container.
|
|
||||||
func (c *Client) Container(vmID int) *containers.Client {
|
|
||||||
return &containers.Client{
|
|
||||||
Client: c,
|
|
||||||
VMID: vmID,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// VM returns a client for managing a specific VM.
|
|
||||||
func (c *Client) VM(vmID int) *vms.Client {
|
|
||||||
return &vms.Client{
|
|
||||||
Client: c,
|
|
||||||
VMID: vmID,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tasks returns a client for managing VM tasks.
|
|
||||||
func (c *Client) Tasks() *tasks.Client {
|
|
||||||
return &tasks.Client{
|
|
||||||
Client: c,
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,42 +0,0 @@
|
|||||||
/*
|
|
||||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package containers
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/bpg/terraform-provider-proxmox/proxmox/api"
|
|
||||||
"github.com/bpg/terraform-provider-proxmox/proxmox/firewall"
|
|
||||||
containerfirewall "github.com/bpg/terraform-provider-proxmox/proxmox/nodes/containers/firewall"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Client is an interface for accessing the Proxmox container API.
|
|
||||||
type Client struct {
|
|
||||||
api.Client
|
|
||||||
VMID int
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) basePath() string {
|
|
||||||
return c.Client.ExpandPath("lxc")
|
|
||||||
}
|
|
||||||
|
|
||||||
// ExpandPath expands a relative path to a full container API path.
|
|
||||||
func (c *Client) ExpandPath(path string) string {
|
|
||||||
ep := fmt.Sprintf("%s/%d", c.basePath(), c.VMID)
|
|
||||||
if path != "" {
|
|
||||||
ep = fmt.Sprintf("%s/%s", ep, path)
|
|
||||||
}
|
|
||||||
|
|
||||||
return ep
|
|
||||||
}
|
|
||||||
|
|
||||||
// Firewall returns a client for managing the container firewall.
|
|
||||||
func (c *Client) Firewall() firewall.API {
|
|
||||||
return &containerfirewall.Client{
|
|
||||||
Client: firewall.Client{Client: c},
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,202 +0,0 @@
|
|||||||
/*
|
|
||||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package containers
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/bpg/terraform-provider-proxmox/proxmox/api"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CloneContainer clones a container.
|
|
||||||
func (c *Client) CloneContainer(ctx context.Context, d *CloneRequestBody) error {
|
|
||||||
err := c.DoRequest(ctx, http.MethodPost, c.ExpandPath("/clone"), d, nil)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error cloning container: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateContainer creates a container.
|
|
||||||
func (c *Client) CreateContainer(ctx context.Context, d *CreateRequestBody) error {
|
|
||||||
err := c.DoRequest(ctx, http.MethodPost, c.basePath(), d, nil)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error creating container: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteContainer deletes a container.
|
|
||||||
func (c *Client) DeleteContainer(ctx context.Context) error {
|
|
||||||
err := c.DoRequest(ctx, http.MethodDelete, c.ExpandPath(""), nil, nil)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error deleting container: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetContainer retrieves a container.
|
|
||||||
func (c *Client) GetContainer(ctx context.Context) (*GetResponseData, error) {
|
|
||||||
resBody := &GetResponseBody{}
|
|
||||||
|
|
||||||
err := c.DoRequest(ctx, http.MethodGet, c.ExpandPath("config"), nil, resBody)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error retrieving container: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if resBody.Data == nil {
|
|
||||||
return nil, api.ErrNoDataObjectInResponse
|
|
||||||
}
|
|
||||||
|
|
||||||
return resBody.Data, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetContainerStatus retrieves the status for a container.
|
|
||||||
func (c *Client) GetContainerStatus(ctx context.Context) (*GetStatusResponseData, error) {
|
|
||||||
resBody := &GetStatusResponseBody{}
|
|
||||||
|
|
||||||
err := c.DoRequest(ctx, http.MethodGet, c.ExpandPath("status/current"), nil, resBody)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error retrieving container status: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if resBody.Data == nil {
|
|
||||||
return nil, api.ErrNoDataObjectInResponse
|
|
||||||
}
|
|
||||||
|
|
||||||
return resBody.Data, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// RebootContainer reboots a container.
|
|
||||||
func (c *Client) RebootContainer(ctx context.Context, d *RebootRequestBody) error {
|
|
||||||
err := c.DoRequest(ctx, http.MethodPost, c.ExpandPath("status/reboot"), d, nil)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error rebooting container: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ShutdownContainer shuts down a container.
|
|
||||||
func (c *Client) ShutdownContainer(ctx context.Context, d *ShutdownRequestBody) error {
|
|
||||||
err := c.DoRequest(ctx, http.MethodPost, c.ExpandPath("status/shutdown"), d, nil)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error shutting down container: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// StartContainer starts a container.
|
|
||||||
func (c *Client) StartContainer(ctx context.Context) error {
|
|
||||||
err := c.DoRequest(ctx, http.MethodPost, c.ExpandPath("status/start"), nil, nil)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error starting container: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// StopContainer stops a container immediately.
|
|
||||||
func (c *Client) StopContainer(ctx context.Context) error {
|
|
||||||
err := c.DoRequest(ctx, http.MethodPost, c.ExpandPath("status/stop"), nil, nil)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error stopping container: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// UpdateContainer updates a container.
|
|
||||||
func (c *Client) UpdateContainer(ctx context.Context, d *UpdateRequestBody) error {
|
|
||||||
err := c.DoRequest(ctx, http.MethodPut, c.ExpandPath("config"), d, nil)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error updating container: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// WaitForContainerState waits for a container to reach a specific state.
|
|
||||||
func (c *Client) WaitForContainerState(ctx context.Context, state string, timeout int, delay int) error {
|
|
||||||
state = strings.ToLower(state)
|
|
||||||
|
|
||||||
timeDelay := int64(delay)
|
|
||||||
timeMax := float64(timeout)
|
|
||||||
timeStart := time.Now()
|
|
||||||
timeElapsed := timeStart.Sub(timeStart)
|
|
||||||
|
|
||||||
for timeElapsed.Seconds() < timeMax {
|
|
||||||
if int64(timeElapsed.Seconds())%timeDelay == 0 {
|
|
||||||
data, err := c.GetContainerStatus(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error retrieving container status: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if data.Status == state {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
time.Sleep(1 * time.Second)
|
|
||||||
}
|
|
||||||
|
|
||||||
time.Sleep(200 * time.Millisecond)
|
|
||||||
|
|
||||||
timeElapsed = time.Since(timeStart)
|
|
||||||
|
|
||||||
if ctx.Err() != nil {
|
|
||||||
return fmt.Errorf("context error: %w", ctx.Err())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Errorf(
|
|
||||||
"timeout while waiting for container \"%d\" to enter the state \"%s\"",
|
|
||||||
c.VMID,
|
|
||||||
state,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// WaitForContainerLock waits for a container lock to be released.
|
|
||||||
func (c *Client) WaitForContainerLock(ctx context.Context, timeout int, delay int, ignoreErrorResponse bool) error {
|
|
||||||
timeDelay := int64(delay)
|
|
||||||
timeMax := float64(timeout)
|
|
||||||
timeStart := time.Now()
|
|
||||||
timeElapsed := timeStart.Sub(timeStart)
|
|
||||||
|
|
||||||
for timeElapsed.Seconds() < timeMax {
|
|
||||||
if int64(timeElapsed.Seconds())%timeDelay == 0 {
|
|
||||||
data, err := c.GetContainerStatus(ctx)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
if !ignoreErrorResponse {
|
|
||||||
return fmt.Errorf("error retrieving container status: %w", err)
|
|
||||||
}
|
|
||||||
} else if data.Lock == nil || *data.Lock == "" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
time.Sleep(1 * time.Second)
|
|
||||||
}
|
|
||||||
|
|
||||||
time.Sleep(200 * time.Millisecond)
|
|
||||||
|
|
||||||
timeElapsed = time.Since(timeStart)
|
|
||||||
|
|
||||||
if ctx.Err() != nil {
|
|
||||||
return fmt.Errorf("context error: %w", ctx.Err())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Errorf("timeout while waiting for container \"%d\" to become unlocked", c.VMID)
|
|
||||||
}
|
|
@ -1,811 +0,0 @@
|
|||||||
/*
|
|
||||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package containers
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"net/url"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/bpg/terraform-provider-proxmox/internal/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CloneRequestBody contains the data for an container clone request.
|
|
||||||
type CloneRequestBody struct {
|
|
||||||
BandwidthLimit *int `json:"bwlimit,omitempty" url:"bwlimit,omitempty"`
|
|
||||||
Description *string `json:"description,omitempty" url:"description,omitempty"`
|
|
||||||
FullCopy *types.CustomBool `json:"full,omitempty" url:"full,omitempty,int"`
|
|
||||||
Hostname *string `json:"hostname,omitempty" url:"hostname,omitempty"`
|
|
||||||
PoolID *string `json:"pool,omitempty" url:"pool,omitempty"`
|
|
||||||
SnapshotName *string `json:"snapname,omitempty" url:"snapname,omitempty"`
|
|
||||||
TargetNodeName *string `json:"target,omitempty" url:"target,omitempty"`
|
|
||||||
TargetStorage *string `json:"storage,omitempty" url:"storage,omitempty"`
|
|
||||||
VMIDNew int `json:"newid" url:"newid"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateRequestBody contains the data for a user create request.
|
|
||||||
type CreateRequestBody struct {
|
|
||||||
BandwidthLimit *float64 `json:"bwlimit,omitempty" url:"bwlimit,omitempty"`
|
|
||||||
ConsoleEnabled *types.CustomBool `json:"console,omitempty" url:"console,omitempty,int"`
|
|
||||||
ConsoleMode *string `json:"cmode,omitempty" url:"cmode,omitempty"`
|
|
||||||
CPUArchitecture *string `json:"arch,omitempty" url:"arch,omitempty"`
|
|
||||||
CPUCores *int `json:"cores,omitempty" url:"cores,omitempty"`
|
|
||||||
CPULimit *int `json:"cpulimit,omitempty" url:"cpulimit,omitempty"`
|
|
||||||
CPUUnits *int `json:"cpuunits,omitempty" url:"cpuunits,omitempty"`
|
|
||||||
DatastoreID *string `json:"storage,omitempty" url:"storage,omitempty"`
|
|
||||||
DedicatedMemory *int `json:"memory,omitempty" url:"memory,omitempty"`
|
|
||||||
Delete []string `json:"delete,omitempty" url:"delete,omitempty"`
|
|
||||||
Description *string `json:"description,omitempty" url:"description,omitempty"`
|
|
||||||
DNSDomain *string `json:"searchdomain,omitempty" url:"searchdomain,omitempty"`
|
|
||||||
DNSServer *string `json:"nameserver,omitempty" url:"nameserver,omitempty"`
|
|
||||||
Features *CustomFeatures `json:"features,omitempty" url:"features,omitempty"`
|
|
||||||
Force *types.CustomBool `json:"force,omitempty" url:"force,omitempty,int"`
|
|
||||||
HookScript *string `json:"hookscript,omitempty" url:"hookscript,omitempty"`
|
|
||||||
Hostname *string `json:"hostname,omitempty" url:"hostname,omitempty"`
|
|
||||||
IgnoreUnpackErrors *types.CustomBool `json:"ignore-unpack-errors,omitempty" url:"force,omitempty,int"`
|
|
||||||
Lock *string `json:"lock,omitempty" url:"lock,omitempty,int"`
|
|
||||||
MountPoints CustomMountPointArray `json:"mp,omitempty" url:"mp,omitempty,numbered"`
|
|
||||||
NetworkInterfaces CustomNetworkInterfaceArray `json:"net,omitempty" url:"net,omitempty,numbered"`
|
|
||||||
OSTemplateFileVolume *string `json:"ostemplate,omitempty" url:"ostemplate,omitempty"`
|
|
||||||
OSType *string `json:"ostype,omitempty" url:"ostype,omitempty"`
|
|
||||||
Password *string `json:"password,omitempty" url:"password,omitempty"`
|
|
||||||
PoolID *string `json:"pool,omitempty" url:"pool,omitempty"`
|
|
||||||
Protection *types.CustomBool `json:"protection,omitempty" url:"protection,omitempty,int"`
|
|
||||||
Restore *types.CustomBool `json:"restore,omitempty" url:"restore,omitempty,int"`
|
|
||||||
RootFS *CustomRootFS `json:"rootfs,omitempty" url:"rootfs,omitempty"`
|
|
||||||
SSHKeys *CustomSSHKeys `json:"ssh-public-keys,omitempty" url:"ssh-public-keys,omitempty"`
|
|
||||||
Start *types.CustomBool `json:"start,omitempty" url:"start,omitempty,int"`
|
|
||||||
StartOnBoot *types.CustomBool `json:"onboot,omitempty" url:"onboot,omitempty,int"`
|
|
||||||
StartupBehavior *CustomStartupBehavior `json:"startup,omitempty" url:"startup,omitempty"`
|
|
||||||
Swap *int `json:"swap,omitempty" url:"swap,omitempty"`
|
|
||||||
Tags *string `json:"tags,omitempty" url:"tags,omitempty"`
|
|
||||||
Template *types.CustomBool `json:"template,omitempty" url:"template,omitempty,int"`
|
|
||||||
TTY *int `json:"tty,omitempty" url:"tty,omitempty"`
|
|
||||||
Unique *types.CustomBool `json:"unique,omitempty" url:"unique,omitempty,int"`
|
|
||||||
Unprivileged *types.CustomBool `json:"unprivileged,omitempty" url:"unprivileged,omitempty,int"`
|
|
||||||
VMID *int `json:"vmid,omitempty" url:"vmid,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// CustomFeatures contains the values for the "features" property.
|
|
||||||
type CustomFeatures struct {
|
|
||||||
FUSE *types.CustomBool `json:"fuse,omitempty" url:"fuse,omitempty,int"`
|
|
||||||
KeyControl *types.CustomBool `json:"keyctl,omitempty" url:"keyctl,omitempty,int"`
|
|
||||||
MountTypes *[]string `json:"mount,omitempty" url:"mount,omitempty"`
|
|
||||||
Nesting *types.CustomBool `json:"nesting,omitempty" url:"nesting,omitempty,int"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// CustomMountPoint contains the values for the "mp[n]" properties.
|
|
||||||
type CustomMountPoint struct {
|
|
||||||
ACL *types.CustomBool `json:"acl,omitempty" url:"acl,omitempty,int"`
|
|
||||||
Backup *types.CustomBool `json:"backup,omitempty" url:"backup,omitempty,int"`
|
|
||||||
DiskSize *string `json:"size,omitempty" url:"size,omitempty"`
|
|
||||||
Enabled bool `json:"-" url:"-"`
|
|
||||||
MountOptions *[]string `json:"mountoptions,omitempty" url:"mountoptions,omitempty"`
|
|
||||||
MountPoint string `json:"mp" url:"mp"`
|
|
||||||
Quota *types.CustomBool `json:"quota,omitempty" url:"quota,omitempty,int"`
|
|
||||||
ReadOnly *types.CustomBool `json:"ro,omitempty" url:"ro,omitempty,int"`
|
|
||||||
Replicate *types.CustomBool `json:"replicate,omitempty" url:"replicate,omitempty,int"`
|
|
||||||
Shared *types.CustomBool `json:"shared,omitempty" url:"shared,omitempty,int"`
|
|
||||||
Volume string `json:"volume" url:"volume"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// CustomMountPointArray is an array of CustomMountPoint.
|
|
||||||
type CustomMountPointArray []CustomMountPoint
|
|
||||||
|
|
||||||
// CustomNetworkInterface contains the values for the "net[n]" properties.
|
|
||||||
type CustomNetworkInterface struct {
|
|
||||||
Bridge *string `json:"bridge,omitempty" url:"bridge,omitempty"`
|
|
||||||
Enabled bool `json:"-" url:"-"`
|
|
||||||
Firewall *types.CustomBool `json:"firewall,omitempty" url:"firewall,omitempty,int"`
|
|
||||||
IPv4Address *string `json:"ip,omitempty" url:"ip,omitempty"`
|
|
||||||
IPv4Gateway *string `json:"gw,omitempty" url:"gw,omitempty"`
|
|
||||||
IPv6Address *string `json:"ip6,omitempty" url:"ip6,omitempty"`
|
|
||||||
IPv6Gateway *string `json:"gw6,omitempty" url:"gw6,omitempty"`
|
|
||||||
MACAddress *string `json:"hwaddr,omitempty" url:"hwaddr,omitempty"`
|
|
||||||
MTU *int `json:"mtu,omitempty" url:"mtu,omitempty"`
|
|
||||||
Name string `json:"name" url:"name"`
|
|
||||||
RateLimit *float64 `json:"rate,omitempty" url:"rate,omitempty"`
|
|
||||||
Tag *int `json:"tag,omitempty" url:"tag,omitempty"`
|
|
||||||
Trunks *[]int `json:"trunks,omitempty" url:"trunks,omitempty"`
|
|
||||||
Type *string `json:"type,omitempty" url:"type,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// CustomNetworkInterfaceArray is an array of CustomNetworkInterface.
|
|
||||||
type CustomNetworkInterfaceArray []CustomNetworkInterface
|
|
||||||
|
|
||||||
// CustomRootFS contains the values for the "rootfs" property.
|
|
||||||
type CustomRootFS struct {
|
|
||||||
ACL *types.CustomBool `json:"acl,omitempty" url:"acl,omitempty,int"`
|
|
||||||
Size *types.DiskSize `json:"size,omitempty" url:"size,omitempty"`
|
|
||||||
MountOptions *[]string `json:"mountoptions,omitempty" url:"mountoptions,omitempty"`
|
|
||||||
Quota *types.CustomBool `json:"quota,omitempty" url:"quota,omitempty,int"`
|
|
||||||
ReadOnly *types.CustomBool `json:"ro,omitempty" url:"ro,omitempty,int"`
|
|
||||||
Replicate *types.CustomBool `json:"replicate,omitempty" url:"replicate,omitempty,int"`
|
|
||||||
Shared *types.CustomBool `json:"shared,omitempty" url:"shared,omitempty,int"`
|
|
||||||
Volume string `json:"volume" url:"volume"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// CustomSSHKeys contains the values for the "ssh-public-keys" property.
|
|
||||||
type CustomSSHKeys []string
|
|
||||||
|
|
||||||
// CustomStartupBehavior contains the values for the "startup" property.
|
|
||||||
type CustomStartupBehavior struct {
|
|
||||||
Down *int `json:"down,omitempty" url:"down,omitempty"`
|
|
||||||
Order *int `json:"order,omitempty" url:"order,omitempty"`
|
|
||||||
Up *int `json:"up,omitempty" url:"up,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetResponseBody contains the body from a user get response.
|
|
||||||
type GetResponseBody struct {
|
|
||||||
Data *GetResponseData `json:"data,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetResponseData contains the data from a user get response.
|
|
||||||
type GetResponseData struct {
|
|
||||||
ConsoleEnabled *types.CustomBool `json:"console,omitempty"`
|
|
||||||
ConsoleMode *string `json:"cmode,omitempty"`
|
|
||||||
CPUArchitecture *string `json:"arch,omitempty"`
|
|
||||||
CPUCores *int `json:"cores,omitempty"`
|
|
||||||
CPULimit *int `json:"cpulimit,omitempty"`
|
|
||||||
CPUUnits *int `json:"cpuunits,omitempty"`
|
|
||||||
DedicatedMemory *int `json:"memory,omitempty"`
|
|
||||||
Description *string `json:"description,omitempty"`
|
|
||||||
Digest string `json:"digest"`
|
|
||||||
DNSDomain *string `json:"searchdomain,omitempty"`
|
|
||||||
DNSServer *string `json:"nameserver,omitempty"`
|
|
||||||
Features *CustomFeatures `json:"features,omitempty"`
|
|
||||||
HookScript *string `json:"hookscript,omitempty"`
|
|
||||||
Hostname *string `json:"hostname,omitempty"`
|
|
||||||
Lock *types.CustomBool `json:"lock,omitempty"`
|
|
||||||
LXCConfiguration *[][2]string `json:"lxc,omitempty"`
|
|
||||||
MountPoint0 CustomMountPoint `json:"mp0,omitempty"`
|
|
||||||
MountPoint1 CustomMountPoint `json:"mp1,omitempty"`
|
|
||||||
MountPoint2 CustomMountPoint `json:"mp2,omitempty"`
|
|
||||||
MountPoint3 CustomMountPoint `json:"mp3,omitempty"`
|
|
||||||
NetworkInterface0 *CustomNetworkInterface `json:"net0,omitempty"`
|
|
||||||
NetworkInterface1 *CustomNetworkInterface `json:"net1,omitempty"`
|
|
||||||
NetworkInterface2 *CustomNetworkInterface `json:"net2,omitempty"`
|
|
||||||
NetworkInterface3 *CustomNetworkInterface `json:"net3,omitempty"`
|
|
||||||
NetworkInterface4 *CustomNetworkInterface `json:"net4,omitempty"`
|
|
||||||
NetworkInterface5 *CustomNetworkInterface `json:"net5,omitempty"`
|
|
||||||
NetworkInterface6 *CustomNetworkInterface `json:"net6,omitempty"`
|
|
||||||
NetworkInterface7 *CustomNetworkInterface `json:"net7,omitempty"`
|
|
||||||
OSType *string `json:"ostype,omitempty"`
|
|
||||||
Protection *types.CustomBool `json:"protection,omitempty"`
|
|
||||||
RootFS *CustomRootFS `json:"rootfs,omitempty"`
|
|
||||||
StartOnBoot *types.CustomBool `json:"onboot,omitempty"`
|
|
||||||
StartupBehavior *CustomStartupBehavior `json:"startup,omitempty"`
|
|
||||||
Swap *int `json:"swap,omitempty"`
|
|
||||||
Tags *string `json:"tags,omitempty"`
|
|
||||||
Template *types.CustomBool `json:"template,omitempty"`
|
|
||||||
TTY *int `json:"tty,omitempty"`
|
|
||||||
Unprivileged *types.CustomBool `json:"unprivileged,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetStatusResponseBody contains the body from a container get status response.
|
|
||||||
type GetStatusResponseBody struct {
|
|
||||||
Data *GetStatusResponseData `json:"data,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetStatusResponseData contains the data from a container get status response.
|
|
||||||
type GetStatusResponseData struct {
|
|
||||||
CPUCount *float64 `json:"cpus,omitempty"`
|
|
||||||
Lock *string `json:"lock,omitempty"`
|
|
||||||
MemoryAllocation *int `json:"maxmem,omitempty"`
|
|
||||||
Name *string `json:"name,omitempty"`
|
|
||||||
RootDiskSize *interface{} `json:"maxdisk,omitempty"`
|
|
||||||
Status string `json:"status,omitempty"`
|
|
||||||
SwapAllocation *int `json:"maxswap,omitempty"`
|
|
||||||
Tags *string `json:"tags,omitempty"`
|
|
||||||
Uptime *int `json:"uptime,omitempty"`
|
|
||||||
VMID *int `json:"vmid,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// RebootRequestBody contains the body for a container reboot request.
|
|
||||||
type RebootRequestBody struct {
|
|
||||||
Timeout *int `json:"timeout,omitempty" url:"timeout,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// ShutdownRequestBody contains the body for a container shutdown request.
|
|
||||||
type ShutdownRequestBody struct {
|
|
||||||
ForceStop *types.CustomBool `json:"forceStop,omitempty" url:"forceStop,omitempty,int"`
|
|
||||||
Timeout *int `json:"timeout,omitempty" url:"timeout,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// UpdateRequestBody contains the data for an user update request.
|
|
||||||
type UpdateRequestBody CreateRequestBody
|
|
||||||
|
|
||||||
// EncodeValues converts a ContainerCustomFeatures struct to a URL value.
|
|
||||||
func (r *CustomFeatures) EncodeValues(key string, v *url.Values) error {
|
|
||||||
var values []string
|
|
||||||
|
|
||||||
if r.FUSE != nil {
|
|
||||||
if *r.FUSE {
|
|
||||||
values = append(values, "fuse=1")
|
|
||||||
} else {
|
|
||||||
values = append(values, "fuse=0")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if r.KeyControl != nil {
|
|
||||||
if *r.KeyControl {
|
|
||||||
values = append(values, "keyctl=1")
|
|
||||||
} else {
|
|
||||||
values = append(values, "keyctl=0")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if r.MountTypes != nil {
|
|
||||||
if len(*r.MountTypes) > 0 {
|
|
||||||
values = append(values, fmt.Sprintf("mount=%s", strings.Join(*r.MountTypes, ";")))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if r.Nesting != nil {
|
|
||||||
if *r.Nesting {
|
|
||||||
values = append(values, "nesting=1")
|
|
||||||
} else {
|
|
||||||
values = append(values, "nesting=0")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(values) > 0 {
|
|
||||||
v.Add(key, strings.Join(values, ","))
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// EncodeValues converts a CustomMountPoint struct to a URL value.
|
|
||||||
func (r *CustomMountPoint) EncodeValues(key string, v *url.Values) error {
|
|
||||||
var values []string
|
|
||||||
|
|
||||||
if r.ACL != nil {
|
|
||||||
if *r.ACL {
|
|
||||||
values = append(values, "acl=%d")
|
|
||||||
} else {
|
|
||||||
values = append(values, "acl=0")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if r.Backup != nil {
|
|
||||||
if *r.Backup {
|
|
||||||
values = append(values, "backup=1")
|
|
||||||
} else {
|
|
||||||
values = append(values, "backup=0")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if r.DiskSize != nil {
|
|
||||||
values = append(values, fmt.Sprintf("size=%s", *r.DiskSize))
|
|
||||||
}
|
|
||||||
|
|
||||||
if r.MountOptions != nil {
|
|
||||||
if len(*r.MountOptions) > 0 {
|
|
||||||
values = append(values, fmt.Sprintf("mount=%s", strings.Join(*r.MountOptions, ";")))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
values = append(values, fmt.Sprintf("mp=%s", r.MountPoint))
|
|
||||||
|
|
||||||
if r.Quota != nil {
|
|
||||||
if *r.Quota {
|
|
||||||
values = append(values, "quota=1")
|
|
||||||
} else {
|
|
||||||
values = append(values, "quota=0")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if r.ReadOnly != nil {
|
|
||||||
if *r.ReadOnly {
|
|
||||||
values = append(values, "ro=1")
|
|
||||||
} else {
|
|
||||||
values = append(values, "ro=0")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if r.Replicate != nil {
|
|
||||||
if *r.ReadOnly {
|
|
||||||
values = append(values, "replicate=1")
|
|
||||||
} else {
|
|
||||||
values = append(values, "replicate=0")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if r.Shared != nil {
|
|
||||||
if *r.Shared {
|
|
||||||
values = append(values, "shared=1")
|
|
||||||
} else {
|
|
||||||
values = append(values, "shared=0")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
values = append(values, fmt.Sprintf("volume=%s", r.Volume))
|
|
||||||
|
|
||||||
if len(values) > 0 {
|
|
||||||
v.Add(key, strings.Join(values, ","))
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// EncodeValues converts a CustomMountPointArray array to multiple URL values.
|
|
||||||
func (r CustomMountPointArray) EncodeValues(
|
|
||||||
key string,
|
|
||||||
v *url.Values,
|
|
||||||
) error {
|
|
||||||
for i, d := range r {
|
|
||||||
if err := d.EncodeValues(fmt.Sprintf("%s%d", key, i), v); err != nil {
|
|
||||||
return fmt.Errorf("failed to encode CustomMountPointArray: %w", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// EncodeValues converts a CustomNetworkInterface struct to a URL value.
|
|
||||||
func (r *CustomNetworkInterface) EncodeValues(
|
|
||||||
key string,
|
|
||||||
v *url.Values,
|
|
||||||
) error {
|
|
||||||
var values []string
|
|
||||||
|
|
||||||
if r.Bridge != nil {
|
|
||||||
values = append(values, fmt.Sprintf("bridge=%s", *r.Bridge))
|
|
||||||
}
|
|
||||||
|
|
||||||
if r.Firewall != nil {
|
|
||||||
if *r.Firewall {
|
|
||||||
values = append(values, "firewall=1")
|
|
||||||
} else {
|
|
||||||
values = append(values, "firewall=0")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if r.IPv4Address != nil {
|
|
||||||
values = append(values, fmt.Sprintf("ip=%s", *r.IPv4Address))
|
|
||||||
}
|
|
||||||
|
|
||||||
if r.IPv4Gateway != nil {
|
|
||||||
values = append(values, fmt.Sprintf("gw=%s", *r.IPv4Gateway))
|
|
||||||
}
|
|
||||||
|
|
||||||
if r.IPv6Address != nil {
|
|
||||||
values = append(values, fmt.Sprintf("ip6=%s", *r.IPv6Address))
|
|
||||||
}
|
|
||||||
|
|
||||||
if r.IPv6Gateway != nil {
|
|
||||||
values = append(values, fmt.Sprintf("gw6=%s", *r.IPv6Gateway))
|
|
||||||
}
|
|
||||||
|
|
||||||
if r.MACAddress != nil {
|
|
||||||
values = append(values, fmt.Sprintf("hwaddr=%s", *r.MACAddress))
|
|
||||||
}
|
|
||||||
|
|
||||||
if r.MTU != nil {
|
|
||||||
values = append(values, fmt.Sprintf("mtu=%d", *r.MTU))
|
|
||||||
}
|
|
||||||
|
|
||||||
values = append(values, fmt.Sprintf("name=%s", r.Name))
|
|
||||||
|
|
||||||
if r.RateLimit != nil {
|
|
||||||
values = append(values, fmt.Sprintf("rate=%.2f", *r.RateLimit))
|
|
||||||
}
|
|
||||||
|
|
||||||
if r.Tag != nil {
|
|
||||||
values = append(values, fmt.Sprintf("tag=%d", *r.Tag))
|
|
||||||
}
|
|
||||||
|
|
||||||
if r.Trunks != nil && len(*r.Trunks) > 0 {
|
|
||||||
sTrunks := make([]string, len(*r.Trunks))
|
|
||||||
|
|
||||||
for i, v := range *r.Trunks {
|
|
||||||
sTrunks[i] = strconv.Itoa(v)
|
|
||||||
}
|
|
||||||
|
|
||||||
values = append(values, fmt.Sprintf("trunks=%s", strings.Join(sTrunks, ";")))
|
|
||||||
}
|
|
||||||
|
|
||||||
if r.Type != nil {
|
|
||||||
values = append(values, fmt.Sprintf("type=%s", *r.Type))
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(values) > 0 {
|
|
||||||
v.Add(key, strings.Join(values, ","))
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// EncodeValues converts a CustomNetworkInterfaceArray array to multiple URL values.
|
|
||||||
func (r CustomNetworkInterfaceArray) EncodeValues(
|
|
||||||
key string,
|
|
||||||
v *url.Values,
|
|
||||||
) error {
|
|
||||||
for i, d := range r {
|
|
||||||
if err := d.EncodeValues(fmt.Sprintf("%s%d", key, i), v); err != nil {
|
|
||||||
return fmt.Errorf("failed to encode CustomNetworkInterfaceArray: %w", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// EncodeValues converts a CustomRootFS struct to a URL value.
|
|
||||||
func (r *CustomRootFS) EncodeValues(key string, v *url.Values) error {
|
|
||||||
var values []string
|
|
||||||
|
|
||||||
if r.ACL != nil {
|
|
||||||
if *r.ACL {
|
|
||||||
values = append(values, "acl=%d")
|
|
||||||
} else {
|
|
||||||
values = append(values, "acl=0")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if r.Size != nil {
|
|
||||||
values = append(values, fmt.Sprintf("size=%s", *r.Size))
|
|
||||||
}
|
|
||||||
|
|
||||||
if r.MountOptions != nil {
|
|
||||||
if len(*r.MountOptions) > 0 {
|
|
||||||
values = append(values, fmt.Sprintf("mount=%s", strings.Join(*r.MountOptions, ";")))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if r.Quota != nil {
|
|
||||||
if *r.Quota {
|
|
||||||
values = append(values, "quota=1")
|
|
||||||
} else {
|
|
||||||
values = append(values, "quota=0")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if r.ReadOnly != nil {
|
|
||||||
if *r.ReadOnly {
|
|
||||||
values = append(values, "ro=1")
|
|
||||||
} else {
|
|
||||||
values = append(values, "ro=0")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if r.Replicate != nil {
|
|
||||||
if *r.ReadOnly {
|
|
||||||
values = append(values, "replicate=1")
|
|
||||||
} else {
|
|
||||||
values = append(values, "replicate=0")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if r.Shared != nil {
|
|
||||||
if *r.Shared {
|
|
||||||
values = append(values, "shared=1")
|
|
||||||
} else {
|
|
||||||
values = append(values, "shared=0")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
values = append(values, fmt.Sprintf("volume=%s", r.Volume))
|
|
||||||
|
|
||||||
if len(values) > 0 {
|
|
||||||
v.Add(key, strings.Join(values, ","))
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// EncodeValues converts a CustomSSHKeys array to a URL value.
|
|
||||||
func (r CustomSSHKeys) EncodeValues(key string, v *url.Values) error {
|
|
||||||
v.Add(key, strings.Join(r, "\n"))
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// EncodeValues converts a CustomStartupBehavior struct to a URL value.
|
|
||||||
func (r *CustomStartupBehavior) EncodeValues(
|
|
||||||
key string,
|
|
||||||
v *url.Values,
|
|
||||||
) error {
|
|
||||||
var values []string
|
|
||||||
|
|
||||||
if r.Down != nil {
|
|
||||||
values = append(values, fmt.Sprintf("down=%d", *r.Down))
|
|
||||||
}
|
|
||||||
|
|
||||||
if r.Order != nil {
|
|
||||||
values = append(values, fmt.Sprintf("order=%d", *r.Order))
|
|
||||||
}
|
|
||||||
|
|
||||||
if r.Up != nil {
|
|
||||||
values = append(values, fmt.Sprintf("up=%d", *r.Up))
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(values) > 0 {
|
|
||||||
v.Add(key, strings.Join(values, ","))
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalJSON converts a ContainerCustomFeatures string to an object.
|
|
||||||
func (r *CustomFeatures) UnmarshalJSON(b []byte) error {
|
|
||||||
var s string
|
|
||||||
|
|
||||||
err := json.Unmarshal(b, &s)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unable to unmarshal ContainerCustomFeatures: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
pairs := strings.Split(s, ",")
|
|
||||||
|
|
||||||
for _, p := range pairs {
|
|
||||||
v := strings.Split(strings.TrimSpace(p), "=")
|
|
||||||
|
|
||||||
if len(v) == 2 {
|
|
||||||
switch v[0] {
|
|
||||||
case "fuse":
|
|
||||||
bv := types.CustomBool(v[1] == "1")
|
|
||||||
r.FUSE = &bv
|
|
||||||
case "keyctl":
|
|
||||||
bv := types.CustomBool(v[1] == "1")
|
|
||||||
r.KeyControl = &bv
|
|
||||||
case "mount":
|
|
||||||
if v[1] != "" {
|
|
||||||
a := strings.Split(v[1], ";")
|
|
||||||
r.MountTypes = &a
|
|
||||||
} else {
|
|
||||||
var a []string
|
|
||||||
r.MountTypes = &a
|
|
||||||
}
|
|
||||||
case "nesting":
|
|
||||||
bv := types.CustomBool(v[1] == "1")
|
|
||||||
r.Nesting = &bv
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalJSON converts a CustomMountPoint string to an object.
|
|
||||||
func (r *CustomMountPoint) UnmarshalJSON(b []byte) error {
|
|
||||||
var s string
|
|
||||||
|
|
||||||
err := json.Unmarshal(b, &s)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unable to unmarshal CustomMountPoint: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
pairs := strings.Split(s, ",")
|
|
||||||
|
|
||||||
for _, p := range pairs {
|
|
||||||
v := strings.Split(strings.TrimSpace(p), "=")
|
|
||||||
|
|
||||||
if len(v) == 1 {
|
|
||||||
r.Volume = v[0]
|
|
||||||
} else if len(v) == 2 {
|
|
||||||
switch v[0] {
|
|
||||||
case "acl":
|
|
||||||
bv := types.CustomBool(v[1] == "1")
|
|
||||||
r.ACL = &bv
|
|
||||||
case "backup":
|
|
||||||
bv := types.CustomBool(v[1] == "1")
|
|
||||||
r.Backup = &bv
|
|
||||||
case "mountoptions":
|
|
||||||
if v[1] != "" {
|
|
||||||
a := strings.Split(v[1], ";")
|
|
||||||
r.MountOptions = &a
|
|
||||||
} else {
|
|
||||||
var a []string
|
|
||||||
r.MountOptions = &a
|
|
||||||
}
|
|
||||||
case "mp":
|
|
||||||
r.MountPoint = v[1]
|
|
||||||
case "quota":
|
|
||||||
bv := types.CustomBool(v[1] == "1")
|
|
||||||
r.Quota = &bv
|
|
||||||
case "ro":
|
|
||||||
bv := types.CustomBool(v[1] == "1")
|
|
||||||
r.ReadOnly = &bv
|
|
||||||
case "replicate":
|
|
||||||
bv := types.CustomBool(v[1] == "1")
|
|
||||||
r.Replicate = &bv
|
|
||||||
case "shared":
|
|
||||||
bv := types.CustomBool(v[1] == "1")
|
|
||||||
r.Shared = &bv
|
|
||||||
case "size":
|
|
||||||
r.DiskSize = &v[1]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalJSON converts a CustomNetworkInterface string to an object.
|
|
||||||
func (r *CustomNetworkInterface) UnmarshalJSON(b []byte) error {
|
|
||||||
var s string
|
|
||||||
|
|
||||||
er := json.Unmarshal(b, &s)
|
|
||||||
if er != nil {
|
|
||||||
return fmt.Errorf("unable to unmarshal CustomNetworkInterface: %w", er)
|
|
||||||
}
|
|
||||||
|
|
||||||
pairs := strings.Split(s, ",")
|
|
||||||
|
|
||||||
for _, p := range pairs {
|
|
||||||
v := strings.Split(strings.TrimSpace(p), "=")
|
|
||||||
|
|
||||||
//nolint:nestif
|
|
||||||
if len(v) == 1 {
|
|
||||||
r.Name = v[0]
|
|
||||||
} else if len(v) == 2 {
|
|
||||||
switch v[0] {
|
|
||||||
case "bridge":
|
|
||||||
r.Bridge = &v[1]
|
|
||||||
case "firewall":
|
|
||||||
bv := types.CustomBool(v[1] == "1")
|
|
||||||
r.Firewall = &bv
|
|
||||||
case "gw":
|
|
||||||
r.IPv4Gateway = &v[1]
|
|
||||||
case "gw6":
|
|
||||||
r.IPv6Gateway = &v[1]
|
|
||||||
case "ip":
|
|
||||||
r.IPv4Address = &v[1]
|
|
||||||
case "ip6":
|
|
||||||
r.IPv6Address = &v[1]
|
|
||||||
case "hwaddr":
|
|
||||||
r.MACAddress = &v[1]
|
|
||||||
case "mtu":
|
|
||||||
iv, err := strconv.Atoi(v[1])
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unable to unmarshal 'mtu': %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
r.MTU = &iv
|
|
||||||
case "name":
|
|
||||||
r.Name = v[1]
|
|
||||||
case "rate":
|
|
||||||
fv, err := strconv.ParseFloat(v[1], 64)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unable to unmarshal 'rate': %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
r.RateLimit = &fv
|
|
||||||
case "tag":
|
|
||||||
iv, err := strconv.Atoi(v[1])
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unable to unmarshal 'tag': %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
r.Tag = &iv
|
|
||||||
case "trunks":
|
|
||||||
var err error
|
|
||||||
if v[1] != "" {
|
|
||||||
trunks := strings.Split(v[1], ";")
|
|
||||||
a := make([]int, len(trunks))
|
|
||||||
|
|
||||||
for ti, tv := range trunks {
|
|
||||||
a[ti], err = strconv.Atoi(tv)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unable to unmarshal 'trunks': %w", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
r.Trunks = &a
|
|
||||||
} else {
|
|
||||||
var a []int
|
|
||||||
r.Trunks = &a
|
|
||||||
}
|
|
||||||
case "type":
|
|
||||||
r.Type = &v[1]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalJSON converts a CustomRootFS string to an object.
|
|
||||||
func (r *CustomRootFS) UnmarshalJSON(b []byte) error {
|
|
||||||
var s string
|
|
||||||
|
|
||||||
err := json.Unmarshal(b, &s)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unable to unmarshal CustomRootFS: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
pairs := strings.Split(s, ",")
|
|
||||||
|
|
||||||
for _, p := range pairs {
|
|
||||||
v := strings.Split(strings.TrimSpace(p), "=")
|
|
||||||
|
|
||||||
if len(v) == 1 {
|
|
||||||
r.Volume = v[0]
|
|
||||||
} else if len(v) == 2 {
|
|
||||||
switch v[0] {
|
|
||||||
case "acl":
|
|
||||||
bv := types.CustomBool(v[1] == "1")
|
|
||||||
r.ACL = &bv
|
|
||||||
case "mountoptions":
|
|
||||||
if v[1] != "" {
|
|
||||||
a := strings.Split(v[1], ";")
|
|
||||||
r.MountOptions = &a
|
|
||||||
} else {
|
|
||||||
var a []string
|
|
||||||
r.MountOptions = &a
|
|
||||||
}
|
|
||||||
case "quota":
|
|
||||||
bv := types.CustomBool(v[1] == "1")
|
|
||||||
r.Quota = &bv
|
|
||||||
case "ro":
|
|
||||||
bv := types.CustomBool(v[1] == "1")
|
|
||||||
r.ReadOnly = &bv
|
|
||||||
case "replicate":
|
|
||||||
bv := types.CustomBool(v[1] == "1")
|
|
||||||
r.Replicate = &bv
|
|
||||||
case "shared":
|
|
||||||
bv := types.CustomBool(v[1] == "1")
|
|
||||||
r.Shared = &bv
|
|
||||||
case "size":
|
|
||||||
r.Size = new(types.DiskSize)
|
|
||||||
err := r.Size.UnmarshalJSON([]byte(v[1]))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to unmarshal disk size: %w", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalJSON converts a CustomStartupBehavior string to an object.
|
|
||||||
func (r *CustomStartupBehavior) UnmarshalJSON(b []byte) error {
|
|
||||||
var s string
|
|
||||||
|
|
||||||
err := json.Unmarshal(b, &s)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unable to unmarshal CustomStartupBehavior: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
pairs := strings.Split(s, ",")
|
|
||||||
|
|
||||||
for _, p := range pairs {
|
|
||||||
v := strings.Split(strings.TrimSpace(p), "=")
|
|
||||||
|
|
||||||
if len(v) == 2 {
|
|
||||||
switch v[0] {
|
|
||||||
case "down":
|
|
||||||
iv, err := strconv.Atoi(v[1])
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unable to unmarshal 'down': %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
r.Down = &iv
|
|
||||||
case "order":
|
|
||||||
iv, err := strconv.Atoi(v[1])
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unable to unmarshal 'order': %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
r.Order = &iv
|
|
||||||
case "up":
|
|
||||||
iv, err := strconv.Atoi(v[1])
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unable to unmarshal 'up': %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
r.Up = &iv
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
@ -1,16 +0,0 @@
|
|||||||
/*
|
|
||||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package firewall
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/bpg/terraform-provider-proxmox/proxmox/firewall"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Client is an interface for accessing the Proxmox container firewall API.
|
|
||||||
type Client struct {
|
|
||||||
firewall.Client
|
|
||||||
}
|
|
@ -1,41 +0,0 @@
|
|||||||
/*
|
|
||||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package nodes
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/bpg/terraform-provider-proxmox/proxmox/api"
|
|
||||||
)
|
|
||||||
|
|
||||||
// GetDNS retrieves the DNS configuration for a node.
|
|
||||||
func (c *Client) GetDNS(ctx context.Context) (*DNSGetResponseData, error) {
|
|
||||||
resBody := &DNSGetResponseBody{}
|
|
||||||
|
|
||||||
err := c.DoRequest(ctx, http.MethodGet, c.ExpandPath("dns"), nil, resBody)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error retrieving DNS configuration: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if resBody.Data == nil {
|
|
||||||
return nil, api.ErrNoDataObjectInResponse
|
|
||||||
}
|
|
||||||
|
|
||||||
return resBody.Data, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// UpdateDNS updates the DNS configuration for a node.
|
|
||||||
func (c *Client) UpdateDNS(ctx context.Context, d *DNSUpdateRequestBody) error {
|
|
||||||
err := c.DoRequest(ctx, http.MethodPut, c.ExpandPath("dns"), d, nil)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error updating DNS configuration: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
@ -1,28 +0,0 @@
|
|||||||
/*
|
|
||||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package nodes
|
|
||||||
|
|
||||||
// DNSGetResponseBody contains the body from a DNS get response.
|
|
||||||
type DNSGetResponseBody struct {
|
|
||||||
Data *DNSGetResponseData `json:"data,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// DNSGetResponseData contains the data from a DNS get response.
|
|
||||||
type DNSGetResponseData struct {
|
|
||||||
Server1 *string `json:"dns1,omitempty" url:"dns1,omitempty"`
|
|
||||||
Server2 *string `json:"dns2,omitempty" url:"dns2,omitempty"`
|
|
||||||
Server3 *string `json:"dns3,omitempty" url:"dns3,omitempty"`
|
|
||||||
SearchDomain *string `json:"search,omitempty" url:"search,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// DNSUpdateRequestBody contains the body for a DNS update request.
|
|
||||||
type DNSUpdateRequestBody struct {
|
|
||||||
Server1 *string `json:"dns1,omitempty" url:"dns1,omitempty"`
|
|
||||||
Server2 *string `json:"dns2,omitempty" url:"dns2,omitempty"`
|
|
||||||
Server3 *string `json:"dns3,omitempty" url:"dns3,omitempty"`
|
|
||||||
SearchDomain *string `json:"search,omitempty" url:"search,omitempty"`
|
|
||||||
}
|
|
@ -1,41 +0,0 @@
|
|||||||
/*
|
|
||||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package nodes
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/bpg/terraform-provider-proxmox/proxmox/api"
|
|
||||||
)
|
|
||||||
|
|
||||||
// GetHosts retrieves the Hosts configuration for a node.
|
|
||||||
func (c *Client) GetHosts(ctx context.Context) (*HostsGetResponseData, error) {
|
|
||||||
resBody := &HostsGetResponseBody{}
|
|
||||||
|
|
||||||
err := c.DoRequest(ctx, http.MethodGet, c.ExpandPath("hosts"), nil, resBody)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error retrieving hosts configuration: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if resBody.Data == nil {
|
|
||||||
return nil, api.ErrNoDataObjectInResponse
|
|
||||||
}
|
|
||||||
|
|
||||||
return resBody.Data, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// UpdateHosts updates the Hosts configuration for a node.
|
|
||||||
func (c *Client) UpdateHosts(ctx context.Context, d *HostsUpdateRequestBody) error {
|
|
||||||
err := c.DoRequest(ctx, http.MethodPost, c.ExpandPath("hosts"), d, nil)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error updating hosts configuration: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
@ -1,24 +0,0 @@
|
|||||||
/*
|
|
||||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package nodes
|
|
||||||
|
|
||||||
// HostsGetResponseBody contains the body from a hosts get response.
|
|
||||||
type HostsGetResponseBody struct {
|
|
||||||
Data *HostsGetResponseData `json:"data,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// HostsGetResponseData contains the data from a hosts get response.
|
|
||||||
type HostsGetResponseData struct {
|
|
||||||
Data string `json:"data"`
|
|
||||||
Digest *string `json:"digest,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// HostsUpdateRequestBody contains the body for a hosts update request.
|
|
||||||
type HostsUpdateRequestBody struct {
|
|
||||||
Data string `json:"data" url:"data"`
|
|
||||||
Digest *string `json:"digest,omitempty" url:"digest,omitempty"`
|
|
||||||
}
|
|
@ -1,125 +0,0 @@
|
|||||||
/*
|
|
||||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package nodes
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"sort"
|
|
||||||
|
|
||||||
"github.com/bpg/terraform-provider-proxmox/proxmox/api"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
networkReloadTimeoutSec = 5
|
|
||||||
)
|
|
||||||
|
|
||||||
// ListNetworkInterfaces retrieves a list of network interfaces for a specific nodes.
|
|
||||||
func (c *Client) ListNetworkInterfaces(ctx context.Context) ([]*NetworkInterfaceListResponseData, error) {
|
|
||||||
resBody := &NetworkInterfaceListResponseBody{}
|
|
||||||
|
|
||||||
err := c.DoRequest(ctx, http.MethodGet, c.ExpandPath("network"), nil, resBody)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to get network interfaces for node \"%s\": %w", c.NodeName, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if resBody.Data == nil {
|
|
||||||
return nil, api.ErrNoDataObjectInResponse
|
|
||||||
}
|
|
||||||
|
|
||||||
sort.Slice(resBody.Data, func(i, j int) bool {
|
|
||||||
return resBody.Data[i].Priority < resBody.Data[j].Priority
|
|
||||||
})
|
|
||||||
|
|
||||||
return resBody.Data, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateNetworkInterface creates a network interface for a specific node.
|
|
||||||
func (c *Client) CreateNetworkInterface(ctx context.Context, d *NetworkInterfaceCreateUpdateRequestBody) error {
|
|
||||||
err := c.DoRequest(ctx, http.MethodPost, c.ExpandPath("network"), d, nil)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf(
|
|
||||||
"failed to create network interface \"%s\" for node \"%s\": %w",
|
|
||||||
d.Iface, c.NodeName, err,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReloadNetworkConfiguration reloads the network configuration for a specific node.
|
|
||||||
func (c *Client) ReloadNetworkConfiguration(ctx context.Context) error {
|
|
||||||
resBody := &ReloadNetworkResponseBody{}
|
|
||||||
|
|
||||||
err := c.DoRequest(ctx, http.MethodPut, c.ExpandPath("network"), nil, resBody)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to reload network configuration for node \"%s\": %w", c.NodeName, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if resBody.Data == nil {
|
|
||||||
return api.ErrNoDataObjectInResponse
|
|
||||||
}
|
|
||||||
|
|
||||||
err = c.Tasks().WaitForTask(ctx, *resBody.Data, networkReloadTimeoutSec, 1)
|
|
||||||
if err == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// RevertNetworkConfiguration reverts the network configuration changes for a specific node.
|
|
||||||
func (c *Client) RevertNetworkConfiguration(ctx context.Context) error {
|
|
||||||
err := c.DoRequest(ctx, http.MethodDelete, c.ExpandPath("network"), nil, nil)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to revert network configuration for node \"%s\": %w", c.NodeName, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// UpdateNetworkInterface updates a network interface for a specific node.
|
|
||||||
func (c *Client) UpdateNetworkInterface(
|
|
||||||
ctx context.Context,
|
|
||||||
iface string,
|
|
||||||
d *NetworkInterfaceCreateUpdateRequestBody,
|
|
||||||
) error {
|
|
||||||
err := c.DoRequest(
|
|
||||||
ctx,
|
|
||||||
http.MethodPut,
|
|
||||||
c.ExpandPath(fmt.Sprintf("network/%s", url.PathEscape(iface))),
|
|
||||||
d,
|
|
||||||
nil,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to update network interface \"%s\" for node \"%s\": %w",
|
|
||||||
d.Iface, c.NodeName, err,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteNetworkInterface deletes a network interface configuration for a specific node.
|
|
||||||
func (c *Client) DeleteNetworkInterface(ctx context.Context, iface string) error {
|
|
||||||
err := c.DoRequest(
|
|
||||||
ctx,
|
|
||||||
http.MethodDelete,
|
|
||||||
c.ExpandPath(fmt.Sprintf("network/%s", url.PathEscape(iface))),
|
|
||||||
nil,
|
|
||||||
nil,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to delete network interface \"%s\" for node \"%s\": %w",
|
|
||||||
iface, c.NodeName, err,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
@ -1,86 +0,0 @@
|
|||||||
/*
|
|
||||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package nodes
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/bpg/terraform-provider-proxmox/internal/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
// NetworkInterfaceListResponseBody contains the body from a node network interface list response.
|
|
||||||
type NetworkInterfaceListResponseBody struct {
|
|
||||||
Data []*NetworkInterfaceListResponseData `json:"data,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// NetworkInterfaceListResponseData contains the data from a node network interface list response.
|
|
||||||
type NetworkInterfaceListResponseData struct {
|
|
||||||
// There seems to be inconsistency in the APIs between certain versions of Proxmox.
|
|
||||||
// See https://github.com/bpg/terraform-provider-proxmox/issues/410
|
|
||||||
// BridgeFD *int `json:"bridge_fd,omitempty"`
|
|
||||||
|
|
||||||
Active *types.CustomBool `json:"active,omitempty"`
|
|
||||||
Address *string `json:"address,omitempty"`
|
|
||||||
Address6 *string `json:"address6,omitempty"`
|
|
||||||
Autostart *types.CustomBool `json:"autostart,omitempty"`
|
|
||||||
BridgePorts *string `json:"bridge_ports,omitempty"`
|
|
||||||
BridgeSTP *string `json:"bridge_stp,omitempty"`
|
|
||||||
BridgeVIDs *string `json:"bridge_vids,omitempty"`
|
|
||||||
BridgeVLANAware *types.CustomBool `json:"bridge_vlan_aware,omitempty"`
|
|
||||||
CIDR *string `json:"cidr,omitempty"`
|
|
||||||
CIDR6 *string `json:"cidr6,omitempty"`
|
|
||||||
Comments *string `json:"comments,omitempty"`
|
|
||||||
Exists *types.CustomBool `json:"exists,omitempty"`
|
|
||||||
Families *[]string `json:"families,omitempty"`
|
|
||||||
Gateway *string `json:"gateway,omitempty"`
|
|
||||||
Gateway6 *string `json:"gateway6,omitempty"`
|
|
||||||
Iface string `json:"iface"`
|
|
||||||
MethodIPv4 *string `json:"method,omitempty"`
|
|
||||||
MethodIPv6 *string `json:"method6,omitempty"`
|
|
||||||
MTU *string `json:"mtu,omitempty"`
|
|
||||||
Netmask *string `json:"netmask,omitempty"`
|
|
||||||
VLANID *string `json:"vlan-id,omitempty"`
|
|
||||||
VLANRawDevice *string `json:"vlan-raw-device,omitempty"`
|
|
||||||
Priority int `json:"priority"`
|
|
||||||
Type string `json:"type"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// NetworkInterfaceCreateUpdateRequestBody contains the body for a node network interface create / update request.
|
|
||||||
type NetworkInterfaceCreateUpdateRequestBody struct {
|
|
||||||
Iface string `json:"iface" url:"iface"`
|
|
||||||
Type string `json:"type" url:"type"`
|
|
||||||
|
|
||||||
Address *string `json:"address,omitempty" url:"address,omitempty"`
|
|
||||||
Address6 *string `json:"address6,omitempty" url:"address6,omitempty"`
|
|
||||||
Autostart *types.CustomBool `json:"autostart,omitempty" url:"autostart,omitempty,int"`
|
|
||||||
BondPrimary *string `json:"bond-primary,omitempty" url:"bond-primary,omitempty"`
|
|
||||||
BondMode *string `json:"bond_mode,omitempty" url:"bond_mode,omitempty"`
|
|
||||||
BondXmitHashPolicy *string `json:"bond_xmit_hash_policy,omitempty" url:"bond_xmit_hash_policy,omitempty"`
|
|
||||||
BridgePorts *string `json:"bridge_ports,omitempty" url:"bridge_ports,omitempty"`
|
|
||||||
BridgeVLANAware *types.CustomBool `json:"bridge_vlan_aware,omitempty" url:"bridge_vlan_aware,omitempty,int"`
|
|
||||||
CIDR *string `json:"cidr,omitempty" url:"cidr,omitempty"`
|
|
||||||
CIDR6 *string `json:"cidr6,omitempty" url:"cidr6,omitempty"`
|
|
||||||
Comments *string `json:"comments,omitempty" url:"comments,omitempty"`
|
|
||||||
Comments6 *string `json:"comments6,omitempty" url:"comments6,omitempty"`
|
|
||||||
Gateway *string `json:"gateway,omitempty" url:"gateway,omitempty"`
|
|
||||||
Gateway6 *string `json:"gateway6,omitempty" url:"gateway6,omitempty"`
|
|
||||||
Delete *[]string `json:"delete,omitempty" url:"delete,omitempty"`
|
|
||||||
MTU *int64 `json:"mtu,omitempty" url:"mtu,omitempty"`
|
|
||||||
Netmask *string `json:"netmask,omitempty" url:"netmask,omitempty"`
|
|
||||||
Netmask6 *string `json:"netmask6,omitempty" url:"netmask6,omitempty"`
|
|
||||||
OVSBonds *string `json:"ovs_bonds,omitempty" url:"ovs_bonds,omitempty"`
|
|
||||||
OVSBridge *string `json:"ovs_bridge,omitempty" url:"ovs_bridge,omitempty"`
|
|
||||||
OVSOptions *string `json:"ovs_options,omitempty" url:"ovs_options,omitempty"`
|
|
||||||
OVSPorts *string `json:"ovs_ports,omitempty" url:"ovs_ports,omitempty"`
|
|
||||||
OVSTag *string `json:"ovs_tag,omitempty" url:"ovs_tag,omitempty"`
|
|
||||||
Slaves *string `json:"slaves,omitempty" url:"slaves,omitempty"`
|
|
||||||
VLANID *int64 `json:"vlan_id,omitempty" url:"vlan_id,omitempty"`
|
|
||||||
VLANRawDevice *string `json:"vlan-raw-device,omitempty" url:"vlan-raw-device,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReloadNetworkResponseBody contains the body from a node network reload response.
|
|
||||||
type ReloadNetworkResponseBody struct {
|
|
||||||
Data *string `json:"data,omitempty"`
|
|
||||||
}
|
|
@ -1,62 +0,0 @@
|
|||||||
/*
|
|
||||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package nodes
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"sort"
|
|
||||||
|
|
||||||
"github.com/bpg/terraform-provider-proxmox/proxmox/api"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ListNodes retrieves a list of nodes.
|
|
||||||
func (c *Client) ListNodes(ctx context.Context) ([]*ListResponseData, error) {
|
|
||||||
resBody := &ListResponseBody{}
|
|
||||||
|
|
||||||
err := c.DoRequest(ctx, http.MethodGet, "nodes", nil, resBody)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to get nodes: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if resBody.Data == nil {
|
|
||||||
return nil, api.ErrNoDataObjectInResponse
|
|
||||||
}
|
|
||||||
|
|
||||||
sort.Slice(resBody.Data, func(i, j int) bool {
|
|
||||||
return resBody.Data[i].Name < resBody.Data[j].Name
|
|
||||||
})
|
|
||||||
|
|
||||||
return resBody.Data, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetTime retrieves the time information for a node.
|
|
||||||
func (c *Client) GetTime(ctx context.Context) (*GetTimeResponseData, error) {
|
|
||||||
resBody := &GetTimeResponseBody{}
|
|
||||||
|
|
||||||
err := c.DoRequest(ctx, http.MethodGet, c.ExpandPath("time"), nil, resBody)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to get time information for node \"%s\": %w", c.NodeName, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if resBody.Data == nil {
|
|
||||||
return nil, api.ErrNoDataObjectInResponse
|
|
||||||
}
|
|
||||||
|
|
||||||
return resBody.Data, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// UpdateTime updates the time on a node.
|
|
||||||
func (c *Client) UpdateTime(ctx context.Context, d *UpdateTimeRequestBody) error {
|
|
||||||
err := c.DoRequest(ctx, http.MethodPut, c.ExpandPath("time"), d, nil)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to update node time: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
@ -1,70 +0,0 @@
|
|||||||
/*
|
|
||||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package nodes
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"net/url"
|
|
||||||
|
|
||||||
"github.com/bpg/terraform-provider-proxmox/internal/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CustomCommands contains an array of commands to execute.
|
|
||||||
type CustomCommands []string
|
|
||||||
|
|
||||||
// ExecuteRequestBody contains the data for a node execute request.
|
|
||||||
type ExecuteRequestBody struct {
|
|
||||||
Commands CustomCommands `json:"commands" url:"commands"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetTimeResponseBody contains the body from a node time zone get response.
|
|
||||||
type GetTimeResponseBody struct {
|
|
||||||
Data *GetTimeResponseData `json:"data,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetTimeResponseData contains the data from a node list response.
|
|
||||||
type GetTimeResponseData struct {
|
|
||||||
LocalTime types.CustomTimestamp `json:"localtime"`
|
|
||||||
TimeZone string `json:"timezone"`
|
|
||||||
UTCTime types.CustomTimestamp `json:"time"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListResponseBody contains the body from a node list response.
|
|
||||||
type ListResponseBody struct {
|
|
||||||
Data []*ListResponseData `json:"data,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListResponseData contains the data from a node list response.
|
|
||||||
type ListResponseData struct {
|
|
||||||
CPUCount *int `json:"maxcpu,omitempty"`
|
|
||||||
CPUUtilization *float64 `json:"cpu,omitempty"`
|
|
||||||
MemoryAvailable *int `json:"maxmem,omitempty"`
|
|
||||||
MemoryUsed *int `json:"mem,omitempty"`
|
|
||||||
Name string `json:"node"`
|
|
||||||
SSLFingerprint *string `json:"ssl_fingerprint,omitempty"`
|
|
||||||
Status *string `json:"status"`
|
|
||||||
SupportLevel *string `json:"level,omitempty"`
|
|
||||||
Uptime *int `json:"uptime"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// UpdateTimeRequestBody contains the body for a node time update request.
|
|
||||||
type UpdateTimeRequestBody struct {
|
|
||||||
TimeZone string `json:"timezone" url:"timezone"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// EncodeValues converts a CustomCommands array to a JSON encoded URL value.
|
|
||||||
func (r CustomCommands) EncodeValues(key string, v *url.Values) error {
|
|
||||||
jsonArrayBytes, err := json.Marshal(r)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error marshalling CustomCommands array: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
v.Add(key, string(jsonArrayBytes))
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
@ -1,280 +0,0 @@
|
|||||||
/*
|
|
||||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package nodes
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"mime/multipart"
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"os"
|
|
||||||
"sort"
|
|
||||||
|
|
||||||
"github.com/hashicorp/terraform-plugin-log/tflog"
|
|
||||||
|
|
||||||
"github.com/bpg/terraform-provider-proxmox/proxmox/api"
|
|
||||||
)
|
|
||||||
|
|
||||||
// DeleteDatastoreFile deletes a file in a datastore.
|
|
||||||
func (c *Client) DeleteDatastoreFile(
|
|
||||||
ctx context.Context,
|
|
||||||
datastoreID, volumeID string,
|
|
||||||
) error {
|
|
||||||
err := c.DoRequest(
|
|
||||||
ctx,
|
|
||||||
http.MethodDelete,
|
|
||||||
c.ExpandPath(
|
|
||||||
fmt.Sprintf(
|
|
||||||
"storage/%s/content/%s",
|
|
||||||
url.PathEscape(datastoreID),
|
|
||||||
url.PathEscape(volumeID),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
nil,
|
|
||||||
nil,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error deleting file %s from datastore %s: %w", volumeID, datastoreID, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetDatastoreStatus gets status information for a given datastore.
|
|
||||||
func (c *Client) GetDatastoreStatus(
|
|
||||||
ctx context.Context,
|
|
||||||
datastoreID string,
|
|
||||||
) (*DatastoreGetStatusResponseData, error) {
|
|
||||||
resBody := &DatastoreGetStatusResponseBody{}
|
|
||||||
|
|
||||||
err := c.DoRequest(
|
|
||||||
ctx,
|
|
||||||
http.MethodGet,
|
|
||||||
c.ExpandPath(
|
|
||||||
fmt.Sprintf(
|
|
||||||
"storage/%s/status",
|
|
||||||
url.PathEscape(datastoreID),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
nil,
|
|
||||||
resBody,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error retrieving status for datastore %s: %w", datastoreID, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if resBody.Data == nil {
|
|
||||||
return nil, api.ErrNoDataObjectInResponse
|
|
||||||
}
|
|
||||||
|
|
||||||
return resBody.Data, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListDatastoreFiles retrieves a list of the files in a datastore.
|
|
||||||
func (c *Client) ListDatastoreFiles(
|
|
||||||
ctx context.Context,
|
|
||||||
datastoreID string,
|
|
||||||
) ([]*DatastoreFileListResponseData, error) {
|
|
||||||
resBody := &DatastoreFileListResponseBody{}
|
|
||||||
|
|
||||||
err := c.DoRequest(
|
|
||||||
ctx,
|
|
||||||
http.MethodGet,
|
|
||||||
c.ExpandPath(
|
|
||||||
fmt.Sprintf(
|
|
||||||
"storage/%s/content",
|
|
||||||
url.PathEscape(datastoreID),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
nil,
|
|
||||||
resBody,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error retrieving files from datastore %s: %w", datastoreID, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if resBody.Data == nil {
|
|
||||||
return nil, api.ErrNoDataObjectInResponse
|
|
||||||
}
|
|
||||||
|
|
||||||
sort.Slice(resBody.Data, func(i, j int) bool {
|
|
||||||
return resBody.Data[i].VolumeID < resBody.Data[j].VolumeID
|
|
||||||
})
|
|
||||||
|
|
||||||
return resBody.Data, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListDatastores retrieves a list of nodes.
|
|
||||||
func (c *Client) ListDatastores(
|
|
||||||
ctx context.Context,
|
|
||||||
d *DatastoreListRequestBody,
|
|
||||||
) ([]*DatastoreListResponseData, error) {
|
|
||||||
resBody := &DatastoreListResponseBody{}
|
|
||||||
|
|
||||||
err := c.DoRequest(
|
|
||||||
ctx,
|
|
||||||
http.MethodGet,
|
|
||||||
c.ExpandPath("storage"),
|
|
||||||
d,
|
|
||||||
resBody,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error retrieving datastores: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if resBody.Data == nil {
|
|
||||||
return nil, api.ErrNoDataObjectInResponse
|
|
||||||
}
|
|
||||||
|
|
||||||
sort.Slice(resBody.Data, func(i, j int) bool {
|
|
||||||
return resBody.Data[i].ID < resBody.Data[j].ID
|
|
||||||
})
|
|
||||||
|
|
||||||
return resBody.Data, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// APIUpload uploads a file to a datastore using the Proxmox API.
|
|
||||||
func (c *Client) APIUpload(
|
|
||||||
ctx context.Context,
|
|
||||||
datastoreID string,
|
|
||||||
d *api.FileUploadRequest,
|
|
||||||
) (*DatastoreUploadResponseBody, error) {
|
|
||||||
tflog.Debug(ctx, "uploading file to datastore using PVE API", map[string]interface{}{
|
|
||||||
"file_name": d.FileName,
|
|
||||||
"content_type": d.ContentType,
|
|
||||||
})
|
|
||||||
|
|
||||||
r, w := io.Pipe()
|
|
||||||
|
|
||||||
defer func(r *io.PipeReader) {
|
|
||||||
err := r.Close()
|
|
||||||
if err != nil {
|
|
||||||
tflog.Error(ctx, "failed to close pipe reader", map[string]interface{}{
|
|
||||||
"error": err,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}(r)
|
|
||||||
|
|
||||||
m := multipart.NewWriter(w)
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
defer func(w *io.PipeWriter) {
|
|
||||||
err := w.Close()
|
|
||||||
if err != nil {
|
|
||||||
tflog.Error(ctx, "failed to close pipe writer", map[string]interface{}{
|
|
||||||
"error": err,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}(w)
|
|
||||||
defer func(m *multipart.Writer) {
|
|
||||||
err := m.Close()
|
|
||||||
if err != nil {
|
|
||||||
tflog.Error(ctx, "failed to close multipart writer", map[string]interface{}{
|
|
||||||
"error": err,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}(m)
|
|
||||||
|
|
||||||
err := m.WriteField("content", d.ContentType)
|
|
||||||
if err != nil {
|
|
||||||
tflog.Error(ctx, "failed to write 'content' field", map[string]interface{}{
|
|
||||||
"error": err,
|
|
||||||
})
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
part, err := m.CreateFormFile("filename", d.FileName)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = io.Copy(part, d.File)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
// We need to store the multipart content in a temporary file to avoid using high amounts of memory.
|
|
||||||
// This is necessary due to Proxmox VE not supporting chunked transfers in v6.1 and earlier versions.
|
|
||||||
tempMultipartFile, err := os.CreateTemp("", "multipart")
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to create temporary file: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
tempMultipartFileName := tempMultipartFile.Name()
|
|
||||||
|
|
||||||
_, err = io.Copy(tempMultipartFile, r)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to copy multipart data to temporary file: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = tempMultipartFile.Close()
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to close temporary file: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
defer func(name string) {
|
|
||||||
e := os.Remove(name)
|
|
||||||
if e != nil {
|
|
||||||
tflog.Error(ctx, "failed to remove temporary file", map[string]interface{}{
|
|
||||||
"error": e,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}(tempMultipartFileName)
|
|
||||||
|
|
||||||
// Now that the multipart data is stored in a file, we can go ahead and do an HTTP POST request.
|
|
||||||
fileReader, err := os.Open(tempMultipartFileName)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to open temporary file: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
defer func(fileReader *os.File) {
|
|
||||||
e := fileReader.Close()
|
|
||||||
if e != nil {
|
|
||||||
tflog.Error(ctx, "failed to close file reader", map[string]interface{}{
|
|
||||||
"error": e,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}(fileReader)
|
|
||||||
|
|
||||||
fileInfo, err := fileReader.Stat()
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to get file info: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
fileSize := fileInfo.Size()
|
|
||||||
|
|
||||||
reqBody := &api.MultiPartData{
|
|
||||||
Boundary: m.Boundary(),
|
|
||||||
Reader: fileReader,
|
|
||||||
Size: &fileSize,
|
|
||||||
}
|
|
||||||
|
|
||||||
resBody := &DatastoreUploadResponseBody{}
|
|
||||||
err = c.DoRequest(
|
|
||||||
ctx,
|
|
||||||
http.MethodPost,
|
|
||||||
c.ExpandPath(
|
|
||||||
fmt.Sprintf(
|
|
||||||
"storage/%s/upload",
|
|
||||||
url.PathEscape(datastoreID),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
reqBody,
|
|
||||||
resBody,
|
|
||||||
)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error uploading file to datastore %s: %w", datastoreID, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return resBody, nil
|
|
||||||
}
|
|
@ -1,77 +0,0 @@
|
|||||||
/*
|
|
||||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package nodes
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/bpg/terraform-provider-proxmox/internal/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
// DatastoreFileListResponseBody contains the body from a datastore content list response.
|
|
||||||
type DatastoreFileListResponseBody struct {
|
|
||||||
Data []*DatastoreFileListResponseData `json:"data,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// DatastoreFileListResponseData contains the data from a datastore content list response.
|
|
||||||
type DatastoreFileListResponseData struct {
|
|
||||||
ContentType string `json:"content"`
|
|
||||||
FileFormat string `json:"format"`
|
|
||||||
FileSize int `json:"size"`
|
|
||||||
ParentVolumeID *string `json:"parent,omitempty"`
|
|
||||||
SpaceUsed *int `json:"used,omitempty"`
|
|
||||||
VMID *int `json:"vmid,omitempty"`
|
|
||||||
VolumeID string `json:"volid"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// DatastoreGetStatusResponseBody contains the body from a datastore status get request.
|
|
||||||
type DatastoreGetStatusResponseBody struct {
|
|
||||||
Data *DatastoreGetStatusResponseData `json:"data,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// DatastoreGetStatusResponseData contains the data from a datastore status get request.
|
|
||||||
type DatastoreGetStatusResponseData struct {
|
|
||||||
Active *types.CustomBool `json:"active,omitempty"`
|
|
||||||
AvailableBytes *int64 `json:"avail,omitempty"`
|
|
||||||
Content *types.CustomCommaSeparatedList `json:"content,omitempty" url:"content,omitempty,comma"`
|
|
||||||
Enabled *types.CustomBool `json:"enabled,omitempty"`
|
|
||||||
Shared *types.CustomBool `json:"shared,omitempty"`
|
|
||||||
TotalBytes *int64 `json:"total,omitempty"`
|
|
||||||
Type *string `json:"type,omitempty"`
|
|
||||||
UsedBytes *int64 `json:"used,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// DatastoreListRequestBody contains the body for a datastore list request.
|
|
||||||
type DatastoreListRequestBody struct {
|
|
||||||
ContentTypes types.CustomCommaSeparatedList `json:"content,omitempty" url:"content,omitempty,comma"`
|
|
||||||
Enabled *types.CustomBool `json:"enabled,omitempty" url:"enabled,omitempty,int"`
|
|
||||||
Format *types.CustomBool `json:"format,omitempty" url:"format,omitempty,int"`
|
|
||||||
ID *string `json:"storage,omitempty" url:"storage,omitempty"`
|
|
||||||
Target *string `json:"target,omitempty" url:"target,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// DatastoreListResponseBody contains the body from a datastore list response.
|
|
||||||
type DatastoreListResponseBody struct {
|
|
||||||
Data []*DatastoreListResponseData `json:"data,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// DatastoreListResponseData contains the data from a datastore list response.
|
|
||||||
type DatastoreListResponseData struct {
|
|
||||||
Active *types.CustomBool `json:"active,omitempty"`
|
|
||||||
ContentTypes *types.CustomCommaSeparatedList `json:"content,omitempty"`
|
|
||||||
Enabled *types.CustomBool `json:"enabled,omitempty"`
|
|
||||||
ID string `json:"storage,omitempty"`
|
|
||||||
Shared *types.CustomBool `json:"shared,omitempty"`
|
|
||||||
SpaceAvailable *int `json:"avail,omitempty"`
|
|
||||||
SpaceTotal *int `json:"total,omitempty"`
|
|
||||||
SpaceUsed *int `json:"used,omitempty"`
|
|
||||||
SpaceUsedPercentage *float64 `json:"used_fraction,omitempty"`
|
|
||||||
Type string `json:"type,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// DatastoreUploadResponseBody contains the body from a datastore upload response.
|
|
||||||
type DatastoreUploadResponseBody struct {
|
|
||||||
UploadID *string `json:"data,omitempty"`
|
|
||||||
}
|
|
@ -1,25 +0,0 @@
|
|||||||
/*
|
|
||||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package tasks
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/bpg/terraform-provider-proxmox/proxmox/api"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Client is an interface for performing requests against the Proxmox 'tasks' API.
|
|
||||||
type Client struct {
|
|
||||||
api.Client
|
|
||||||
}
|
|
||||||
|
|
||||||
// ExpandPath expands a path relative to the client's base path.
|
|
||||||
func (c *Client) ExpandPath(path string) string {
|
|
||||||
return c.Client.ExpandPath(
|
|
||||||
fmt.Sprintf("tasks/%s", path),
|
|
||||||
)
|
|
||||||
}
|
|
@ -1,86 +0,0 @@
|
|||||||
/*
|
|
||||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package tasks
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/bpg/terraform-provider-proxmox/proxmox/api"
|
|
||||||
)
|
|
||||||
|
|
||||||
// GetTaskStatus retrieves the status of a task.
|
|
||||||
func (c *Client) GetTaskStatus(ctx context.Context, upid string) (*GetTaskStatusResponseData, error) {
|
|
||||||
resBody := &GetTaskStatusResponseBody{}
|
|
||||||
|
|
||||||
err := c.DoRequest(
|
|
||||||
ctx,
|
|
||||||
http.MethodGet,
|
|
||||||
c.ExpandPath(fmt.Sprintf("%s/status", url.PathEscape(upid))),
|
|
||||||
nil,
|
|
||||||
resBody,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error retrievinf task status: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if resBody.Data == nil {
|
|
||||||
return nil, api.ErrNoDataObjectInResponse
|
|
||||||
}
|
|
||||||
|
|
||||||
return resBody.Data, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// WaitForTask waits for a specific task to complete.
|
|
||||||
func (c *Client) WaitForTask(ctx context.Context, upid string, timeoutSec, delaySec int) error {
|
|
||||||
timeDelay := int64(delaySec)
|
|
||||||
timeMax := float64(timeoutSec)
|
|
||||||
timeStart := time.Now()
|
|
||||||
timeElapsed := timeStart.Sub(timeStart)
|
|
||||||
|
|
||||||
for timeElapsed.Seconds() < timeMax {
|
|
||||||
if int64(timeElapsed.Seconds())%timeDelay == 0 {
|
|
||||||
status, err := c.GetTaskStatus(ctx, upid)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if status.Status != "running" {
|
|
||||||
if status.ExitCode != "OK" {
|
|
||||||
return fmt.Errorf(
|
|
||||||
"task \"%s\" failed to complete with exit code: %s",
|
|
||||||
upid,
|
|
||||||
status.ExitCode,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
time.Sleep(1 * time.Second)
|
|
||||||
}
|
|
||||||
|
|
||||||
time.Sleep(200 * time.Millisecond)
|
|
||||||
|
|
||||||
timeElapsed = time.Since(timeStart)
|
|
||||||
|
|
||||||
if ctx.Err() != nil {
|
|
||||||
return fmt.Errorf(
|
|
||||||
"context error while waiting for task \"%s\" to complete: %w",
|
|
||||||
upid, ctx.Err(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Errorf(
|
|
||||||
"timeout while waiting for task \"%s\" to complete",
|
|
||||||
upid,
|
|
||||||
)
|
|
||||||
}
|
|
@ -1,19 +0,0 @@
|
|||||||
/*
|
|
||||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package tasks
|
|
||||||
|
|
||||||
// GetTaskStatusResponseBody contains the body from a node get task status response.
|
|
||||||
type GetTaskStatusResponseBody struct {
|
|
||||||
Data *GetTaskStatusResponseData `json:"data,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetTaskStatusResponseData contains the data from a node get task status response.
|
|
||||||
type GetTaskStatusResponseData struct {
|
|
||||||
PID int `json:"pid,omitempty"`
|
|
||||||
Status string `json:"status,omitempty"`
|
|
||||||
ExitCode string `json:"exitstatus,omitempty"`
|
|
||||||
}
|
|
@ -1,50 +0,0 @@
|
|||||||
/*
|
|
||||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package vms
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/bpg/terraform-provider-proxmox/proxmox/api"
|
|
||||||
"github.com/bpg/terraform-provider-proxmox/proxmox/firewall"
|
|
||||||
"github.com/bpg/terraform-provider-proxmox/proxmox/nodes/tasks"
|
|
||||||
vmfirewall "github.com/bpg/terraform-provider-proxmox/proxmox/nodes/vms/firewall"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Client is an interface for accessing the Proxmox VM API.
|
|
||||||
type Client struct {
|
|
||||||
api.Client
|
|
||||||
VMID int
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) basePath() string {
|
|
||||||
return c.Client.ExpandPath("qemu")
|
|
||||||
}
|
|
||||||
|
|
||||||
// ExpandPath expands a relative path to a full VM API path.
|
|
||||||
func (c *Client) ExpandPath(path string) string {
|
|
||||||
ep := fmt.Sprintf("%s/%d", c.basePath(), c.VMID)
|
|
||||||
if path != "" {
|
|
||||||
ep = fmt.Sprintf("%s/%s", ep, path)
|
|
||||||
}
|
|
||||||
|
|
||||||
return ep
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tasks returns a client for managing VM tasks.
|
|
||||||
func (c *Client) Tasks() *tasks.Client {
|
|
||||||
return &tasks.Client{
|
|
||||||
Client: c.Client,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Firewall returns a client for managing the VM firewall.
|
|
||||||
func (c *Client) Firewall() firewall.API {
|
|
||||||
return &vmfirewall.Client{
|
|
||||||
Client: firewall.Client{Client: c},
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,16 +0,0 @@
|
|||||||
/*
|
|
||||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package firewall
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/bpg/terraform-provider-proxmox/proxmox/firewall"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Client is an interface for accessing the Proxmox VM firewall API.
|
|
||||||
type Client struct {
|
|
||||||
firewall.Client
|
|
||||||
}
|
|
@ -1,581 +0,0 @@
|
|||||||
/*
|
|
||||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package vms
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/hashicorp/terraform-plugin-log/tflog"
|
|
||||||
|
|
||||||
"github.com/bpg/terraform-provider-proxmox/proxmox/api"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CloneVM clones a virtual machine.
|
|
||||||
func (c *Client) CloneVM(ctx context.Context, retries int, d *CloneRequestBody, timeout int) error {
|
|
||||||
var err error
|
|
||||||
|
|
||||||
resBody := &MoveDiskResponseBody{}
|
|
||||||
|
|
||||||
// just a guard in case someone sets retries to 0 unknowingly
|
|
||||||
if retries <= 0 {
|
|
||||||
retries = 1
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := 0; i < retries; i++ {
|
|
||||||
err = c.DoRequest(ctx, http.MethodPost, c.ExpandPath("clone"), d, resBody)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error cloning VM: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if resBody.Data == nil {
|
|
||||||
return api.ErrNoDataObjectInResponse
|
|
||||||
}
|
|
||||||
|
|
||||||
err = c.Tasks().WaitForTask(ctx, *resBody.Data, timeout, 5)
|
|
||||||
if err == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
time.Sleep(10 * time.Second)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error waiting for VM clone: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateVM creates a virtual machine.
|
|
||||||
func (c *Client) CreateVM(ctx context.Context, d *CreateRequestBody, timeout int) error {
|
|
||||||
taskID, err := c.CreateVMAsync(ctx, d)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = c.Tasks().WaitForTask(ctx, *taskID, timeout, 1)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error waiting for VM creation: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateVMAsync creates a virtual machine asynchronously.
|
|
||||||
func (c *Client) CreateVMAsync(ctx context.Context, d *CreateRequestBody) (*string, error) {
|
|
||||||
resBody := &CreateResponseBody{}
|
|
||||||
|
|
||||||
err := c.DoRequest(ctx, http.MethodPost, c.basePath(), d, resBody)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error creating VM: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if resBody.Data == nil {
|
|
||||||
return nil, api.ErrNoDataObjectInResponse
|
|
||||||
}
|
|
||||||
|
|
||||||
return resBody.Data, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteVM deletes a virtual machine.
|
|
||||||
func (c *Client) DeleteVM(ctx context.Context) error {
|
|
||||||
err := c.DoRequest(ctx, http.MethodDelete, c.ExpandPath("?destroy-unreferenced-disks=1&purge=1"), nil, nil)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error deleting VM: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetVM retrieves a virtual machine.
|
|
||||||
func (c *Client) GetVM(ctx context.Context) (*GetResponseData, error) {
|
|
||||||
resBody := &GetResponseBody{}
|
|
||||||
|
|
||||||
err := c.DoRequest(ctx, http.MethodGet, c.ExpandPath("config"), nil, resBody)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error retrieving VM: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if resBody.Data == nil {
|
|
||||||
return nil, api.ErrNoDataObjectInResponse
|
|
||||||
}
|
|
||||||
|
|
||||||
return resBody.Data, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetVMNetworkInterfacesFromAgent retrieves the network interfaces reported by the QEMU agent.
|
|
||||||
func (c *Client) GetVMNetworkInterfacesFromAgent(ctx context.Context) (*GetQEMUNetworkInterfacesResponseData, error) {
|
|
||||||
resBody := &GetQEMUNetworkInterfacesResponseBody{}
|
|
||||||
|
|
||||||
err := c.DoRequest(ctx, http.MethodGet, c.ExpandPath("agent/network-get-interfaces"), nil, resBody)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error retrieving VM network interfaces from agent: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if resBody.Data == nil {
|
|
||||||
return nil, api.ErrNoDataObjectInResponse
|
|
||||||
}
|
|
||||||
|
|
||||||
return resBody.Data, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetVMStatus retrieves the status for a virtual machine.
|
|
||||||
func (c *Client) GetVMStatus(ctx context.Context) (*GetStatusResponseData, error) {
|
|
||||||
resBody := &GetStatusResponseBody{}
|
|
||||||
|
|
||||||
err := c.DoRequest(ctx, http.MethodGet, c.ExpandPath("status/current"), nil, resBody)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error retrieving VM status: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if resBody.Data == nil {
|
|
||||||
return nil, api.ErrNoDataObjectInResponse
|
|
||||||
}
|
|
||||||
|
|
||||||
return resBody.Data, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// MigrateVM migrates a virtual machine.
|
|
||||||
func (c *Client) MigrateVM(ctx context.Context, d *MigrateRequestBody, timeout int) error {
|
|
||||||
taskID, err := c.MigrateVMAsync(ctx, d)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = c.Tasks().WaitForTask(ctx, *taskID, timeout, 5)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error waiting for VM migration: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// MigrateVMAsync migrates a virtual machine asynchronously.
|
|
||||||
func (c *Client) MigrateVMAsync(ctx context.Context, d *MigrateRequestBody) (*string, error) {
|
|
||||||
resBody := &MigrateResponseBody{}
|
|
||||||
|
|
||||||
err := c.DoRequest(ctx, http.MethodPost, c.ExpandPath("migrate"), d, resBody)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error migrating VM: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if resBody.Data == nil {
|
|
||||||
return nil, api.ErrNoDataObjectInResponse
|
|
||||||
}
|
|
||||||
|
|
||||||
return resBody.Data, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// MoveVMDisk moves a virtual machine disk.
|
|
||||||
func (c *Client) MoveVMDisk(ctx context.Context, d *MoveDiskRequestBody, timeout int) error {
|
|
||||||
taskID, err := c.MoveVMDiskAsync(ctx, d)
|
|
||||||
if err != nil {
|
|
||||||
if strings.Contains(err.Error(), "you can't move to the same storage with same format") {
|
|
||||||
// if someone tries to move to the same storage, the move is considered to be successful
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = c.Tasks().WaitForTask(ctx, *taskID, timeout, 5)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error waiting for VM disk move: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// MoveVMDiskAsync moves a virtual machine disk asynchronously.
|
|
||||||
func (c *Client) MoveVMDiskAsync(ctx context.Context, d *MoveDiskRequestBody) (*string, error) {
|
|
||||||
resBody := &MoveDiskResponseBody{}
|
|
||||||
|
|
||||||
err := c.DoRequest(ctx, http.MethodPost, c.ExpandPath("move_disk"), d, resBody)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error moving VM disk: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if resBody.Data == nil {
|
|
||||||
return nil, api.ErrNoDataObjectInResponse
|
|
||||||
}
|
|
||||||
|
|
||||||
return resBody.Data, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListVMs retrieves a list of virtual machines.
|
|
||||||
func (c *Client) ListVMs(ctx context.Context) ([]*ListResponseData, error) {
|
|
||||||
resBody := &ListResponseBody{}
|
|
||||||
|
|
||||||
err := c.DoRequest(ctx, http.MethodGet, c.basePath(), nil, resBody)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error retrieving VMs: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if resBody.Data == nil {
|
|
||||||
return nil, api.ErrNoDataObjectInResponse
|
|
||||||
}
|
|
||||||
|
|
||||||
return resBody.Data, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// RebootVM reboots a virtual machine.
|
|
||||||
func (c *Client) RebootVM(ctx context.Context, d *RebootRequestBody, timeout int) error {
|
|
||||||
taskID, err := c.RebootVMAsync(ctx, d)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = c.Tasks().WaitForTask(ctx, *taskID, timeout, 5)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error waiting for VM reboot: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// RebootVMAsync reboots a virtual machine asynchronously.
|
|
||||||
func (c *Client) RebootVMAsync(ctx context.Context, d *RebootRequestBody) (*string, error) {
|
|
||||||
resBody := &RebootResponseBody{}
|
|
||||||
|
|
||||||
err := c.DoRequest(ctx, http.MethodPost, c.ExpandPath("status/reboot"), d, resBody)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error rebooting VM: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if resBody.Data == nil {
|
|
||||||
return nil, api.ErrNoDataObjectInResponse
|
|
||||||
}
|
|
||||||
|
|
||||||
return resBody.Data, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ResizeVMDisk resizes a virtual machine disk.
|
|
||||||
func (c *Client) ResizeVMDisk(ctx context.Context, d *ResizeDiskRequestBody) error {
|
|
||||||
var err error
|
|
||||||
|
|
||||||
tflog.Debug(ctx, "resize disk", map[string]interface{}{
|
|
||||||
"disk": d.Disk,
|
|
||||||
"size": d.Size,
|
|
||||||
})
|
|
||||||
|
|
||||||
for i := 0; i < 5; i++ {
|
|
||||||
err = c.DoRequest(
|
|
||||||
ctx,
|
|
||||||
http.MethodPut,
|
|
||||||
c.ExpandPath("resize"),
|
|
||||||
d,
|
|
||||||
nil,
|
|
||||||
)
|
|
||||||
if err == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
tflog.Debug(ctx, "resize disk failed", map[string]interface{}{
|
|
||||||
"retry": i,
|
|
||||||
})
|
|
||||||
time.Sleep(5 * time.Second)
|
|
||||||
|
|
||||||
if ctx.Err() != nil {
|
|
||||||
return fmt.Errorf("error resizing VM disk: %w", ctx.Err())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error resizing VM disk: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ShutdownVM shuts down a virtual machine.
|
|
||||||
func (c *Client) ShutdownVM(ctx context.Context, d *ShutdownRequestBody, timeout int) error {
|
|
||||||
taskID, err := c.ShutdownVMAsync(ctx, d)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = c.Tasks().WaitForTask(ctx, *taskID, timeout, 5)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error waiting for VM shutdown: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ShutdownVMAsync shuts down a virtual machine asynchronously.
|
|
||||||
func (c *Client) ShutdownVMAsync(ctx context.Context, d *ShutdownRequestBody) (*string, error) {
|
|
||||||
resBody := &ShutdownResponseBody{}
|
|
||||||
|
|
||||||
err := c.DoRequest(ctx, http.MethodPost, c.ExpandPath("status/shutdown"), d, resBody)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error shutting down VM: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if resBody.Data == nil {
|
|
||||||
return nil, api.ErrNoDataObjectInResponse
|
|
||||||
}
|
|
||||||
|
|
||||||
return resBody.Data, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// StartVM starts a virtual machine.
|
|
||||||
func (c *Client) StartVM(ctx context.Context, timeout int) error {
|
|
||||||
taskID, err := c.StartVMAsync(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = c.Tasks().WaitForTask(ctx, *taskID, timeout, 5)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error waiting for VM start: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// StartVMAsync starts a virtual machine asynchronously.
|
|
||||||
func (c *Client) StartVMAsync(ctx context.Context) (*string, error) {
|
|
||||||
resBody := &StartResponseBody{}
|
|
||||||
|
|
||||||
err := c.DoRequest(ctx, http.MethodPost, c.ExpandPath("status/start"), nil, resBody)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error starting VM: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if resBody.Data == nil {
|
|
||||||
return nil, api.ErrNoDataObjectInResponse
|
|
||||||
}
|
|
||||||
|
|
||||||
return resBody.Data, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// StopVM stops a virtual machine.
|
|
||||||
func (c *Client) StopVM(ctx context.Context, timeout int) error {
|
|
||||||
taskID, err := c.StopVMAsync(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = c.Tasks().WaitForTask(ctx, *taskID, timeout, 5)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error waiting for VM stop: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// StopVMAsync stops a virtual machine asynchronously.
|
|
||||||
func (c *Client) StopVMAsync(ctx context.Context) (*string, error) {
|
|
||||||
resBody := &StopResponseBody{}
|
|
||||||
|
|
||||||
err := c.DoRequest(ctx, http.MethodPost, c.ExpandPath("status/stop"), nil, resBody)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error stopping VM: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if resBody.Data == nil {
|
|
||||||
return nil, api.ErrNoDataObjectInResponse
|
|
||||||
}
|
|
||||||
|
|
||||||
return resBody.Data, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// UpdateVM updates a virtual machine.
|
|
||||||
func (c *Client) UpdateVM(ctx context.Context, d *UpdateRequestBody) error {
|
|
||||||
err := c.DoRequest(ctx, http.MethodPut, c.ExpandPath("config"), d, nil)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error updating VM: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// UpdateVMAsync updates a virtual machine asynchronously.
|
|
||||||
func (c *Client) UpdateVMAsync(ctx context.Context, d *UpdateRequestBody) (*string, error) {
|
|
||||||
resBody := &UpdateAsyncResponseBody{}
|
|
||||||
|
|
||||||
err := c.DoRequest(ctx, http.MethodPost, c.ExpandPath("config"), d, resBody)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error updating VM: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if resBody.Data == nil {
|
|
||||||
return nil, api.ErrNoDataObjectInResponse
|
|
||||||
}
|
|
||||||
|
|
||||||
return resBody.Data, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// WaitForNetworkInterfacesFromVMAgent waits for a virtual machine's QEMU agent to publish the network interfaces.
|
|
||||||
func (c *Client) WaitForNetworkInterfacesFromVMAgent(
|
|
||||||
ctx context.Context,
|
|
||||||
timeout int,
|
|
||||||
delay int,
|
|
||||||
waitForIP bool,
|
|
||||||
) (*GetQEMUNetworkInterfacesResponseData, error) {
|
|
||||||
timeDelay := int64(delay)
|
|
||||||
timeMax := float64(timeout)
|
|
||||||
timeStart := time.Now()
|
|
||||||
timeElapsed := timeStart.Sub(timeStart)
|
|
||||||
|
|
||||||
for timeElapsed.Seconds() < timeMax {
|
|
||||||
//nolint:nestif
|
|
||||||
if int64(timeElapsed.Seconds())%timeDelay == 0 {
|
|
||||||
data, err := c.GetVMNetworkInterfacesFromAgent(ctx)
|
|
||||||
|
|
||||||
if err == nil && data != nil && data.Result != nil {
|
|
||||||
hasAnyGlobalUnicast := false
|
|
||||||
|
|
||||||
if waitForIP {
|
|
||||||
for _, nic := range *data.Result {
|
|
||||||
if nic.Name == "lo" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if nic.IPAddresses == nil ||
|
|
||||||
(nic.IPAddresses != nil && len(*nic.IPAddresses) == 0) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, addr := range *nic.IPAddresses {
|
|
||||||
if ip := net.ParseIP(addr.Address); ip != nil && ip.IsGlobalUnicast() {
|
|
||||||
hasAnyGlobalUnicast = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if hasAnyGlobalUnicast {
|
|
||||||
return data, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
time.Sleep(1 * time.Second)
|
|
||||||
}
|
|
||||||
|
|
||||||
time.Sleep(200 * time.Millisecond)
|
|
||||||
|
|
||||||
timeElapsed = time.Since(timeStart)
|
|
||||||
|
|
||||||
if ctx.Err() != nil {
|
|
||||||
return nil, fmt.Errorf("error waiting for VM network interfaces: %w", ctx.Err())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, fmt.Errorf(
|
|
||||||
"timeout while waiting for the QEMU agent on VM \"%d\" to publish the network interfaces",
|
|
||||||
c.VMID,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// WaitForNoNetworkInterfacesFromVMAgent waits for a virtual machine's QEMU agent to unpublish the network interfaces.
|
|
||||||
func (c *Client) WaitForNoNetworkInterfacesFromVMAgent(ctx context.Context, timeout int, delay int) error {
|
|
||||||
timeDelay := int64(delay)
|
|
||||||
timeMax := float64(timeout)
|
|
||||||
timeStart := time.Now()
|
|
||||||
timeElapsed := timeStart.Sub(timeStart)
|
|
||||||
|
|
||||||
for timeElapsed.Seconds() < timeMax {
|
|
||||||
if int64(timeElapsed.Seconds())%timeDelay == 0 {
|
|
||||||
_, err := c.GetVMNetworkInterfacesFromAgent(ctx)
|
|
||||||
if err == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
time.Sleep(1 * time.Second)
|
|
||||||
}
|
|
||||||
|
|
||||||
time.Sleep(200 * time.Millisecond)
|
|
||||||
|
|
||||||
timeElapsed = time.Since(timeStart)
|
|
||||||
|
|
||||||
if ctx.Err() != nil {
|
|
||||||
return fmt.Errorf("error waiting for VM network interfaces: %w", ctx.Err())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Errorf(
|
|
||||||
"timeout while waiting for the QEMU agent on VM \"%d\" to unpublish the network interfaces",
|
|
||||||
c.VMID,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// WaitForVMConfigUnlock waits for a virtual machine configuration to become unlocked.
|
|
||||||
func (c *Client) WaitForVMConfigUnlock(ctx context.Context, timeout int, delay int, ignoreErrorResponse bool) error {
|
|
||||||
timeDelay := int64(delay)
|
|
||||||
timeMax := float64(timeout)
|
|
||||||
timeStart := time.Now()
|
|
||||||
timeElapsed := timeStart.Sub(timeStart)
|
|
||||||
|
|
||||||
for timeElapsed.Seconds() < timeMax {
|
|
||||||
if int64(timeElapsed.Seconds())%timeDelay == 0 {
|
|
||||||
data, err := c.GetVMStatus(ctx)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
if !ignoreErrorResponse {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
} else if data.Lock == nil || *data.Lock == "" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
time.Sleep(1 * time.Second)
|
|
||||||
}
|
|
||||||
|
|
||||||
time.Sleep(200 * time.Millisecond)
|
|
||||||
|
|
||||||
timeElapsed = time.Since(timeStart)
|
|
||||||
|
|
||||||
if ctx.Err() != nil {
|
|
||||||
return fmt.Errorf("error waiting for VM configuration to become unlocked: %w", ctx.Err())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Errorf("timeout while waiting for VM \"%d\" configuration to become unlocked", c.VMID)
|
|
||||||
}
|
|
||||||
|
|
||||||
// WaitForVMState waits for a virtual machine to reach a specific state.
|
|
||||||
func (c *Client) WaitForVMState(ctx context.Context, state string, timeout int, delay int) error {
|
|
||||||
state = strings.ToLower(state)
|
|
||||||
|
|
||||||
timeDelay := int64(delay)
|
|
||||||
timeMax := float64(timeout)
|
|
||||||
timeStart := time.Now()
|
|
||||||
timeElapsed := timeStart.Sub(timeStart)
|
|
||||||
|
|
||||||
for timeElapsed.Seconds() < timeMax {
|
|
||||||
if int64(timeElapsed.Seconds())%timeDelay == 0 {
|
|
||||||
data, err := c.GetVMStatus(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if data.Status == state {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
time.Sleep(1 * time.Second)
|
|
||||||
}
|
|
||||||
|
|
||||||
time.Sleep(200 * time.Millisecond)
|
|
||||||
|
|
||||||
timeElapsed = time.Since(timeStart)
|
|
||||||
|
|
||||||
if ctx.Err() != nil {
|
|
||||||
return fmt.Errorf("error waiting for VM state: %w", ctx.Err())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Errorf("timeout while waiting for VM \"%d\" to enter the state \"%s\"", c.VMID, state)
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load Diff
@ -1,60 +0,0 @@
|
|||||||
/*
|
|
||||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package vms
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
|
|
||||||
"github.com/bpg/terraform-provider-proxmox/internal/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestCustomStorageDevice_UnmarshalJSON(t *testing.T) {
|
|
||||||
ds8gig := types.DiskSizeFromGigabytes(8)
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
line string
|
|
||||||
want *CustomStorageDevice
|
|
||||||
wantErr bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "simple volume",
|
|
||||||
line: `"local-lvm:vm-2041-disk-0,discard=on,ssd=1,iothread=1,size=8G"`,
|
|
||||||
want: &CustomStorageDevice{
|
|
||||||
Discard: types.StrPtr("on"),
|
|
||||||
Enabled: true,
|
|
||||||
FileVolume: "local-lvm:vm-2041-disk-0",
|
|
||||||
IOThread: types.BoolPtr(true),
|
|
||||||
Size: &ds8gig,
|
|
||||||
SSD: types.BoolPtr(true),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "raw volume type",
|
|
||||||
line: `"nfs:2041/vm-2041-disk-0.raw,discard=ignore,ssd=1,iothread=1,size=8G"`,
|
|
||||||
want: &CustomStorageDevice{
|
|
||||||
Discard: types.StrPtr("ignore"),
|
|
||||||
Enabled: true,
|
|
||||||
FileVolume: "nfs:2041/vm-2041-disk-0.raw",
|
|
||||||
Format: types.StrPtr("raw"),
|
|
||||||
IOThread: types.BoolPtr(true),
|
|
||||||
Size: &ds8gig,
|
|
||||||
SSD: types.BoolPtr(true),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
r := &CustomStorageDevice{}
|
|
||||||
if err := r.UnmarshalJSON([]byte(tt.line)); (err != nil) != tt.wantErr {
|
|
||||||
t.Errorf("UnmarshalJSON() error = %v, wantErr %v", err, tt.wantErr)
|
|
||||||
}
|
|
||||||
require.Equal(t, tt.want, r)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,16 +0,0 @@
|
|||||||
/*
|
|
||||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package pools
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/bpg/terraform-provider-proxmox/proxmox/api"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Client is an interface for accessing the Proxmox pools API.
|
|
||||||
type Client struct {
|
|
||||||
api.Client
|
|
||||||
}
|
|
@ -1,87 +0,0 @@
|
|||||||
/*
|
|
||||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package pools
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"sort"
|
|
||||||
|
|
||||||
"github.com/bpg/terraform-provider-proxmox/proxmox/api"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CreatePool creates a pool.
|
|
||||||
func (c *Client) CreatePool(ctx context.Context, d *PoolCreateRequestBody) error {
|
|
||||||
err := c.DoRequest(ctx, http.MethodPost, "pools", d, nil)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error creating pool: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeletePool deletes a pool.
|
|
||||||
func (c *Client) DeletePool(ctx context.Context, id string) error {
|
|
||||||
err := c.DoRequest(ctx, http.MethodDelete, fmt.Sprintf("pools/%s", url.PathEscape(id)), nil, nil)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error deleting pool: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetPool retrieves a pool.
|
|
||||||
func (c *Client) GetPool(ctx context.Context, id string) (*PoolGetResponseData, error) {
|
|
||||||
resBody := &PoolGetResponseBody{}
|
|
||||||
|
|
||||||
err := c.DoRequest(ctx, http.MethodGet, fmt.Sprintf("pools/%s", url.PathEscape(id)), nil, resBody)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error getting pool: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if resBody.Data == nil {
|
|
||||||
return nil, api.ErrNoDataObjectInResponse
|
|
||||||
}
|
|
||||||
|
|
||||||
sort.Slice(resBody.Data.Members, func(i, j int) bool {
|
|
||||||
return resBody.Data.Members[i].ID < resBody.Data.Members[j].ID
|
|
||||||
})
|
|
||||||
|
|
||||||
return resBody.Data, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListPools retrieves a list of pools.
|
|
||||||
func (c *Client) ListPools(ctx context.Context) ([]*PoolListResponseData, error) {
|
|
||||||
resBody := &PoolListResponseBody{}
|
|
||||||
|
|
||||||
err := c.DoRequest(ctx, http.MethodGet, "pools", nil, resBody)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error listing pools: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if resBody.Data == nil {
|
|
||||||
return nil, api.ErrNoDataObjectInResponse
|
|
||||||
}
|
|
||||||
|
|
||||||
sort.Slice(resBody.Data, func(i, j int) bool {
|
|
||||||
return resBody.Data[i].ID < resBody.Data[j].ID
|
|
||||||
})
|
|
||||||
|
|
||||||
return resBody.Data, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// UpdatePool updates a pool.
|
|
||||||
func (c *Client) UpdatePool(ctx context.Context, id string, d *PoolUpdateRequestBody) error {
|
|
||||||
err := c.DoRequest(ctx, http.MethodPut, fmt.Sprintf("pools/%s", url.PathEscape(id)), d, nil)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error updating pool: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
@ -1,49 +0,0 @@
|
|||||||
/*
|
|
||||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package pools
|
|
||||||
|
|
||||||
// PoolCreateRequestBody contains the data for a pool create request.
|
|
||||||
type PoolCreateRequestBody struct {
|
|
||||||
Comment *string `json:"comment,omitempty" url:"comment,omitempty"`
|
|
||||||
ID string `json:"groupid" url:"poolid"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// PoolGetResponseBody contains the body from a pool get response.
|
|
||||||
type PoolGetResponseBody struct {
|
|
||||||
Data *PoolGetResponseData `json:"data,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// PoolGetResponseData contains the data from a pool get response.
|
|
||||||
type PoolGetResponseData struct {
|
|
||||||
Comment *string `json:"comment,omitempty"`
|
|
||||||
Members []VirtualEnvironmentPoolGetResponseMembers `json:"members,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// VirtualEnvironmentPoolGetResponseMembers contains the members data from a pool get response.
|
|
||||||
type VirtualEnvironmentPoolGetResponseMembers struct {
|
|
||||||
ID string `json:"id"`
|
|
||||||
Node string `json:"node"`
|
|
||||||
DatastoreID *string `json:"storage,omitempty"`
|
|
||||||
Type string `json:"type"`
|
|
||||||
VMID *int `json:"vmid"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// PoolListResponseBody contains the body from a pool list response.
|
|
||||||
type PoolListResponseBody struct {
|
|
||||||
Data []*PoolListResponseData `json:"data,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// PoolListResponseData contains the data from a pool list response.
|
|
||||||
type PoolListResponseData struct {
|
|
||||||
Comment *string `json:"comment,omitempty"`
|
|
||||||
ID string `json:"poolid"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// PoolUpdateRequestBody contains the data for an pool update request.
|
|
||||||
type PoolUpdateRequestBody struct {
|
|
||||||
Comment *string `json:"comment,omitempty" url:"comment,omitempty"`
|
|
||||||
}
|
|
@ -1,343 +0,0 @@
|
|||||||
/*
|
|
||||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package ssh
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
"os"
|
|
||||||
"path"
|
|
||||||
"path/filepath"
|
|
||||||
"runtime"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/hashicorp/terraform-plugin-log/tflog"
|
|
||||||
"github.com/pkg/sftp"
|
|
||||||
"github.com/skeema/knownhosts"
|
|
||||||
"golang.org/x/crypto/ssh"
|
|
||||||
"golang.org/x/crypto/ssh/agent"
|
|
||||||
|
|
||||||
"github.com/bpg/terraform-provider-proxmox/proxmox/api"
|
|
||||||
"github.com/bpg/terraform-provider-proxmox/utils"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Client is an interface for performing SSH requests against the Proxmox Nodes.
|
|
||||||
type Client interface {
|
|
||||||
// ExecuteNodeCommands executes a command on a node.
|
|
||||||
ExecuteNodeCommands(ctx context.Context, nodeName string, commands []string) error
|
|
||||||
|
|
||||||
// NodeUpload uploads a file to a node.
|
|
||||||
NodeUpload(ctx context.Context, nodeName string,
|
|
||||||
remoteFileDir string, fileUploadRequest *api.FileUploadRequest) error
|
|
||||||
}
|
|
||||||
|
|
||||||
type client struct {
|
|
||||||
username string
|
|
||||||
password string
|
|
||||||
agent bool
|
|
||||||
agentSocket string
|
|
||||||
nodeLookup NodeResolver
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewClient creates a new SSH client.
|
|
||||||
func NewClient(
|
|
||||||
username string, password string,
|
|
||||||
agent bool, agentSocket string,
|
|
||||||
nodeLookup NodeResolver,
|
|
||||||
) (Client, error) {
|
|
||||||
//goland:noinspection GoBoolExpressions
|
|
||||||
if agent && runtime.GOOS != "linux" && runtime.GOOS != "darwin" && runtime.GOOS != "freebsd" {
|
|
||||||
return nil, errors.New(
|
|
||||||
"the ssh agent flag is only supported on POSIX systems, please set it to 'false'" +
|
|
||||||
" or remove it from your provider configuration",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if nodeLookup == nil {
|
|
||||||
return nil, errors.New("node lookup is required")
|
|
||||||
}
|
|
||||||
|
|
||||||
return &client{
|
|
||||||
username: username,
|
|
||||||
password: password,
|
|
||||||
agent: agent,
|
|
||||||
agentSocket: agentSocket,
|
|
||||||
nodeLookup: nodeLookup,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ExecuteNodeCommands executes commands on a given node.
|
|
||||||
func (c *client) ExecuteNodeCommands(ctx context.Context, nodeName string, commands []string) error {
|
|
||||||
ip, err := c.nodeLookup.Resolve(ctx, nodeName)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to find node endpoint: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
tflog.Debug(ctx, "executing commands on the node using SSH", map[string]interface{}{
|
|
||||||
"node_address": ip,
|
|
||||||
"commands": commands,
|
|
||||||
})
|
|
||||||
|
|
||||||
closeOrLogError := utils.CloseOrLogError(ctx)
|
|
||||||
|
|
||||||
sshClient, err := c.openNodeShell(ctx, ip)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
defer closeOrLogError(sshClient)
|
|
||||||
|
|
||||||
sshSession, err := sshClient.NewSession()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to create SSH session: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
defer closeOrLogError(sshSession)
|
|
||||||
|
|
||||||
script := strings.Join(commands, " && \\\n")
|
|
||||||
|
|
||||||
output, err := sshSession.CombinedOutput(
|
|
||||||
fmt.Sprintf(
|
|
||||||
"/bin/bash -c '%s'",
|
|
||||||
strings.ReplaceAll(script, "'", "'\"'\"'"),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return errors.New(string(output))
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *client) NodeUpload(
|
|
||||||
ctx context.Context,
|
|
||||||
nodeName string,
|
|
||||||
remoteFileDir string,
|
|
||||||
d *api.FileUploadRequest,
|
|
||||||
) error {
|
|
||||||
ip, err := c.nodeLookup.Resolve(ctx, nodeName)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to find node endpoint: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
tflog.Debug(ctx, "uploading file to the node datastore using SFTP", map[string]interface{}{
|
|
||||||
"node_address": ip,
|
|
||||||
"remote_dir": remoteFileDir,
|
|
||||||
"file_name": d.FileName,
|
|
||||||
"content_type": d.ContentType,
|
|
||||||
})
|
|
||||||
|
|
||||||
fileInfo, err := d.File.Stat()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to get file info: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
fileSize := fileInfo.Size()
|
|
||||||
|
|
||||||
sshClient, err := c.openNodeShell(ctx, ip)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to open SSH client: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
defer func(sshClient *ssh.Client) {
|
|
||||||
e := sshClient.Close()
|
|
||||||
if e != nil {
|
|
||||||
tflog.Error(ctx, "failed to close SSH client", map[string]interface{}{
|
|
||||||
"error": e,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}(sshClient)
|
|
||||||
|
|
||||||
if d.ContentType != "" {
|
|
||||||
remoteFileDir = filepath.Join(remoteFileDir, d.ContentType)
|
|
||||||
}
|
|
||||||
|
|
||||||
remoteFilePath := strings.ReplaceAll(filepath.Join(remoteFileDir, d.FileName), `\`, `/`)
|
|
||||||
|
|
||||||
sftpClient, err := sftp.NewClient(sshClient)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to create SFTP client: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
defer func(sftpClient *sftp.Client) {
|
|
||||||
e := sftpClient.Close()
|
|
||||||
if e != nil {
|
|
||||||
tflog.Error(ctx, "failed to close SFTP client", map[string]interface{}{
|
|
||||||
"error": e,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}(sftpClient)
|
|
||||||
|
|
||||||
err = sftpClient.MkdirAll(remoteFileDir)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to create directory %s: %w", remoteFileDir, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
remoteFile, err := sftpClient.Create(remoteFilePath)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to create file %s: %w", remoteFilePath, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
defer func(remoteFile *sftp.File) {
|
|
||||||
e := remoteFile.Close()
|
|
||||||
if e != nil {
|
|
||||||
tflog.Error(ctx, "failed to close remote file", map[string]interface{}{
|
|
||||||
"error": e,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}(remoteFile)
|
|
||||||
|
|
||||||
bytesUploaded, err := remoteFile.ReadFrom(d.File)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to upload file %s: %w", remoteFilePath, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if bytesUploaded != fileSize {
|
|
||||||
return fmt.Errorf("failed to upload file %s: uploaded %d bytes, expected %d bytes",
|
|
||||||
remoteFilePath, bytesUploaded, fileSize)
|
|
||||||
}
|
|
||||||
|
|
||||||
tflog.Debug(ctx, "uploaded file to datastore", map[string]interface{}{
|
|
||||||
"remote_file_path": remoteFilePath,
|
|
||||||
"size": bytesUploaded,
|
|
||||||
})
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// openNodeShell establishes a new SSH connection to a node.
|
|
||||||
func (c *client) openNodeShell(ctx context.Context, nodeAddress string) (*ssh.Client, error) {
|
|
||||||
homeDir, err := os.UserHomeDir()
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to determine the home directory: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
sshHost := fmt.Sprintf("%s:22", nodeAddress)
|
|
||||||
|
|
||||||
sshPath := path.Join(homeDir, ".ssh")
|
|
||||||
if _, err = os.Stat(sshPath); os.IsNotExist(err) {
|
|
||||||
e := os.Mkdir(sshPath, 0o700)
|
|
||||||
if e != nil {
|
|
||||||
return nil, fmt.Errorf("failed to create %s: %w", sshPath, e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
khPath := path.Join(sshPath, "known_hosts")
|
|
||||||
if _, err = os.Stat(khPath); os.IsNotExist(err) {
|
|
||||||
e := os.WriteFile(khPath, []byte{}, 0o600)
|
|
||||||
if e != nil {
|
|
||||||
return nil, fmt.Errorf("failed to create %s: %w", khPath, e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
kh, err := knownhosts.New(khPath)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to read %s: %w", khPath, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a custom permissive hostkey callback which still errors on hosts
|
|
||||||
// with changed keys, but allows unknown hosts and adds them to known_hosts
|
|
||||||
cb := ssh.HostKeyCallback(func(hostname string, remote net.Addr, key ssh.PublicKey) error {
|
|
||||||
kherr := kh(hostname, remote, key)
|
|
||||||
if knownhosts.IsHostKeyChanged(kherr) {
|
|
||||||
return fmt.Errorf("REMOTE HOST IDENTIFICATION HAS CHANGED for host %s! This may indicate a MitM attack", hostname)
|
|
||||||
}
|
|
||||||
|
|
||||||
if knownhosts.IsHostUnknown(kherr) {
|
|
||||||
f, ferr := os.OpenFile(khPath, os.O_APPEND|os.O_WRONLY, 0o600)
|
|
||||||
if ferr == nil {
|
|
||||||
defer utils.CloseOrLogError(ctx)(f)
|
|
||||||
ferr = knownhosts.WriteKnownHost(f, hostname, remote, key)
|
|
||||||
}
|
|
||||||
if ferr == nil {
|
|
||||||
tflog.Info(ctx, fmt.Sprintf("Added host %s to known_hosts", hostname))
|
|
||||||
} else {
|
|
||||||
tflog.Error(ctx, fmt.Sprintf("Failed to add host %s to known_hosts", hostname), map[string]interface{}{
|
|
||||||
"error": kherr,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return kherr
|
|
||||||
})
|
|
||||||
|
|
||||||
sshConfig := &ssh.ClientConfig{
|
|
||||||
User: c.username,
|
|
||||||
Auth: []ssh.AuthMethod{ssh.Password(c.password)},
|
|
||||||
HostKeyCallback: cb,
|
|
||||||
HostKeyAlgorithms: kh.HostKeyAlgorithms(sshHost),
|
|
||||||
}
|
|
||||||
|
|
||||||
tflog.Info(ctx, fmt.Sprintf("agent is set to %t", c.agent))
|
|
||||||
|
|
||||||
var sshClient *ssh.Client
|
|
||||||
if c.agent {
|
|
||||||
sshClient, err = c.createSSHClientAgent(ctx, cb, kh, sshHost)
|
|
||||||
if err != nil {
|
|
||||||
tflog.Error(ctx, "Failed ssh connection through agent, "+
|
|
||||||
"falling back to password authentication",
|
|
||||||
map[string]interface{}{
|
|
||||||
"error": err,
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
return sshClient, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sshClient, err = ssh.Dial("tcp", sshHost, sshConfig)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to dial %s: %w", sshHost, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
tflog.Debug(ctx, "SSH connection established", map[string]interface{}{
|
|
||||||
"host": sshHost,
|
|
||||||
"user": c.username,
|
|
||||||
})
|
|
||||||
|
|
||||||
return sshClient, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// createSSHClientAgent establishes an ssh connection through the agent authentication mechanism.
|
|
||||||
func (c *client) createSSHClientAgent(
|
|
||||||
ctx context.Context,
|
|
||||||
cb ssh.HostKeyCallback,
|
|
||||||
kh knownhosts.HostKeyCallback,
|
|
||||||
sshHost string,
|
|
||||||
) (*ssh.Client, error) {
|
|
||||||
if c.agentSocket == "" {
|
|
||||||
return nil, errors.New("failed connecting to SSH agent socket: the socket file is not defined, " +
|
|
||||||
"authentication will fall back to password")
|
|
||||||
}
|
|
||||||
|
|
||||||
conn, err := net.Dial("unix", c.agentSocket)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed connecting to SSH auth socket '%s': %w", c.agentSocket, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
ag := agent.NewClient(conn)
|
|
||||||
|
|
||||||
sshConfig := &ssh.ClientConfig{
|
|
||||||
User: c.username,
|
|
||||||
Auth: []ssh.AuthMethod{ssh.PublicKeysCallback(ag.Signers), ssh.Password(c.password)},
|
|
||||||
HostKeyCallback: cb,
|
|
||||||
HostKeyAlgorithms: kh.HostKeyAlgorithms(sshHost),
|
|
||||||
}
|
|
||||||
|
|
||||||
sshClient, err := ssh.Dial("tcp", sshHost, sshConfig)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to dial %s: %w", sshHost, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
tflog.Debug(ctx, "SSH connection established", map[string]interface{}{
|
|
||||||
"host": sshHost,
|
|
||||||
"user": c.username,
|
|
||||||
})
|
|
||||||
|
|
||||||
return sshClient, nil
|
|
||||||
}
|
|
@ -1,16 +0,0 @@
|
|||||||
/*
|
|
||||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package ssh
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
)
|
|
||||||
|
|
||||||
// NodeResolver is an interface for resolving node names to IP addresses to use for SSH connection.
|
|
||||||
type NodeResolver interface {
|
|
||||||
Resolve(ctx context.Context, nodeName string) (string, error)
|
|
||||||
}
|
|
@ -1,16 +0,0 @@
|
|||||||
/*
|
|
||||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package storage
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/bpg/terraform-provider-proxmox/proxmox/api"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Client is an interface for accessing the Proxmox storage API.
|
|
||||||
type Client struct {
|
|
||||||
api.Client
|
|
||||||
}
|
|
@ -1,54 +0,0 @@
|
|||||||
/*
|
|
||||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package storage
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
)
|
|
||||||
|
|
||||||
// GetDatastore retrieves information about a datastore.
|
|
||||||
/*
|
|
||||||
Using undocumented API endpoints is not recommended, but sometimes it's the only way to get things done.
|
|
||||||
$ pvesh get /storage/local
|
|
||||||
┌─────────┬───────────────────────────────────────────┐
|
|
||||||
│ key │ value │
|
|
||||||
╞═════════╪═══════════════════════════════════════════╡
|
|
||||||
│ content │ images,vztmpl,iso,backup,snippets,rootdir │
|
|
||||||
├─────────┼───────────────────────────────────────────┤
|
|
||||||
│ digest │ 5b65ede80f34631d6039e6922845cfa4abc956be │
|
|
||||||
├─────────┼───────────────────────────────────────────┤
|
|
||||||
│ path │ /var/lib/vz │
|
|
||||||
├─────────┼───────────────────────────────────────────┤
|
|
||||||
│ shared │ 0 │
|
|
||||||
├─────────┼───────────────────────────────────────────┤
|
|
||||||
│ storage │ local │
|
|
||||||
├─────────┼───────────────────────────────────────────┤
|
|
||||||
│ type │ dir │
|
|
||||||
└─────────┴───────────────────────────────────────────┘.
|
|
||||||
*/
|
|
||||||
func (c *Client) GetDatastore(
|
|
||||||
ctx context.Context,
|
|
||||||
datastoreID string,
|
|
||||||
) (*DatastoreGetResponseData, error) {
|
|
||||||
resBody := &DatastoreGetResponseBody{}
|
|
||||||
|
|
||||||
err := c.DoRequest(
|
|
||||||
ctx,
|
|
||||||
http.MethodGet,
|
|
||||||
fmt.Sprintf("storage/%s", url.PathEscape(datastoreID)),
|
|
||||||
nil,
|
|
||||||
resBody,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error retrieving datastore %s: %w", datastoreID, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return resBody.Data, nil
|
|
||||||
}
|
|
@ -1,26 +0,0 @@
|
|||||||
/*
|
|
||||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package storage
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/bpg/terraform-provider-proxmox/internal/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
// DatastoreGetResponseBody contains the body from a datastore get response.
|
|
||||||
type DatastoreGetResponseBody struct {
|
|
||||||
Data *DatastoreGetResponseData `json:"data,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// DatastoreGetResponseData contains the data from a datastore get response.
|
|
||||||
type DatastoreGetResponseData struct {
|
|
||||||
Content types.CustomCommaSeparatedList `json:"content,omitempty" url:"content,omitempty,comma"`
|
|
||||||
Digest *string `json:"digest,omitempty"`
|
|
||||||
Path *string `json:"path,omitempty"`
|
|
||||||
Shared *types.CustomBool `json:"shared,omitempty"`
|
|
||||||
Storage *string `json:"storage,omitempty"`
|
|
||||||
Type *string `json:"type,omitempty"`
|
|
||||||
}
|
|
@ -1,16 +0,0 @@
|
|||||||
/*
|
|
||||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package version
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/bpg/terraform-provider-proxmox/proxmox/api"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Client is an interface for accessing the Proxmox version API.
|
|
||||||
type Client struct {
|
|
||||||
api.Client
|
|
||||||
}
|
|
@ -1,31 +0,0 @@
|
|||||||
/*
|
|
||||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package version
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/bpg/terraform-provider-proxmox/proxmox/api"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Version retrieves the version information.
|
|
||||||
func (c *Client) Version(ctx context.Context) (*ResponseData, error) {
|
|
||||||
resBody := &ResponseBody{}
|
|
||||||
|
|
||||||
err := c.DoRequest(ctx, http.MethodGet, "version", nil, resBody)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to get version information: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if resBody.Data == nil {
|
|
||||||
return nil, api.ErrNoDataObjectInResponse
|
|
||||||
}
|
|
||||||
|
|
||||||
return resBody.Data, nil
|
|
||||||
}
|
|
@ -1,20 +0,0 @@
|
|||||||
/*
|
|
||||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package version
|
|
||||||
|
|
||||||
// ResponseBody contains the body from a version response.
|
|
||||||
type ResponseBody struct {
|
|
||||||
Data *ResponseData `json:"data,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// ResponseData contains the data from a version response.
|
|
||||||
type ResponseData struct {
|
|
||||||
Keyboard string `json:"keyboard"`
|
|
||||||
Release string `json:"release"`
|
|
||||||
RepositoryID string `json:"repoid"`
|
|
||||||
Version string `json:"version"`
|
|
||||||
}
|
|
@ -9,31 +9,31 @@ package proxmoxtf
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
|
||||||
"github.com/bpg/terraform-provider-proxmox/proxmox"
|
"github.com/bpg/proxmox-api"
|
||||||
"github.com/bpg/terraform-provider-proxmox/proxmox/api"
|
"github.com/bpg/proxmox-api/rest"
|
||||||
"github.com/bpg/terraform-provider-proxmox/proxmox/ssh"
|
"github.com/bpg/proxmox-api/ssh"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ProviderConfiguration is the configuration for the provider.
|
// ProviderConfiguration is the configuration for the provider.
|
||||||
type ProviderConfiguration struct {
|
type ProviderConfiguration struct {
|
||||||
apiClient api.Client
|
restClient rest.Client
|
||||||
sshClient ssh.Client
|
sshClient ssh.Client
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewProviderConfiguration creates a new provider configuration.
|
// NewProviderConfiguration creates a new provider configuration.
|
||||||
func NewProviderConfiguration(
|
func NewProviderConfiguration(
|
||||||
apiClient api.Client,
|
restClient rest.Client,
|
||||||
sshClient ssh.Client,
|
sshClient ssh.Client,
|
||||||
) ProviderConfiguration {
|
) ProviderConfiguration {
|
||||||
return ProviderConfiguration{
|
return ProviderConfiguration{
|
||||||
apiClient: apiClient,
|
restClient: restClient,
|
||||||
sshClient: sshClient,
|
sshClient: sshClient,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetClient returns the Proxmox API client.
|
// GetClient returns the Proxmox API client.
|
||||||
func (c *ProviderConfiguration) GetClient() (proxmox.Client, error) {
|
func (c *ProviderConfiguration) GetClient() (proxmox.Client, error) {
|
||||||
if c.apiClient == nil {
|
if c.restClient == nil {
|
||||||
return nil, errors.New(
|
return nil, errors.New(
|
||||||
"you must specify the API access details in the provider configuration",
|
"you must specify the API access details in the provider configuration",
|
||||||
)
|
)
|
||||||
@ -45,5 +45,5 @@ func (c *ProviderConfiguration) GetClient() (proxmox.Client, error) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return proxmox.NewClient(c.apiClient, c.sshClient), nil
|
return proxmox.NewClient(c.restClient, c.sshClient), nil
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,8 @@ import (
|
|||||||
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
|
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
|
||||||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
|
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
|
||||||
|
|
||||||
fw "github.com/bpg/terraform-provider-proxmox/proxmox/firewall"
|
fw "github.com/bpg/proxmox-api/firewall"
|
||||||
|
|
||||||
"github.com/bpg/terraform-provider-proxmox/proxmoxtf"
|
"github.com/bpg/terraform-provider-proxmox/proxmoxtf"
|
||||||
"github.com/bpg/terraform-provider-proxmox/proxmoxtf/datasource/firewall"
|
"github.com/bpg/terraform-provider-proxmox/proxmoxtf/datasource/firewall"
|
||||||
)
|
)
|
||||||
|
@ -12,7 +12,7 @@ import (
|
|||||||
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
|
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
|
||||||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
|
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
|
||||||
|
|
||||||
"github.com/bpg/terraform-provider-proxmox/proxmox/firewall"
|
"github.com/bpg/proxmox-api/firewall"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -12,7 +12,7 @@ import (
|
|||||||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
|
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
"github.com/bpg/terraform-provider-proxmox/proxmoxtf/structure"
|
"github.com/bpg/terraform-provider-proxmox/proxmoxtf/test"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TestAliasSchemaInstantiation tests whether the AliasSchema instance can be instantiated.
|
// TestAliasSchemaInstantiation tests whether the AliasSchema instance can be instantiated.
|
||||||
@ -25,18 +25,18 @@ func TestAliasSchemaInstantiation(t *testing.T) {
|
|||||||
func TestAliasSchema(t *testing.T) {
|
func TestAliasSchema(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
s := AliasSchema()
|
r := schema.Resource{Schema: AliasSchema()}
|
||||||
|
|
||||||
structure.AssertRequiredArguments(t, s, []string{
|
test.AssertRequiredArguments(t, &r, []string{
|
||||||
mkAliasName,
|
mkAliasName,
|
||||||
})
|
})
|
||||||
|
|
||||||
structure.AssertComputedAttributes(t, s, []string{
|
test.AssertComputedAttributes(t, &r, []string{
|
||||||
mkAliasCIDR,
|
mkAliasCIDR,
|
||||||
mkAliasComment,
|
mkAliasComment,
|
||||||
})
|
})
|
||||||
|
|
||||||
structure.AssertValueTypes(t, s, map[string]schema.ValueType{
|
test.AssertValueTypes(t, &r, map[string]schema.ValueType{
|
||||||
mkAliasName: schema.TypeString,
|
mkAliasName: schema.TypeString,
|
||||||
mkAliasCIDR: schema.TypeString,
|
mkAliasCIDR: schema.TypeString,
|
||||||
mkAliasComment: schema.TypeString,
|
mkAliasComment: schema.TypeString,
|
||||||
|
@ -13,7 +13,7 @@ import (
|
|||||||
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
|
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
|
||||||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
|
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
|
||||||
|
|
||||||
"github.com/bpg/terraform-provider-proxmox/proxmox/firewall"
|
"github.com/bpg/proxmox-api/firewall"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -12,7 +12,7 @@ import (
|
|||||||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
|
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
"github.com/bpg/terraform-provider-proxmox/proxmoxtf/structure"
|
"github.com/bpg/terraform-provider-proxmox/proxmoxtf/test"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TestAliasesSchemaInstantiation tests whether the AliasesSchema instance can be instantiated.
|
// TestAliasesSchemaInstantiation tests whether the AliasesSchema instance can be instantiated.
|
||||||
@ -25,13 +25,13 @@ func TestAliasesSchemaInstantiation(t *testing.T) {
|
|||||||
func TestAliasesSchema(t *testing.T) {
|
func TestAliasesSchema(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
s := AliasesSchema()
|
r := schema.Resource{Schema: AliasesSchema()}
|
||||||
|
|
||||||
structure.AssertComputedAttributes(t, s, []string{
|
test.AssertComputedAttributes(t, &r, []string{
|
||||||
mkAliasesAliasNames,
|
mkAliasesAliasNames,
|
||||||
})
|
})
|
||||||
|
|
||||||
structure.AssertValueTypes(t, s, map[string]schema.ValueType{
|
test.AssertValueTypes(t, &r, map[string]schema.ValueType{
|
||||||
mkAliasesAliasNames: schema.TypeList,
|
mkAliasesAliasNames: schema.TypeList,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,7 @@ import (
|
|||||||
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
|
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
|
||||||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
|
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
|
||||||
|
|
||||||
"github.com/bpg/terraform-provider-proxmox/proxmox/firewall"
|
"github.com/bpg/proxmox-api/firewall"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -12,7 +12,7 @@ import (
|
|||||||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
|
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
"github.com/bpg/terraform-provider-proxmox/proxmoxtf/structure"
|
"github.com/bpg/terraform-provider-proxmox/proxmoxtf/test"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TestIPSetSchemaInstantiation tests whether the IPSetSchema instance can be instantiated.
|
// TestIPSetSchemaInstantiation tests whether the IPSetSchema instance can be instantiated.
|
||||||
@ -25,32 +25,32 @@ func TestIPSetSchemaInstantiation(t *testing.T) {
|
|||||||
func TestIPSetSchema(t *testing.T) {
|
func TestIPSetSchema(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
s := IPSetSchema()
|
r := schema.Resource{Schema: IPSetSchema()}
|
||||||
|
|
||||||
structure.AssertRequiredArguments(t, s, []string{
|
test.AssertRequiredArguments(t, &r, []string{
|
||||||
mkIPSetName,
|
mkIPSetName,
|
||||||
})
|
})
|
||||||
|
|
||||||
structure.AssertComputedAttributes(t, s, []string{
|
test.AssertComputedAttributes(t, &r, []string{
|
||||||
mkIPSetCIDR,
|
mkIPSetCIDR,
|
||||||
mkIPSetCIDRComment,
|
mkIPSetCIDRComment,
|
||||||
})
|
})
|
||||||
|
|
||||||
structure.AssertValueTypes(t, s, map[string]schema.ValueType{
|
test.AssertValueTypes(t, &r, map[string]schema.ValueType{
|
||||||
mkIPSetName: schema.TypeString,
|
mkIPSetName: schema.TypeString,
|
||||||
mkIPSetCIDR: schema.TypeList,
|
mkIPSetCIDR: schema.TypeList,
|
||||||
mkIPSetCIDRComment: schema.TypeString,
|
mkIPSetCIDRComment: schema.TypeString,
|
||||||
})
|
})
|
||||||
|
|
||||||
cirdSchema := structure.AssertNestedSchemaExistence(t, s, mkIPSetCIDR).Schema
|
cidr := test.AssertNestedSchemaExistence(t, &r, mkIPSetCIDR)
|
||||||
|
|
||||||
structure.AssertComputedAttributes(t, cirdSchema, []string{
|
test.AssertComputedAttributes(t, cidr, []string{
|
||||||
mkIPSetCIDRName,
|
mkIPSetCIDRName,
|
||||||
mkIPSetCIDRNoMatch,
|
mkIPSetCIDRNoMatch,
|
||||||
mkIPSetCIDRComment,
|
mkIPSetCIDRComment,
|
||||||
})
|
})
|
||||||
|
|
||||||
structure.AssertValueTypes(t, cirdSchema, map[string]schema.ValueType{
|
test.AssertValueTypes(t, cidr, map[string]schema.ValueType{
|
||||||
mkIPSetCIDRName: schema.TypeString,
|
mkIPSetCIDRName: schema.TypeString,
|
||||||
mkIPSetCIDRNoMatch: schema.TypeBool,
|
mkIPSetCIDRNoMatch: schema.TypeBool,
|
||||||
mkIPSetCIDRComment: schema.TypeString,
|
mkIPSetCIDRComment: schema.TypeString,
|
||||||
|
@ -13,7 +13,7 @@ import (
|
|||||||
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
|
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
|
||||||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
|
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
|
||||||
|
|
||||||
"github.com/bpg/terraform-provider-proxmox/proxmox/firewall"
|
"github.com/bpg/proxmox-api/firewall"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -12,7 +12,7 @@ import (
|
|||||||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
|
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
"github.com/bpg/terraform-provider-proxmox/proxmoxtf/structure"
|
"github.com/bpg/terraform-provider-proxmox/proxmoxtf/test"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TestIPSetsSchemaInstantiation tests whether the IPSetsSchema instance can be instantiated.
|
// TestIPSetsSchemaInstantiation tests whether the IPSetsSchema instance can be instantiated.
|
||||||
@ -25,13 +25,13 @@ func TestIPSetsSchemaInstantiation(t *testing.T) {
|
|||||||
func TestIPSetsSchema(t *testing.T) {
|
func TestIPSetsSchema(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
s := IPSetsSchema()
|
r := schema.Resource{Schema: IPSetsSchema()}
|
||||||
|
|
||||||
structure.AssertComputedAttributes(t, s, []string{
|
test.AssertComputedAttributes(t, &r, []string{
|
||||||
mkIPSetsIPSetNames,
|
mkIPSetsIPSetNames,
|
||||||
})
|
})
|
||||||
|
|
||||||
structure.AssertValueTypes(t, s, map[string]schema.ValueType{
|
test.AssertValueTypes(t, &r, map[string]schema.ValueType{
|
||||||
mkIPSetsIPSetNames: schema.TypeList,
|
mkIPSetsIPSetNames: schema.TypeList,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,7 @@ import (
|
|||||||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
|
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
"github.com/bpg/terraform-provider-proxmox/proxmoxtf/structure"
|
"github.com/bpg/terraform-provider-proxmox/proxmoxtf/test"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TestRuleInstantiation tests whether the RuleSchema instance can be instantiated.
|
// TestRuleInstantiation tests whether the RuleSchema instance can be instantiated.
|
||||||
@ -25,14 +25,14 @@ func TestRuleSchemaInstantiation(t *testing.T) {
|
|||||||
func TestRuleSchema(t *testing.T) {
|
func TestRuleSchema(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
ruleSchema := RuleSchema()
|
r := schema.Resource{Schema: RuleSchema()}
|
||||||
|
|
||||||
structure.AssertRequiredArguments(t, ruleSchema, []string{
|
test.AssertRequiredArguments(t, &r, []string{
|
||||||
mkRuleAction,
|
mkRuleAction,
|
||||||
mkRuleType,
|
mkRuleType,
|
||||||
})
|
})
|
||||||
|
|
||||||
structure.AssertComputedAttributes(t, ruleSchema, []string{
|
test.AssertComputedAttributes(t, &r, []string{
|
||||||
mkRuleComment,
|
mkRuleComment,
|
||||||
mkRuleDest,
|
mkRuleDest,
|
||||||
mkRuleDPort,
|
mkRuleDPort,
|
||||||
@ -45,7 +45,7 @@ func TestRuleSchema(t *testing.T) {
|
|||||||
mkRuleSPort,
|
mkRuleSPort,
|
||||||
})
|
})
|
||||||
|
|
||||||
structure.AssertValueTypes(t, ruleSchema, map[string]schema.ValueType{
|
test.AssertValueTypes(t, &r, map[string]schema.ValueType{
|
||||||
mkRulePos: schema.TypeInt,
|
mkRulePos: schema.TypeInt,
|
||||||
mkRuleAction: schema.TypeString,
|
mkRuleAction: schema.TypeString,
|
||||||
mkRuleType: schema.TypeString,
|
mkRuleType: schema.TypeString,
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user