0
0
mirror of https://github.com/bpg/terraform-provider-proxmox.git synced 2025-06-29 18:21:10 +00:00

feat: Extract PVE client to a separate repo

This commit is contained in:
Pavel Boldyrev 2023-07-15 20:34:58 -04:00
parent 6a3bc03470
commit 98bc511140
No known key found for this signature in database
GPG Key ID: 02A24794ADAC7455
133 changed files with 227 additions and 8630 deletions

View File

@ -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:

View File

@ -1 +0,0 @@
1.18.0

14
go.mod
View File

@ -2,8 +2,11 @@ module github.com/bpg/terraform-provider-proxmox
go 1.20
replace github.com/bpg/proxmox-api => ../proxmox-api
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/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320
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-mux v0.11.1
github.com/hashicorp/terraform-plugin-sdk/v2 v2.27.0
github.com/pkg/sftp v1.13.5
github.com/skeema/knownhosts v1.1.1
github.com/pkg/sftp v1.13.5 // indirect
github.com/skeema/knownhosts v1.1.1 // indirect
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
)
@ -45,7 +48,7 @@ require (
github.com/hashicorp/terraform-svchost v0.1.1 // indirect
github.com/hashicorp/yamux v0.1.1 // 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-isatty v0.0.17 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
@ -55,6 +58,7 @@ require (
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/oklog/run v1.1.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/v5 v5.3.5 // indirect
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect

3
go.sum
View File

@ -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/v13 v13.0.0 h1:Y+KvPE1NYz0xl601PVImeQfFyEy6iT90AvPUL1NNfNw=
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/cloudflare/circl v1.1.0/go.mod h1:prBCrKB9DV4poKZY1l9zBXg2QJY7mvgRvtMxxK7fi4I=
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/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
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/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=

7
go.work Normal file
View File

@ -0,0 +1,7 @@
go 1.20
use (
.
tools
../proxmox-api
)

8
go.work.sum Normal file
View 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=

View File

@ -24,9 +24,12 @@ import (
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types"
pvetypes "github.com/bpg/terraform-provider-proxmox/internal/types"
"github.com/bpg/terraform-provider-proxmox/proxmox"
"github.com/bpg/terraform-provider-proxmox/proxmox/nodes"
"github.com/bpg/proxmox-api"
"github.com/bpg/proxmox-api/nodes"
pvetypes "github.com/bpg/proxmox-api/types"
nettypes "github.com/bpg/terraform-provider-proxmox/internal/network/types"
)
var (
@ -40,10 +43,10 @@ type linuxBridgeResourceModel struct {
ID types.String `tfsdk:"id"`
NodeName types.String `tfsdk:"node_name"`
Name types.String `tfsdk:"name"`
Address pvetypes.IPCIDRValue `tfsdk:"address"`
Gateway pvetypes.IPAddrValue `tfsdk:"gateway"`
Address6 pvetypes.IPCIDRValue `tfsdk:"address6"`
Gateway6 pvetypes.IPAddrValue `tfsdk:"gateway6"`
Address nettypes.IPCIDRValue `tfsdk:"address"`
Gateway nettypes.IPAddrValue `tfsdk:"gateway"`
Address6 nettypes.IPCIDRValue `tfsdk:"address6"`
Gateway6 nettypes.IPAddrValue `tfsdk:"gateway6"`
Autostart types.Bool `tfsdk:"autostart"`
MTU types.Int64 `tfsdk:"mtu"`
Comment types.String `tfsdk:"comment"`
@ -95,10 +98,10 @@ func (m *linuxBridgeResourceModel) importFromNetworkInterfaceList(
ctx context.Context,
iface *nodes.NetworkInterfaceListResponseData,
) error {
m.Address = pvetypes.NewIPCIDRPointerValue(iface.CIDR)
m.Gateway = pvetypes.NewIPAddrPointerValue(iface.Gateway)
m.Address6 = pvetypes.NewIPCIDRPointerValue(iface.CIDR6)
m.Gateway6 = pvetypes.NewIPAddrPointerValue(iface.Gateway6)
m.Address = nettypes.NewIPCIDRPointerValue(iface.CIDR)
m.Gateway = nettypes.NewIPAddrPointerValue(iface.Gateway)
m.Address6 = nettypes.NewIPCIDRPointerValue(iface.CIDR6)
m.Gateway6 = nettypes.NewIPAddrPointerValue(iface.Gateway6)
m.Autostart = types.BoolPointerValue(iface.Autostart.PointerBool())
if iface.MTU != nil {
@ -189,22 +192,22 @@ func (r *linuxBridgeResource) Schema(
},
"address": schema.StringAttribute{
Description: "The interface IPv4/CIDR address.",
CustomType: pvetypes.IPCIDRType{},
CustomType: nettypes.IPCIDRType{},
Optional: true,
},
"gateway": schema.StringAttribute{
Description: "Default gateway address.",
CustomType: pvetypes.IPAddrType{},
CustomType: nettypes.IPAddrType{},
Optional: true,
},
"address6": schema.StringAttribute{
Description: "The interface IPv6/CIDR address.",
CustomType: pvetypes.IPCIDRType{},
CustomType: nettypes.IPCIDRType{},
Optional: true,
},
"gateway6": schema.StringAttribute{
Description: "Default IPv6 gateway address.",
CustomType: pvetypes.IPAddrType{},
CustomType: nettypes.IPAddrType{},
Optional: true,
},
"autostart": schema.BoolAttribute{

View File

@ -22,9 +22,12 @@ import (
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types"
pvetypes "github.com/bpg/terraform-provider-proxmox/internal/types"
"github.com/bpg/terraform-provider-proxmox/proxmox"
"github.com/bpg/terraform-provider-proxmox/proxmox/nodes"
"github.com/bpg/proxmox-api"
"github.com/bpg/proxmox-api/nodes"
pvetypes "github.com/bpg/proxmox-api/types"
nettypes "github.com/bpg/terraform-provider-proxmox/internal/network/types"
)
var (
@ -38,10 +41,10 @@ type linuxVLANResourceModel struct {
ID types.String `tfsdk:"id"`
NodeName types.String `tfsdk:"node_name"`
Name types.String `tfsdk:"name"`
Address pvetypes.IPCIDRValue `tfsdk:"address"`
Gateway pvetypes.IPAddrValue `tfsdk:"gateway"`
Address6 pvetypes.IPCIDRValue `tfsdk:"address6"`
Gateway6 pvetypes.IPAddrValue `tfsdk:"gateway6"`
Address nettypes.IPCIDRValue `tfsdk:"address"`
Gateway nettypes.IPAddrValue `tfsdk:"gateway"`
Address6 nettypes.IPCIDRValue `tfsdk:"address6"`
Gateway6 nettypes.IPAddrValue `tfsdk:"gateway6"`
Autostart types.Bool `tfsdk:"autostart"`
MTU types.Int64 `tfsdk:"mtu"`
Comment types.String `tfsdk:"comment"`
@ -80,10 +83,10 @@ func (m *linuxVLANResourceModel) exportToNetworkInterfaceCreateUpdateBody() *nod
}
func (m *linuxVLANResourceModel) importFromNetworkInterfaceList(iface *nodes.NetworkInterfaceListResponseData) {
m.Address = pvetypes.NewIPCIDRPointerValue(iface.CIDR)
m.Gateway = pvetypes.NewIPAddrPointerValue(iface.Gateway)
m.Address6 = pvetypes.NewIPCIDRPointerValue(iface.CIDR6)
m.Gateway6 = pvetypes.NewIPAddrPointerValue(iface.Gateway6)
m.Address = nettypes.NewIPCIDRPointerValue(iface.CIDR)
m.Gateway = nettypes.NewIPAddrPointerValue(iface.Gateway)
m.Address6 = nettypes.NewIPCIDRPointerValue(iface.CIDR6)
m.Gateway6 = nettypes.NewIPAddrPointerValue(iface.Gateway6)
m.Autostart = types.BoolPointerValue(iface.Autostart.PointerBool())
if iface.MTU != nil {
@ -166,22 +169,22 @@ func (r *linuxVLANResource) Schema(
},
"address": schema.StringAttribute{
Description: "The interface IPv4/CIDR address.",
CustomType: pvetypes.IPCIDRType{},
CustomType: nettypes.IPCIDRType{},
Optional: true,
},
"gateway": schema.StringAttribute{
Description: "Default gateway address.",
CustomType: pvetypes.IPAddrType{},
CustomType: nettypes.IPAddrType{},
Optional: true,
},
"address6": schema.StringAttribute{
Description: "The interface IPv6/CIDR address.",
CustomType: pvetypes.IPCIDRType{},
CustomType: nettypes.IPCIDRType{},
Optional: true,
},
"gateway6": schema.StringAttribute{
Description: "Default IPv6 gateway address.",
CustomType: pvetypes.IPAddrType{},
CustomType: nettypes.IPAddrType{},
Optional: true,
},
"autostart": schema.BoolAttribute{

View File

@ -23,11 +23,12 @@ import (
"github.com/hashicorp/terraform-plugin-framework/types"
"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/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"
)
@ -255,7 +256,7 @@ func (p *proxmoxProvider) Configure(
// Create the Proxmox VE API client
creds, err := api.NewCredentials(username, password, "", apiToken)
creds, err := rest.NewCredentials(username, password, "", apiToken)
if err != nil {
resp.Diagnostics.AddError(
"Unable to create Proxmox VE API credentials",
@ -263,7 +264,7 @@ func (p *proxmoxProvider) Configure(
)
}
conn, err := api.NewConnection(
conn, err := rest.NewConnection(
endpoint,
insecure,
)
@ -278,7 +279,7 @@ func (p *proxmoxProvider) Configure(
return
}
apiClient, err := api.NewClient(creds, conn)
restClient, err := rest.NewClient(creds, conn)
if err != nil {
resp.Diagnostics.AddError(
"Unable to create Proxmox VE API client",
@ -325,7 +326,7 @@ func (p *proxmoxProvider) Configure(
sshClient, err := ssh.NewClient(
sshUsername, sshPassword, sshAgent, sshAgentSocket,
&apiResolverWithOverrides{
ar: apiResolver{c: apiClient},
ar: apiResolver{c: restClient},
overrides: nodeOverrides,
},
)
@ -340,7 +341,7 @@ func (p *proxmoxProvider) Configure(
return
}
client := proxmox.NewClient(apiClient, sshClient)
client := proxmox.NewClient(restClient, sshClient)
resp.ResourceData = client
resp.DataSourceData = client
@ -358,7 +359,7 @@ func (p *proxmoxProvider) DataSources(_ context.Context) []func() datasource.Dat
}
type apiResolver struct {
c api.Client
c rest.Client
}
func (r *apiResolver) Resolve(ctx context.Context, nodeName string) (string, error) {

View File

@ -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
}

View File

@ -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"
}

View File

@ -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)
}
})
}
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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"`
}

View File

@ -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)
}

View File

@ -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
}

View File

@ -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"`
}

View File

@ -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
}

View File

@ -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"`
}

View File

@ -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
}

View File

@ -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"`
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View 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
}

View File

@ -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
}

View File

@ -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"`
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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},
}
}

View File

@ -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
}

View File

@ -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"`
}

View File

@ -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)
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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"`
}

View File

@ -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
}

View File

@ -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"`
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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

View File

@ -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
}

View File

@ -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"`
}

View File

@ -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
}

View File

@ -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"`
}

View File

@ -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
}

View File

@ -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"`
}

View File

@ -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,
}
}

View File

@ -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},
}
}

View File

@ -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)
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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"`
}

View File

@ -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
}

View File

@ -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"`
}

View File

@ -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
}

View File

@ -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"`
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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"`
}

View File

@ -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),
)
}

View File

@ -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,
)
}

View File

@ -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"`
}

View File

@ -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},
}
}

View File

@ -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
}

View File

@ -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

View File

@ -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)
})
}
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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"`
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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"`
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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"`
}

View File

@ -9,31 +9,31 @@ package proxmoxtf
import (
"errors"
"github.com/bpg/terraform-provider-proxmox/proxmox"
"github.com/bpg/terraform-provider-proxmox/proxmox/api"
"github.com/bpg/terraform-provider-proxmox/proxmox/ssh"
"github.com/bpg/proxmox-api"
"github.com/bpg/proxmox-api/rest"
"github.com/bpg/proxmox-api/ssh"
)
// ProviderConfiguration is the configuration for the provider.
type ProviderConfiguration struct {
apiClient api.Client
sshClient ssh.Client
restClient rest.Client
sshClient ssh.Client
}
// NewProviderConfiguration creates a new provider configuration.
func NewProviderConfiguration(
apiClient api.Client,
restClient rest.Client,
sshClient ssh.Client,
) ProviderConfiguration {
return ProviderConfiguration{
apiClient: apiClient,
sshClient: sshClient,
restClient: restClient,
sshClient: sshClient,
}
}
// GetClient returns the Proxmox API client.
func (c *ProviderConfiguration) GetClient() (proxmox.Client, error) {
if c.apiClient == nil {
if c.restClient == nil {
return nil, errors.New(
"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
}

View File

@ -12,7 +12,8 @@ import (
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"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/datasource/firewall"
)

View File

@ -12,7 +12,7 @@ import (
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/bpg/terraform-provider-proxmox/proxmox/firewall"
"github.com/bpg/proxmox-api/firewall"
)
const (

View File

@ -12,7 +12,7 @@ import (
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"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.
@ -25,18 +25,18 @@ func TestAliasSchemaInstantiation(t *testing.T) {
func TestAliasSchema(t *testing.T) {
t.Parallel()
s := AliasSchema()
r := schema.Resource{Schema: AliasSchema()}
structure.AssertRequiredArguments(t, s, []string{
test.AssertRequiredArguments(t, &r, []string{
mkAliasName,
})
structure.AssertComputedAttributes(t, s, []string{
test.AssertComputedAttributes(t, &r, []string{
mkAliasCIDR,
mkAliasComment,
})
structure.AssertValueTypes(t, s, map[string]schema.ValueType{
test.AssertValueTypes(t, &r, map[string]schema.ValueType{
mkAliasName: schema.TypeString,
mkAliasCIDR: schema.TypeString,
mkAliasComment: schema.TypeString,

View File

@ -13,7 +13,7 @@ import (
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/bpg/terraform-provider-proxmox/proxmox/firewall"
"github.com/bpg/proxmox-api/firewall"
)
const (

View File

@ -12,7 +12,7 @@ import (
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"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.
@ -25,13 +25,13 @@ func TestAliasesSchemaInstantiation(t *testing.T) {
func TestAliasesSchema(t *testing.T) {
t.Parallel()
s := AliasesSchema()
r := schema.Resource{Schema: AliasesSchema()}
structure.AssertComputedAttributes(t, s, []string{
test.AssertComputedAttributes(t, &r, []string{
mkAliasesAliasNames,
})
structure.AssertValueTypes(t, s, map[string]schema.ValueType{
test.AssertValueTypes(t, &r, map[string]schema.ValueType{
mkAliasesAliasNames: schema.TypeList,
})
}

View File

@ -12,7 +12,7 @@ import (
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/bpg/terraform-provider-proxmox/proxmox/firewall"
"github.com/bpg/proxmox-api/firewall"
)
const (

View File

@ -12,7 +12,7 @@ import (
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"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.
@ -25,32 +25,32 @@ func TestIPSetSchemaInstantiation(t *testing.T) {
func TestIPSetSchema(t *testing.T) {
t.Parallel()
s := IPSetSchema()
r := schema.Resource{Schema: IPSetSchema()}
structure.AssertRequiredArguments(t, s, []string{
test.AssertRequiredArguments(t, &r, []string{
mkIPSetName,
})
structure.AssertComputedAttributes(t, s, []string{
test.AssertComputedAttributes(t, &r, []string{
mkIPSetCIDR,
mkIPSetCIDRComment,
})
structure.AssertValueTypes(t, s, map[string]schema.ValueType{
test.AssertValueTypes(t, &r, map[string]schema.ValueType{
mkIPSetName: schema.TypeString,
mkIPSetCIDR: schema.TypeList,
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,
mkIPSetCIDRNoMatch,
mkIPSetCIDRComment,
})
structure.AssertValueTypes(t, cirdSchema, map[string]schema.ValueType{
test.AssertValueTypes(t, cidr, map[string]schema.ValueType{
mkIPSetCIDRName: schema.TypeString,
mkIPSetCIDRNoMatch: schema.TypeBool,
mkIPSetCIDRComment: schema.TypeString,

View File

@ -13,7 +13,7 @@ import (
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/bpg/terraform-provider-proxmox/proxmox/firewall"
"github.com/bpg/proxmox-api/firewall"
)
const (

View File

@ -12,7 +12,7 @@ import (
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"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.
@ -25,13 +25,13 @@ func TestIPSetsSchemaInstantiation(t *testing.T) {
func TestIPSetsSchema(t *testing.T) {
t.Parallel()
s := IPSetsSchema()
r := schema.Resource{Schema: IPSetsSchema()}
structure.AssertComputedAttributes(t, s, []string{
test.AssertComputedAttributes(t, &r, []string{
mkIPSetsIPSetNames,
})
structure.AssertValueTypes(t, s, map[string]schema.ValueType{
test.AssertValueTypes(t, &r, map[string]schema.ValueType{
mkIPSetsIPSetNames: schema.TypeList,
})
}

View File

@ -12,7 +12,7 @@ import (
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"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.
@ -25,14 +25,14 @@ func TestRuleSchemaInstantiation(t *testing.T) {
func TestRuleSchema(t *testing.T) {
t.Parallel()
ruleSchema := RuleSchema()
r := schema.Resource{Schema: RuleSchema()}
structure.AssertRequiredArguments(t, ruleSchema, []string{
test.AssertRequiredArguments(t, &r, []string{
mkRuleAction,
mkRuleType,
})
structure.AssertComputedAttributes(t, ruleSchema, []string{
test.AssertComputedAttributes(t, &r, []string{
mkRuleComment,
mkRuleDest,
mkRuleDPort,
@ -45,7 +45,7 @@ func TestRuleSchema(t *testing.T) {
mkRuleSPort,
})
structure.AssertValueTypes(t, ruleSchema, map[string]schema.ValueType{
test.AssertValueTypes(t, &r, map[string]schema.ValueType{
mkRulePos: schema.TypeInt,
mkRuleAction: schema.TypeString,
mkRuleType: schema.TypeString,

Some files were not shown because too many files have changed in this diff Show More