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:
parent
6a3bc03470
commit
98bc511140
@ -1,6 +0,0 @@
|
||||
since_tag=v0.4.4
|
||||
exclude-labels=duplicate,question,invalid,wontfix,dependencies
|
||||
pr-wo-labels=false
|
||||
bugs-label=BUG FIXES:
|
||||
breaking-label=BREAKING CHANGES:
|
||||
enhancement-label=ENHANCEMENTS:
|
@ -1 +0,0 @@
|
||||
1.18.0
|
14
go.mod
14
go.mod
@ -2,8 +2,11 @@ module github.com/bpg/terraform-provider-proxmox
|
||||
|
||||
go 1.20
|
||||
|
||||
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
3
go.sum
@ -7,6 +7,8 @@ github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki
|
||||
github.com/apparentlymart/go-textseg/v12 v12.0.0/go.mod h1:S/4uRK2UtaQttw1GenVJEynmyUenKwP++x/+DdGV/Ec=
|
||||
github.com/apparentlymart/go-textseg/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=
|
||||
|
8
go.work.sum
Normal file
8
go.work.sum
Normal file
@ -0,0 +1,8 @@
|
||||
github.com/apparentlymart/go-textseg v1.0.0 h1:rRmlIsPEEhUTIKQb7T++Nz/A5Q6C9IuX2wFoYVvnCs0=
|
||||
github.com/apparentlymart/go-textseg v1.0.0/go.mod h1:z96Txxhf3xSFMPmb5X/1W05FF/Nj9VFpLOpjS5yuumk=
|
||||
github.com/sebdah/goldie v1.0.0/go.mod h1:jXP4hmWywNEwZzhMuv2ccnqTSFpuq8iyQhtQdkkZBH4=
|
||||
github.com/vmihailenco/tagparser v0.1.2 h1:gnjoVuB/kljJ5wICEEOpx98oXMWPLj22G67Vbd1qPqc=
|
||||
github.com/zclconf/go-cty v1.2.0/go.mod h1:hOPWgoHbaTUnI5k4D2ld+GRpFJSCe6bCM7m1q/N4PQ8=
|
||||
github.com/zclconf/go-cty-debug v0.0.0-20191215020915-b22d67c1ba0b/go.mod h1:ZRKQfBXbGkpdV6QMzT3rU1kSTAnfu1dO8dPKjYprgj8=
|
||||
golang.org/x/net v0.0.0-20180811021610-c39426892332/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o=
|
@ -24,9 +24,12 @@ import (
|
||||
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
|
||||
"github.com/hashicorp/terraform-plugin-framework/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{
|
||||
|
@ -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{
|
||||
|
@ -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) {
|
||||
|
@ -1,185 +0,0 @@
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package types
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// CustomBool allows a JSON boolean value to also be an integer.
|
||||
type CustomBool bool
|
||||
|
||||
// CustomCommaSeparatedList allows a JSON string to also be a string array.
|
||||
type CustomCommaSeparatedList []string
|
||||
|
||||
// CustomInt allows a JSON integer value to also be a string.
|
||||
type CustomInt int
|
||||
|
||||
// CustomLineBreakSeparatedList allows a multiline JSON string to also be a string array.
|
||||
type CustomLineBreakSeparatedList []string
|
||||
|
||||
// CustomPrivileges allows a JSON object of privileges to also be a string array.
|
||||
type CustomPrivileges []string
|
||||
|
||||
// CustomTimestamp allows a JSON boolean value to also be a unix timestamp.
|
||||
type CustomTimestamp time.Time
|
||||
|
||||
// MarshalJSON converts a boolean to a JSON value.
|
||||
func (r CustomBool) MarshalJSON() ([]byte, error) {
|
||||
buffer := new(bytes.Buffer)
|
||||
|
||||
if r {
|
||||
buffer.WriteString("1")
|
||||
} else {
|
||||
buffer.WriteString("0")
|
||||
}
|
||||
|
||||
return buffer.Bytes(), nil
|
||||
}
|
||||
|
||||
// UnmarshalJSON converts a JSON value to a boolean.
|
||||
func (r *CustomBool) UnmarshalJSON(b []byte) error {
|
||||
s := string(b)
|
||||
*r = s == "1" || s == "true"
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Pointer returns a pointers.
|
||||
func (r CustomBool) Pointer() *CustomBool {
|
||||
return &r
|
||||
}
|
||||
|
||||
// PointerBool returns a pointer to a boolean.
|
||||
func (r *CustomBool) PointerBool() *bool {
|
||||
return (*bool)(r)
|
||||
}
|
||||
|
||||
// MarshalJSON converts a boolean to a JSON value.
|
||||
func (r *CustomCommaSeparatedList) MarshalJSON() ([]byte, error) {
|
||||
s := strings.Join(*r, ",")
|
||||
|
||||
return json.Marshal(s)
|
||||
}
|
||||
|
||||
// UnmarshalJSON converts a JSON value to a boolean.
|
||||
func (r *CustomCommaSeparatedList) UnmarshalJSON(b []byte) error {
|
||||
var s string
|
||||
|
||||
err := json.Unmarshal(b, &s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*r = strings.Split(s, ",")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnmarshalJSON converts a JSON value to an integer.
|
||||
func (r *CustomInt) UnmarshalJSON(b []byte) error {
|
||||
s := string(b)
|
||||
|
||||
if strings.HasPrefix(s, "\"") && strings.HasSuffix(s, "\"") {
|
||||
s = s[1 : len(s)-1]
|
||||
}
|
||||
|
||||
i, err := strconv.ParseInt(s, 10, 32)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*r = CustomInt(i)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalJSON converts a boolean to a JSON value.
|
||||
func (r *CustomLineBreakSeparatedList) MarshalJSON() ([]byte, error) {
|
||||
s := strings.Join(*r, "\n")
|
||||
|
||||
return json.Marshal(s)
|
||||
}
|
||||
|
||||
// UnmarshalJSON converts a JSON value to a boolean.
|
||||
func (r *CustomLineBreakSeparatedList) UnmarshalJSON(b []byte) error {
|
||||
var s string
|
||||
|
||||
err := json.Unmarshal(b, &s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*r = strings.Split(s, "\n")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalJSON converts a boolean to a JSON value.
|
||||
func (r *CustomPrivileges) MarshalJSON() ([]byte, error) {
|
||||
privileges := map[string]CustomBool{}
|
||||
|
||||
for _, v := range *r {
|
||||
privileges[v] = true
|
||||
}
|
||||
|
||||
return json.Marshal(privileges)
|
||||
}
|
||||
|
||||
// UnmarshalJSON converts a JSON value to a boolean.
|
||||
func (r *CustomPrivileges) UnmarshalJSON(b []byte) error {
|
||||
var privileges interface{}
|
||||
|
||||
err := json.Unmarshal(b, &privileges)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch s := privileges.(type) {
|
||||
case string:
|
||||
if s != "" {
|
||||
*r = strings.Split(s, ",")
|
||||
} else {
|
||||
*r = CustomPrivileges{}
|
||||
}
|
||||
default:
|
||||
*r = CustomPrivileges{}
|
||||
|
||||
for k, v := range privileges.(map[string]interface{}) {
|
||||
if v.(float64) >= 1 {
|
||||
*r = append(*r, k)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalJSON converts a timestamp to a JSON value.
|
||||
func (r CustomTimestamp) MarshalJSON() ([]byte, error) {
|
||||
timestamp := time.Time(r)
|
||||
buffer := bytes.NewBufferString(strconv.FormatInt(timestamp.Unix(), 10))
|
||||
|
||||
return buffer.Bytes(), nil
|
||||
}
|
||||
|
||||
// UnmarshalJSON converts a JSON value to a timestamp.
|
||||
func (r *CustomTimestamp) UnmarshalJSON(b []byte) error {
|
||||
s := string(b)
|
||||
i, err := strconv.ParseInt(s, 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*r = CustomTimestamp(time.Unix(i, 0).UTC())
|
||||
|
||||
return nil
|
||||
}
|
@ -1,122 +0,0 @@
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package types
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Regex used to identify size strings. Case-insensitive. Covers megabytes, gigabytes and terabytes.
|
||||
var sizeRegex = regexp.MustCompile(`(?i)^(\d+(\.\d+)?)(k|kb|kib|m|mb|mib|g|gb|gib|t|tb|tib)?$`)
|
||||
|
||||
// DiskSize allows a JSON integer value to also be a string. This is mapped to `<DiskSize>` data type in Proxmox API.
|
||||
// Represents a disk size in bytes.
|
||||
type DiskSize int64
|
||||
|
||||
// String returns the string representation of the disk size.
|
||||
func (r DiskSize) String() string {
|
||||
return FormatDiskSize(r)
|
||||
}
|
||||
|
||||
// InMegabytes returns the disk size in megabytes.
|
||||
func (r DiskSize) InMegabytes() int {
|
||||
return int(int64(r) / 1024 / 1024)
|
||||
}
|
||||
|
||||
// InGigabytes returns the disk size in gigabytes.
|
||||
func (r DiskSize) InGigabytes() int {
|
||||
return int(int64(r) / 1024 / 1024 / 1024)
|
||||
}
|
||||
|
||||
// DiskSizeFromGigabytes creates a DiskSize from gigabytes.
|
||||
func DiskSizeFromGigabytes(size int) DiskSize {
|
||||
return DiskSize(size * 1024 * 1024 * 1024)
|
||||
}
|
||||
|
||||
// MarshalJSON marshals a disk size into a Proxmox API `<DiskSize>` string.
|
||||
func (r DiskSize) MarshalJSON() ([]byte, error) {
|
||||
bytes, err := json.Marshal(FormatDiskSize(r))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot marshal disk size: %w", err)
|
||||
}
|
||||
|
||||
return bytes, nil
|
||||
}
|
||||
|
||||
// UnmarshalJSON unmarshals a disk size from a Proxmox API `<DiskSize>` string.
|
||||
func (r *DiskSize) UnmarshalJSON(b []byte) error {
|
||||
s := string(b)
|
||||
|
||||
size, err := ParseDiskSize(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*r = size
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ParseDiskSize parses a disk size string into a number of bytes.
|
||||
func ParseDiskSize(size string) (DiskSize, error) {
|
||||
matches := sizeRegex.FindStringSubmatch(size)
|
||||
if len(matches) > 0 {
|
||||
fsize, err := strconv.ParseFloat(matches[1], 64)
|
||||
if err != nil {
|
||||
return -1, fmt.Errorf("cannot parse disk size \"%s\": %w", size, err)
|
||||
}
|
||||
|
||||
switch strings.ToLower(matches[3]) {
|
||||
case "k", "kb", "kib":
|
||||
fsize *= 1024
|
||||
case "m", "mb", "mib":
|
||||
fsize = fsize * 1024 * 1024
|
||||
case "g", "gb", "gib":
|
||||
fsize = fsize * 1024 * 1024 * 1024
|
||||
case "t", "tb", "tib":
|
||||
fsize = fsize * 1024 * 1024 * 1024 * 1024
|
||||
}
|
||||
|
||||
return DiskSize(math.Ceil(fsize)), nil
|
||||
}
|
||||
|
||||
return -1, fmt.Errorf("cannot parse disk size \"%s\"", size)
|
||||
}
|
||||
|
||||
// FormatDiskSize turns a number of bytes into a disk size string.
|
||||
func FormatDiskSize(size DiskSize) string {
|
||||
if size < 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
if size < 1024 {
|
||||
return fmt.Sprintf("%d", size)
|
||||
}
|
||||
|
||||
round := func(f float64) string {
|
||||
return strconv.FormatFloat(math.Ceil(f*100)/100, 'f', -1, 64)
|
||||
}
|
||||
|
||||
if size < 1024*1024 {
|
||||
return round(float64(size)/1024) + "K"
|
||||
}
|
||||
|
||||
if size < 1024*1024*1024 {
|
||||
return round(float64(size)/1024/1024) + "M"
|
||||
}
|
||||
|
||||
if size < 1024*1024*1024*1024 {
|
||||
return round(float64(size)/1024/1024/1024) + "G"
|
||||
}
|
||||
|
||||
return round(float64(size)/1024/1024/1024/1024) + "T"
|
||||
}
|
@ -1,110 +0,0 @@
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package types
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestParseDiskSize(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
size string
|
||||
want int64
|
||||
wantErr bool
|
||||
}{
|
||||
{"parse TB", "2TB", 2199023255552, false},
|
||||
{"parse T", "2T", 2199023255552, false},
|
||||
{"parse fraction T", "2.2T", 2418925581108, false},
|
||||
{"parse GB", "2GB", 2147483648, false},
|
||||
{"parse G", "2G", 2147483648, false},
|
||||
{"parse M", "2048M", 2147483648, false},
|
||||
{"parse MB", "2048MB", 2147483648, false},
|
||||
{"parse MiB", "2048MiB", 2147483648, false},
|
||||
{"parse K", "1K", 1024, false},
|
||||
{"parse KB", "2KB", 2048, false},
|
||||
{"parse KiB", "4KiB", 4096, false},
|
||||
{"parse no units as bytes", "12345", 12345, false},
|
||||
{"error on bad format string", "20l8G", -1, true},
|
||||
{"error on unknown unit string", "2048W", -1, true},
|
||||
{"error on arbitrary string", "something", -1, true},
|
||||
}
|
||||
for _, test := range tests {
|
||||
tt := test
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
got, err := ParseDiskSize(tt.size)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("parseDiskSize() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if int64(got) != tt.want {
|
||||
t.Errorf("parseDiskSize() got = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFormatDiskSize(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
size int64
|
||||
want string
|
||||
}{
|
||||
{"handle 0 size", 0, "0"},
|
||||
{"handle bytes", 1001, "1001"},
|
||||
{"handle kilobytes", 1234, "1.21K"},
|
||||
{"handle megabytes", 2097152, "2M"},
|
||||
{"handle gigabytes", 2147483648, "2G"},
|
||||
{"handle terabytes", 2199023255552, "2T"},
|
||||
}
|
||||
for _, test := range tests {
|
||||
tt := test
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
if got := FormatDiskSize(DiskSize(tt.size)); got != tt.want {
|
||||
t.Errorf("formatDiskSize() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestToFromGigabytes(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
size int
|
||||
want string
|
||||
}{
|
||||
{"handle 0 size", 0, "0"},
|
||||
{"handle 99 GB", 99, "99G"},
|
||||
{"handle 100 GB", 100, "100G"},
|
||||
{"handle 101 GB", 101, "101G"},
|
||||
{"handle 1023 GB", 1023, "1023G"},
|
||||
{"handle 1024 GB", 1024, "1T"},
|
||||
{"handle 1025 GB", 1025, "1.01T"},
|
||||
}
|
||||
for _, test := range tests {
|
||||
tt := test
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
ds := DiskSizeFromGigabytes(tt.size)
|
||||
gb := ds.InGigabytes()
|
||||
assert.Equal(t, tt.size, gb)
|
||||
if got := ds.String(); got != tt.want {
|
||||
t.Errorf("DiskSize.String() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@ -1,18 +0,0 @@
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package types
|
||||
|
||||
// StrPtr returns a pointer to a string.
|
||||
func StrPtr(s string) *string {
|
||||
return &s
|
||||
}
|
||||
|
||||
// BoolPtr returns a pointer to a bool.
|
||||
func BoolPtr(s bool) *CustomBool {
|
||||
customBool := CustomBool(s)
|
||||
return &customBool
|
||||
}
|
@ -1,50 +0,0 @@
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package access
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"sort"
|
||||
|
||||
"github.com/bpg/terraform-provider-proxmox/proxmox/api"
|
||||
)
|
||||
|
||||
func (c *Client) aclPath() string {
|
||||
return c.ExpandPath("acl")
|
||||
}
|
||||
|
||||
// GetACL retrieves the access control list.
|
||||
func (c *Client) GetACL(ctx context.Context) ([]*ACLGetResponseData, error) {
|
||||
resBody := &ACLGetResponseBody{}
|
||||
|
||||
err := c.DoRequest(ctx, http.MethodGet, c.aclPath(), nil, resBody)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get access control list: %w", err)
|
||||
}
|
||||
|
||||
if resBody.Data == nil {
|
||||
return nil, api.ErrNoDataObjectInResponse
|
||||
}
|
||||
|
||||
sort.Slice(resBody.Data, func(i, j int) bool {
|
||||
return resBody.Data[i].Path < resBody.Data[j].Path
|
||||
})
|
||||
|
||||
return resBody.Data, nil
|
||||
}
|
||||
|
||||
// UpdateACL updates the access control list.
|
||||
func (c *Client) UpdateACL(ctx context.Context, d *ACLUpdateRequestBody) error {
|
||||
err := c.DoRequest(ctx, http.MethodPut, c.aclPath(), d, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to update access control list: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
@ -1,35 +0,0 @@
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package access
|
||||
|
||||
import (
|
||||
"github.com/bpg/terraform-provider-proxmox/internal/types"
|
||||
)
|
||||
|
||||
// ACLGetResponseBody contains the body from an access control list response.
|
||||
type ACLGetResponseBody struct {
|
||||
Data []*ACLGetResponseData `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
// ACLGetResponseData contains the data from an access control list response.
|
||||
type ACLGetResponseData struct {
|
||||
Path string `json:"path"`
|
||||
Propagate *types.CustomBool `json:"propagate,omitempty"`
|
||||
RoleID string `json:"roleid"`
|
||||
Type string `json:"type"`
|
||||
UserOrGroupID string `json:"ugid"`
|
||||
}
|
||||
|
||||
// ACLUpdateRequestBody contains the data for an access control list update request.
|
||||
type ACLUpdateRequestBody struct {
|
||||
Delete *types.CustomBool `json:"delete,omitempty" url:"delete,omitempty,int"`
|
||||
Groups []string `json:"groups,omitempty" url:"groups,omitempty,comma"`
|
||||
Path string `json:"path" url:"path"`
|
||||
Propagate *types.CustomBool `json:"propagate,omitempty" url:"propagate,omitempty,int"`
|
||||
Roles []string `json:"roles" url:"roles,comma"`
|
||||
Users []string `json:"users,omitempty" url:"users,omitempty,comma"`
|
||||
}
|
@ -1,23 +0,0 @@
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package access
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/bpg/terraform-provider-proxmox/proxmox/api"
|
||||
)
|
||||
|
||||
// Client is an interface for performing requests against the Proxmox 'access' API.
|
||||
type Client struct {
|
||||
api.Client
|
||||
}
|
||||
|
||||
// ExpandPath expands a path relative to the client's base path.
|
||||
func (c *Client) ExpandPath(path string) string {
|
||||
return fmt.Sprintf("access/%s", path)
|
||||
}
|
@ -1,93 +0,0 @@
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package access
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"sort"
|
||||
|
||||
"github.com/bpg/terraform-provider-proxmox/proxmox/api"
|
||||
)
|
||||
|
||||
func (c *Client) groupsPath() string {
|
||||
return c.ExpandPath("groups")
|
||||
}
|
||||
|
||||
func (c *Client) groupPath(id string) string {
|
||||
return fmt.Sprintf("%s/%s", c.groupsPath(), url.PathEscape(id))
|
||||
}
|
||||
|
||||
// CreateGroup creates an access group.
|
||||
func (c *Client) CreateGroup(ctx context.Context, d *GroupCreateRequestBody) error {
|
||||
err := c.DoRequest(ctx, http.MethodPost, c.groupsPath(), d, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create access group: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteGroup deletes an access group.
|
||||
func (c *Client) DeleteGroup(ctx context.Context, id string) error {
|
||||
err := c.DoRequest(ctx, http.MethodDelete, c.groupPath(id), nil, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to delete access group: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetGroup retrieves an access group.
|
||||
func (c *Client) GetGroup(ctx context.Context, id string) (*GroupGetResponseData, error) {
|
||||
resBody := &GroupGetResponseBody{}
|
||||
|
||||
err := c.DoRequest(ctx, http.MethodGet, c.groupPath(id), nil, resBody)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get access group: %w", err)
|
||||
}
|
||||
|
||||
if resBody.Data == nil {
|
||||
return nil, api.ErrNoDataObjectInResponse
|
||||
}
|
||||
|
||||
sort.Strings(resBody.Data.Members)
|
||||
|
||||
return resBody.Data, nil
|
||||
}
|
||||
|
||||
// ListGroups retrieves a list of access groups.
|
||||
func (c *Client) ListGroups(ctx context.Context) ([]*GroupListResponseData, error) {
|
||||
resBody := &GroupListResponseBody{}
|
||||
|
||||
err := c.DoRequest(ctx, http.MethodGet, c.groupsPath(), nil, resBody)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to list access groups: %w", err)
|
||||
}
|
||||
|
||||
if resBody.Data == nil {
|
||||
return nil, api.ErrNoDataObjectInResponse
|
||||
}
|
||||
|
||||
sort.Slice(resBody.Data, func(i, j int) bool {
|
||||
return resBody.Data[i].ID < resBody.Data[j].ID
|
||||
})
|
||||
|
||||
return resBody.Data, nil
|
||||
}
|
||||
|
||||
// UpdateGroup updates an access group.
|
||||
func (c *Client) UpdateGroup(ctx context.Context, id string, d *GroupUpdateRequestBody) error {
|
||||
err := c.DoRequest(ctx, http.MethodPut, c.groupPath(id), d, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to update access group: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
@ -1,40 +0,0 @@
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package access
|
||||
|
||||
// GroupCreateRequestBody contains the data for an access group create request.
|
||||
type GroupCreateRequestBody struct {
|
||||
Comment *string `json:"comment,omitempty" url:"comment,omitempty"`
|
||||
ID string `json:"groupid" url:"groupid"`
|
||||
}
|
||||
|
||||
// GroupGetResponseBody contains the body from an access group get response.
|
||||
type GroupGetResponseBody struct {
|
||||
Data *GroupGetResponseData `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
// GroupGetResponseData contains the data from an access group get response.
|
||||
type GroupGetResponseData struct {
|
||||
Comment *string `json:"comment,omitempty"`
|
||||
Members []string `json:"members"`
|
||||
}
|
||||
|
||||
// GroupListResponseBody contains the body from an access group list response.
|
||||
type GroupListResponseBody struct {
|
||||
Data []*GroupListResponseData `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
// GroupListResponseData contains the data from an access group list response.
|
||||
type GroupListResponseData struct {
|
||||
Comment *string `json:"comment,omitempty"`
|
||||
ID string `json:"groupid"`
|
||||
}
|
||||
|
||||
// GroupUpdateRequestBody contains the data for an access group update request.
|
||||
type GroupUpdateRequestBody struct {
|
||||
Comment *string `json:"comment,omitempty" url:"comment,omitempty"`
|
||||
}
|
@ -1,100 +0,0 @@
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package access
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"sort"
|
||||
|
||||
"github.com/bpg/terraform-provider-proxmox/internal/types"
|
||||
"github.com/bpg/terraform-provider-proxmox/proxmox/api"
|
||||
)
|
||||
|
||||
func (c *Client) rolesPath() string {
|
||||
return c.ExpandPath("roles")
|
||||
}
|
||||
|
||||
func (c *Client) rolePath(id string) string {
|
||||
return fmt.Sprintf("%s/%s", c.rolesPath(), url.PathEscape(id))
|
||||
}
|
||||
|
||||
// CreateRole creates an access role.
|
||||
func (c *Client) CreateRole(ctx context.Context, d *RoleCreateRequestBody) error {
|
||||
err := c.DoRequest(ctx, http.MethodPost, c.rolesPath(), d, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error creating role: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteRole deletes an access role.
|
||||
func (c *Client) DeleteRole(ctx context.Context, id string) error {
|
||||
err := c.DoRequest(ctx, http.MethodDelete, c.rolePath(id), nil, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error deleting role: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetRole retrieves an access role.
|
||||
func (c *Client) GetRole(ctx context.Context, id string) (*types.CustomPrivileges, error) {
|
||||
resBody := &RoleGetResponseBody{}
|
||||
|
||||
err := c.DoRequest(ctx, http.MethodGet, c.rolePath(id), nil, resBody)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error getting role: %w", err)
|
||||
}
|
||||
|
||||
if resBody.Data == nil {
|
||||
return nil, api.ErrNoDataObjectInResponse
|
||||
}
|
||||
|
||||
sort.Strings(*resBody.Data)
|
||||
|
||||
return resBody.Data, nil
|
||||
}
|
||||
|
||||
// ListRoles retrieves a list of access roles.
|
||||
func (c *Client) ListRoles(ctx context.Context) ([]*RoleListResponseData, error) {
|
||||
resBody := &RoleListResponseBody{}
|
||||
|
||||
err := c.DoRequest(ctx, http.MethodGet, c.rolesPath(), nil, resBody)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error listing roles: %w", err)
|
||||
}
|
||||
|
||||
if resBody.Data == nil {
|
||||
return nil, api.ErrNoDataObjectInResponse
|
||||
}
|
||||
|
||||
sort.Slice(resBody.Data, func(i, j int) bool {
|
||||
return resBody.Data[i].ID < resBody.Data[j].ID
|
||||
})
|
||||
|
||||
for i := range resBody.Data {
|
||||
if resBody.Data[i].Privileges != nil {
|
||||
sort.Strings(*resBody.Data[i].Privileges)
|
||||
}
|
||||
}
|
||||
|
||||
return resBody.Data, nil
|
||||
}
|
||||
|
||||
// UpdateRole updates an access role.
|
||||
func (c *Client) UpdateRole(ctx context.Context, id string, d *RoleUpdateRequestBody) error {
|
||||
err := c.DoRequest(ctx, http.MethodPut, c.rolePath(id), d, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error updating role: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
@ -1,39 +0,0 @@
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package access
|
||||
|
||||
import (
|
||||
"github.com/bpg/terraform-provider-proxmox/internal/types"
|
||||
)
|
||||
|
||||
// RoleCreateRequestBody contains the data for an access group create request.
|
||||
type RoleCreateRequestBody struct {
|
||||
ID string `json:"roleid" url:"roleid"`
|
||||
Privileges types.CustomPrivileges `json:"privs" url:"privs,comma"`
|
||||
}
|
||||
|
||||
// RoleGetResponseBody contains the body from an access group get response.
|
||||
type RoleGetResponseBody struct {
|
||||
Data *types.CustomPrivileges `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
// RoleListResponseBody contains the body from an access group list response.
|
||||
type RoleListResponseBody struct {
|
||||
Data []*RoleListResponseData `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
// RoleListResponseData contains the data from an access group list response.
|
||||
type RoleListResponseData struct {
|
||||
ID string `json:"roleid"`
|
||||
Privileges *types.CustomPrivileges `json:"privs,omitempty"`
|
||||
Special *types.CustomBool `json:"special,omitempty"`
|
||||
}
|
||||
|
||||
// RoleUpdateRequestBody contains the data for an access group update request.
|
||||
type RoleUpdateRequestBody struct {
|
||||
Privileges types.CustomPrivileges `json:"privs" url:"privs,comma"`
|
||||
}
|
@ -1,128 +0,0 @@
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package access
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"github.com/bpg/terraform-provider-proxmox/internal/types"
|
||||
"github.com/bpg/terraform-provider-proxmox/proxmox/api"
|
||||
)
|
||||
|
||||
func (c *Client) usersPath() string {
|
||||
return c.ExpandPath("users")
|
||||
}
|
||||
|
||||
func (c *Client) userPath(id string) string {
|
||||
return fmt.Sprintf("%s/%s", c.usersPath(), url.PathEscape(id))
|
||||
}
|
||||
|
||||
// ChangeUserPassword changes a user's password.
|
||||
func (c *Client) ChangeUserPassword(ctx context.Context, id, password string) error {
|
||||
d := UserChangePasswordRequestBody{
|
||||
ID: id,
|
||||
Password: password,
|
||||
}
|
||||
|
||||
err := c.DoRequest(ctx, http.MethodPut, c.ExpandPath("password"), d, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error changing user password: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateUser creates a user.
|
||||
func (c *Client) CreateUser(ctx context.Context, d *UserCreateRequestBody) error {
|
||||
err := c.DoRequest(ctx, http.MethodPost, c.usersPath(), d, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error creating user: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteUser deletes an user.
|
||||
func (c *Client) DeleteUser(ctx context.Context, id string) error {
|
||||
err := c.DoRequest(ctx, http.MethodDelete, c.userPath(id), nil, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error deleting user: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetUser retrieves a user.
|
||||
func (c *Client) GetUser(ctx context.Context, id string) (*UserGetResponseData, error) {
|
||||
resBody := &UserGetResponseBody{}
|
||||
|
||||
err := c.DoRequest(ctx, http.MethodGet, c.userPath(id), nil, resBody)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error retrieving user: %w", err)
|
||||
}
|
||||
|
||||
if resBody.Data == nil {
|
||||
return nil, api.ErrNoDataObjectInResponse
|
||||
}
|
||||
|
||||
if resBody.Data.ExpirationDate != nil {
|
||||
expirationDate := types.CustomTimestamp(time.Time(*resBody.Data.ExpirationDate).UTC())
|
||||
resBody.Data.ExpirationDate = &expirationDate
|
||||
}
|
||||
|
||||
if resBody.Data.Groups != nil {
|
||||
sort.Strings(*resBody.Data.Groups)
|
||||
}
|
||||
|
||||
return resBody.Data, nil
|
||||
}
|
||||
|
||||
// ListUsers retrieves a list of users.
|
||||
func (c *Client) ListUsers(ctx context.Context) ([]*UserListResponseData, error) {
|
||||
resBody := &UserListResponseBody{}
|
||||
|
||||
err := c.DoRequest(ctx, http.MethodGet, c.usersPath(), nil, resBody)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error listing users: %w", err)
|
||||
}
|
||||
|
||||
if resBody.Data == nil {
|
||||
return nil, api.ErrNoDataObjectInResponse
|
||||
}
|
||||
|
||||
sort.Slice(resBody.Data, func(i, j int) bool {
|
||||
return resBody.Data[i].ID < resBody.Data[j].ID
|
||||
})
|
||||
|
||||
for i := range resBody.Data {
|
||||
if resBody.Data[i].ExpirationDate != nil {
|
||||
expirationDate := types.CustomTimestamp(time.Time(*resBody.Data[i].ExpirationDate).UTC())
|
||||
resBody.Data[i].ExpirationDate = &expirationDate
|
||||
}
|
||||
|
||||
if resBody.Data[i].Groups != nil {
|
||||
sort.Strings(*resBody.Data[i].Groups)
|
||||
}
|
||||
}
|
||||
|
||||
return resBody.Data, nil
|
||||
}
|
||||
|
||||
// UpdateUser updates a user.
|
||||
func (c *Client) UpdateUser(ctx context.Context, id string, d *UserUpdateRequestBody) error {
|
||||
err := c.DoRequest(ctx, http.MethodPut, c.userPath(id), d, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error updating user: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
@ -1,79 +0,0 @@
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package access
|
||||
|
||||
import (
|
||||
"github.com/bpg/terraform-provider-proxmox/internal/types"
|
||||
)
|
||||
|
||||
// UserChangePasswordRequestBody contains the data for a user password change request.
|
||||
type UserChangePasswordRequestBody struct {
|
||||
ID string `json:"userid" url:"userid"`
|
||||
Password string `json:"password" url:"password"`
|
||||
}
|
||||
|
||||
// UserCreateRequestBody contains the data for a user create request.
|
||||
type UserCreateRequestBody struct {
|
||||
Comment *string `json:"comment,omitempty" url:"comment,omitempty"`
|
||||
Email *string `json:"email,omitempty" url:"email,omitempty"`
|
||||
Enabled *types.CustomBool `json:"enable,omitempty" url:"enable,omitempty,int"`
|
||||
ExpirationDate *types.CustomTimestamp `json:"expire,omitempty" url:"expire,omitempty,unix"`
|
||||
FirstName *string `json:"firstname,omitempty" url:"firstname,omitempty"`
|
||||
Groups []string `json:"groups,omitempty" url:"groups,omitempty,comma"`
|
||||
ID string `json:"userid" url:"userid"`
|
||||
Keys *string `json:"keys,omitempty" url:"keys,omitempty"`
|
||||
LastName *string `json:"lastname,omitempty" url:"lastname,omitempty"`
|
||||
Password string `json:"password" url:"password"`
|
||||
}
|
||||
|
||||
// UserGetResponseBody contains the body from a user get response.
|
||||
type UserGetResponseBody struct {
|
||||
Data *UserGetResponseData `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
// UserGetResponseData contains the data from an user get response.
|
||||
type UserGetResponseData struct {
|
||||
Comment *string `json:"comment,omitempty"`
|
||||
Email *string `json:"email,omitempty"`
|
||||
Enabled *types.CustomBool `json:"enable,omitempty"`
|
||||
ExpirationDate *types.CustomTimestamp `json:"expire,omitempty"`
|
||||
FirstName *string `json:"firstname,omitempty"`
|
||||
Groups *[]string `json:"groups,omitempty"`
|
||||
Keys *string `json:"keys,omitempty"`
|
||||
LastName *string `json:"lastname,omitempty"`
|
||||
}
|
||||
|
||||
// UserListResponseBody contains the body from a user list response.
|
||||
type UserListResponseBody struct {
|
||||
Data []*UserListResponseData `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
// UserListResponseData contains the data from an user list response.
|
||||
type UserListResponseData struct {
|
||||
Comment *string `json:"comment,omitempty"`
|
||||
Email *string `json:"email,omitempty"`
|
||||
Enabled *types.CustomBool `json:"enable,omitempty"`
|
||||
ExpirationDate *types.CustomTimestamp `json:"expire,omitempty"`
|
||||
FirstName *string `json:"firstname,omitempty"`
|
||||
Groups *[]string `json:"groups,omitempty"`
|
||||
ID string `json:"userid"`
|
||||
Keys *string `json:"keys,omitempty"`
|
||||
LastName *string `json:"lastname,omitempty"`
|
||||
}
|
||||
|
||||
// UserUpdateRequestBody contains the data for an user update request.
|
||||
type UserUpdateRequestBody struct {
|
||||
Append *types.CustomBool `json:"append,omitempty" url:"append,omitempty"`
|
||||
Comment *string `json:"comment,omitempty" url:"comment,omitempty"`
|
||||
Email *string `json:"email,omitempty" url:"email,omitempty"`
|
||||
Enabled *types.CustomBool `json:"enable,omitempty" url:"enable,omitempty,int"`
|
||||
ExpirationDate *types.CustomTimestamp `json:"expire,omitempty" url:"expire,omitempty,unix"`
|
||||
FirstName *string `json:"firstname,omitempty" url:"firstname,omitempty"`
|
||||
Groups []string `json:"groups,omitempty" url:"groups,omitempty,comma"`
|
||||
Keys *string `json:"keys,omitempty" url:"keys,omitempty"`
|
||||
LastName *string `json:"lastname,omitempty" url:"lastname,omitempty"`
|
||||
}
|
@ -1,27 +0,0 @@
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// Authenticator is an interface for adding authentication data to a request.
|
||||
// The authenticator is also aware of the authentication context, e.g. if it
|
||||
// is configured to use the root user.
|
||||
type Authenticator interface {
|
||||
// IsRoot returns true if the authenticator is configured to use the root
|
||||
IsRoot() bool
|
||||
|
||||
// IsRootTicket returns true if the authenticator is configured to use the root directly using a login ticket.
|
||||
// (root using token is weaker, cannot change VM arch)
|
||||
IsRootTicket() bool
|
||||
|
||||
// AuthenticateRequest adds authentication data to a new request.
|
||||
AuthenticateRequest(ctx context.Context, req *http.Request) error
|
||||
}
|
@ -1,318 +0,0 @@
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package api
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/google/go-querystring/query"
|
||||
"github.com/hashicorp/terraform-plugin-log/tflog"
|
||||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/logging"
|
||||
|
||||
"github.com/bpg/terraform-provider-proxmox/utils"
|
||||
)
|
||||
|
||||
// ErrNoDataObjectInResponse is returned when the server does not include a data object in the response.
|
||||
var ErrNoDataObjectInResponse = errors.New("the server did not include a data object in the response")
|
||||
|
||||
const (
|
||||
// the large timeout is to allow for large file uploads.
|
||||
httpClientTimeout = 5 * time.Minute
|
||||
basePathJSONAPI = "api2/json"
|
||||
)
|
||||
|
||||
// Client is an interface for performing requests against the Proxmox API.
|
||||
type Client interface {
|
||||
// DoRequest performs a request against the Proxmox API.
|
||||
DoRequest(
|
||||
ctx context.Context,
|
||||
method, path string,
|
||||
requestBody, responseBody interface{},
|
||||
) error
|
||||
|
||||
// ExpandPath expands a path relative to the client's base path.
|
||||
// For example, if the client is configured for a VM and the
|
||||
// path is "firewall/options", the returned path will be
|
||||
// "/nodes/<node>/qemu/<vmid>/firewall/options".
|
||||
ExpandPath(path string) string
|
||||
|
||||
// IsRoot returns true if the client is configured with the root user.
|
||||
IsRoot() bool
|
||||
|
||||
// IsRootTicket returns true if the authenticator is configured to use the root directly using a login ticket.
|
||||
// (root using token is weaker, cannot change VM arch)
|
||||
IsRootTicket() bool
|
||||
}
|
||||
|
||||
// Connection represents a connection to the Proxmox Virtual Environment API.
|
||||
type Connection struct {
|
||||
endpoint string
|
||||
httpClient *http.Client
|
||||
}
|
||||
|
||||
// NewConnection creates and initializes a Connection instance.
|
||||
func NewConnection(endpoint string, insecure bool) (*Connection, error) {
|
||||
u, err := url.ParseRequestURI(endpoint)
|
||||
if err != nil {
|
||||
return nil, errors.New(
|
||||
"you must specify a valid endpoint for the Proxmox Virtual Environment API (valid: https://host:port/)",
|
||||
)
|
||||
}
|
||||
|
||||
if u.Scheme != "https" {
|
||||
return nil, errors.New(
|
||||
"you must specify a secure endpoint for the Proxmox Virtual Environment API (valid: https://host:port/)",
|
||||
)
|
||||
}
|
||||
|
||||
var transport http.RoundTripper = &http.Transport{
|
||||
TLSClientConfig: &tls.Config{
|
||||
InsecureSkipVerify: insecure, //nolint:gosec
|
||||
},
|
||||
}
|
||||
|
||||
if logging.IsDebugOrHigher() {
|
||||
transport = logging.NewLoggingHTTPTransport(transport)
|
||||
}
|
||||
|
||||
return &Connection{
|
||||
endpoint: strings.TrimRight(u.String(), "/"),
|
||||
httpClient: &http.Client{
|
||||
Transport: transport,
|
||||
Timeout: httpClientTimeout,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// VirtualEnvironmentClient implements an API client for the Proxmox Virtual Environment API.
|
||||
type client struct {
|
||||
conn *Connection
|
||||
auth Authenticator
|
||||
}
|
||||
|
||||
// NewClient creates and initializes a VirtualEnvironmentClient instance.
|
||||
func NewClient(creds *Credentials, conn *Connection) (Client, error) {
|
||||
if creds == nil {
|
||||
return nil, errors.New("credentials must not be nil")
|
||||
}
|
||||
|
||||
if conn == nil {
|
||||
return nil, errors.New("connection must not be nil")
|
||||
}
|
||||
|
||||
var auth Authenticator
|
||||
|
||||
var err error
|
||||
|
||||
if creds.APIToken != nil {
|
||||
auth, err = NewTokenAuthenticator(*creds.APIToken)
|
||||
} else {
|
||||
auth, err = NewTicketAuthenticator(conn, creds)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &client{
|
||||
conn: conn,
|
||||
auth: auth,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// DoRequest performs a HTTP request against a JSON API endpoint.
|
||||
func (c *client) DoRequest(
|
||||
ctx context.Context,
|
||||
method, path string,
|
||||
requestBody, responseBody interface{},
|
||||
) error {
|
||||
var reqBodyReader io.Reader
|
||||
|
||||
var reqContentLength *int64
|
||||
|
||||
modifiedPath := path
|
||||
reqBodyType := ""
|
||||
|
||||
//nolint:nestif
|
||||
if requestBody != nil {
|
||||
multipartData, multipart := requestBody.(*MultiPartData)
|
||||
pipedBodyReader, pipedBody := requestBody.(*io.PipeReader)
|
||||
|
||||
switch {
|
||||
case multipart:
|
||||
reqBodyReader = multipartData.Reader
|
||||
reqBodyType = fmt.Sprintf("multipart/form-data; boundary=%s", multipartData.Boundary)
|
||||
reqContentLength = multipartData.Size
|
||||
case pipedBody:
|
||||
reqBodyReader = pipedBodyReader
|
||||
default:
|
||||
v, err := query.Values(requestBody)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to encode HTTP %s request (path: %s) - Reason: %w",
|
||||
method,
|
||||
modifiedPath,
|
||||
err,
|
||||
)
|
||||
}
|
||||
|
||||
encodedValues := v.Encode()
|
||||
if encodedValues != "" {
|
||||
if method == http.MethodDelete || method == http.MethodGet || method == http.MethodHead {
|
||||
if !strings.Contains(modifiedPath, "?") {
|
||||
modifiedPath = fmt.Sprintf("%s?%s", modifiedPath, encodedValues)
|
||||
} else {
|
||||
modifiedPath = fmt.Sprintf("%s&%s", modifiedPath, encodedValues)
|
||||
}
|
||||
} else {
|
||||
reqBodyReader = bytes.NewBufferString(encodedValues)
|
||||
reqBodyType = "application/x-www-form-urlencoded"
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
reqBodyReader = new(bytes.Buffer)
|
||||
}
|
||||
|
||||
req, err := http.NewRequestWithContext(
|
||||
ctx,
|
||||
method,
|
||||
fmt.Sprintf("%s/%s/%s", c.conn.endpoint, basePathJSONAPI, modifiedPath),
|
||||
reqBodyReader,
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf(
|
||||
"failed to create HTTP %s request (path: %s) - Reason: %w",
|
||||
method,
|
||||
modifiedPath,
|
||||
err,
|
||||
)
|
||||
}
|
||||
|
||||
req.Header.Add("Accept", "application/json")
|
||||
|
||||
if reqContentLength != nil {
|
||||
req.ContentLength = *reqContentLength
|
||||
}
|
||||
|
||||
if reqBodyType != "" {
|
||||
req.Header.Add("Content-Type", reqBodyType)
|
||||
}
|
||||
|
||||
err = c.auth.AuthenticateRequest(ctx, req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to authenticate HTTP %s request (path: %s) - Reason: %w",
|
||||
method,
|
||||
modifiedPath,
|
||||
err,
|
||||
)
|
||||
}
|
||||
|
||||
//nolint:bodyclose
|
||||
res, err := c.conn.httpClient.Do(req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to perform HTTP %s request (path: %s) - Reason: %w",
|
||||
method,
|
||||
modifiedPath,
|
||||
err,
|
||||
)
|
||||
}
|
||||
|
||||
defer utils.CloseOrLogError(ctx)(res.Body)
|
||||
|
||||
err = validateResponseCode(res)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
//nolint:nestif
|
||||
if responseBody != nil {
|
||||
err = json.NewDecoder(res.Body).Decode(responseBody)
|
||||
if err != nil {
|
||||
return fmt.Errorf(
|
||||
"failed to decode HTTP %s response (path: %s) - Reason: %w",
|
||||
method,
|
||||
modifiedPath,
|
||||
err,
|
||||
)
|
||||
}
|
||||
} else {
|
||||
data, err := io.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
return fmt.Errorf(
|
||||
"failed to read HTTP %s response body (path: %s) - Reason: %w",
|
||||
method,
|
||||
modifiedPath,
|
||||
err,
|
||||
)
|
||||
}
|
||||
if len(data) > 0 {
|
||||
dr := dataResponse{}
|
||||
|
||||
if err2 := json.NewDecoder(bytes.NewReader(data)).Decode(&dr); err2 == nil {
|
||||
if dr.Data == nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
tflog.Warn(ctx, "unhandled HTTP response body", map[string]interface{}{
|
||||
"data": dr.Data,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type dataResponse struct {
|
||||
Data interface{} `json:"data"`
|
||||
}
|
||||
|
||||
// ExpandPath expands the given path to an absolute path.
|
||||
func (c *client) ExpandPath(path string) string {
|
||||
return path
|
||||
}
|
||||
|
||||
func (c *client) IsRoot() bool {
|
||||
return c.auth.IsRoot()
|
||||
}
|
||||
|
||||
func (c *client) IsRootTicket() bool {
|
||||
return c.auth.IsRootTicket()
|
||||
}
|
||||
|
||||
// validateResponseCode ensures that a response is valid.
|
||||
func validateResponseCode(res *http.Response) error {
|
||||
if res.StatusCode < 200 || res.StatusCode >= 300 {
|
||||
status := strings.TrimPrefix(res.Status, fmt.Sprintf("%d ", res.StatusCode))
|
||||
|
||||
errRes := &ErrorResponseBody{}
|
||||
err := json.NewDecoder(res.Body).Decode(errRes)
|
||||
|
||||
if err == nil && errRes.Errors != nil {
|
||||
var errList []string
|
||||
|
||||
for k, v := range *errRes.Errors {
|
||||
errList = append(errList, fmt.Sprintf("%s: %s", k, strings.TrimRight(v, "\n\r")))
|
||||
}
|
||||
|
||||
status = fmt.Sprintf("%s (%s)", status, strings.Join(errList, " - "))
|
||||
}
|
||||
|
||||
return fmt.Errorf("received an HTTP %d response - Reason: %s", res.StatusCode, status)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
@ -1,32 +0,0 @@
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package api
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
)
|
||||
|
||||
// MultiPartData enables multipart uploads in DoRequest.
|
||||
type MultiPartData struct {
|
||||
Boundary string
|
||||
Reader io.Reader
|
||||
Size *int64
|
||||
}
|
||||
|
||||
// ErrorResponseBody contains the body of an error response.
|
||||
type ErrorResponseBody struct {
|
||||
Data *string `json:"data"`
|
||||
Errors *map[string]string `json:"errors"`
|
||||
}
|
||||
|
||||
// FileUploadRequest is a request for uploading a file.
|
||||
type FileUploadRequest struct {
|
||||
ContentType string
|
||||
FileName string
|
||||
File *os.File
|
||||
}
|
@ -1,61 +0,0 @@
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package api
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const rootUsername = "root@pam"
|
||||
|
||||
// Credentials is a struct that holds the credentials for the Proxmox Virtual
|
||||
// Environment API.
|
||||
type Credentials struct {
|
||||
Username string
|
||||
Password string
|
||||
OTP *string
|
||||
APIToken *string
|
||||
}
|
||||
|
||||
// NewCredentials creates a new Credentials struct.
|
||||
func NewCredentials(username, password, otp, apiToken string) (*Credentials, error) {
|
||||
if apiToken != "" {
|
||||
return &Credentials{
|
||||
APIToken: &apiToken,
|
||||
}, nil
|
||||
}
|
||||
|
||||
if password == "" {
|
||||
return nil, errors.New(
|
||||
"you must specify a password for the Proxmox Virtual Environment API",
|
||||
)
|
||||
}
|
||||
|
||||
if username == "" {
|
||||
return nil, errors.New(
|
||||
"you must specify a username for the Proxmox Virtual Environment API",
|
||||
)
|
||||
}
|
||||
|
||||
if !strings.Contains(username, "@") {
|
||||
return nil, errors.New(
|
||||
"make sure the username for the Proxmox Virtual Environment API ends in '@pve or @pam'",
|
||||
)
|
||||
}
|
||||
|
||||
c := &Credentials{
|
||||
Username: username,
|
||||
Password: password,
|
||||
}
|
||||
|
||||
if otp != "" {
|
||||
c.OTP = &otp
|
||||
}
|
||||
|
||||
return c, nil
|
||||
}
|
@ -1,137 +0,0 @@
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package api
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"github.com/hashicorp/terraform-plugin-log/tflog"
|
||||
|
||||
"github.com/bpg/terraform-provider-proxmox/utils"
|
||||
)
|
||||
|
||||
type ticketAuthenticator struct {
|
||||
conn *Connection
|
||||
authRequest string
|
||||
authData *AuthenticationResponseData
|
||||
}
|
||||
|
||||
// NewTicketAuthenticator returns a new ticket authenticator.
|
||||
func NewTicketAuthenticator(conn *Connection, creds *Credentials) (Authenticator, error) {
|
||||
authRequest := fmt.Sprintf(
|
||||
"username=%s&password=%s",
|
||||
url.QueryEscape(creds.Username),
|
||||
url.QueryEscape(creds.Password),
|
||||
)
|
||||
|
||||
// OTP is optional, and probably doesn't make much sense for most provider users.
|
||||
if creds.OTP != nil {
|
||||
authRequest = fmt.Sprintf("%s&otp=%s", authRequest, url.QueryEscape(*creds.OTP))
|
||||
}
|
||||
|
||||
return &ticketAuthenticator{
|
||||
conn: conn,
|
||||
authRequest: authRequest,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (t *ticketAuthenticator) authenticate(ctx context.Context) (*AuthenticationResponseData, error) {
|
||||
if t.authData != nil {
|
||||
return t.authData, nil
|
||||
}
|
||||
|
||||
req, err := http.NewRequestWithContext(
|
||||
ctx,
|
||||
http.MethodPost,
|
||||
fmt.Sprintf("%s/%s/access/ticket", t.conn.endpoint, basePathJSONAPI),
|
||||
bytes.NewBufferString(t.authRequest),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create authentication request: %w", err)
|
||||
}
|
||||
|
||||
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
|
||||
|
||||
tflog.Debug(ctx, "Sending authentication request", map[string]interface{}{
|
||||
"path": req.URL.Path,
|
||||
})
|
||||
|
||||
//nolint:bodyclose
|
||||
res, err := t.conn.httpClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to retrieve authentication response: %w", err)
|
||||
}
|
||||
|
||||
defer utils.CloseOrLogError(ctx)(res.Body)
|
||||
|
||||
err = validateResponseCode(res)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to authenticate: %w", err)
|
||||
}
|
||||
|
||||
resBody := AuthenticationResponseBody{}
|
||||
|
||||
err = json.NewDecoder(res.Body).Decode(&resBody)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to decode authentication response, %w", err)
|
||||
}
|
||||
|
||||
if resBody.Data == nil {
|
||||
return nil, errors.New("the server did not include a data object in the authentication response")
|
||||
}
|
||||
|
||||
if resBody.Data.CSRFPreventionToken == nil {
|
||||
return nil, errors.New(
|
||||
"the server did not include a CSRF prevention token in the authentication response",
|
||||
)
|
||||
}
|
||||
|
||||
if resBody.Data.Ticket == nil {
|
||||
return nil, errors.New("the server did not include a ticket in the authentication response")
|
||||
}
|
||||
|
||||
if resBody.Data.Username == "" {
|
||||
return nil, errors.New("the server did not include the username in the authentication response")
|
||||
}
|
||||
|
||||
t.authData = resBody.Data
|
||||
|
||||
return resBody.Data, nil
|
||||
}
|
||||
|
||||
func (t *ticketAuthenticator) IsRoot() bool {
|
||||
return t.authData != nil && t.authData.Username == rootUsername
|
||||
}
|
||||
|
||||
func (t *ticketAuthenticator) IsRootTicket() bool {
|
||||
return t.IsRoot()
|
||||
}
|
||||
|
||||
// AuthenticateRequest adds authentication data to a new request.
|
||||
func (t *ticketAuthenticator) AuthenticateRequest(ctx context.Context, req *http.Request) error {
|
||||
a, err := t.authenticate(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to authenticate: %w", err)
|
||||
}
|
||||
|
||||
req.AddCookie(&http.Cookie{
|
||||
Name: "PVEAuthCookie",
|
||||
Value: *a.Ticket,
|
||||
})
|
||||
|
||||
if req.Method != http.MethodGet {
|
||||
req.Header.Add("CSRFPreventionToken", *a.CSRFPreventionToken)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
@ -1,34 +0,0 @@
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package api
|
||||
|
||||
import (
|
||||
"github.com/bpg/terraform-provider-proxmox/internal/types"
|
||||
)
|
||||
|
||||
// AuthenticationResponseBody contains the body from an authentication response.
|
||||
type AuthenticationResponseBody struct {
|
||||
Data *AuthenticationResponseData `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
// AuthenticationResponseCapabilities contains the supported capabilities for a session.
|
||||
type AuthenticationResponseCapabilities struct {
|
||||
Access *types.CustomPrivileges `json:"access,omitempty"`
|
||||
Datacenter *types.CustomPrivileges `json:"dc,omitempty"`
|
||||
Nodes *types.CustomPrivileges `json:"nodes,omitempty"`
|
||||
Storage *types.CustomPrivileges `json:"storage,omitempty"`
|
||||
VMs *types.CustomPrivileges `json:"vms,omitempty"`
|
||||
}
|
||||
|
||||
// AuthenticationResponseData contains the data from an authentication response.
|
||||
type AuthenticationResponseData struct {
|
||||
ClusterName *string `json:"clustername,omitempty"`
|
||||
CSRFPreventionToken *string `json:"CSRFPreventionToken,omitempty"`
|
||||
Capabilities *AuthenticationResponseCapabilities `json:"cap,omitempty"`
|
||||
Ticket *string `json:"ticket,omitempty"`
|
||||
Username string `json:"username"`
|
||||
}
|
@ -1,41 +0,0 @@
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type tokenAuthenticator struct {
|
||||
username string
|
||||
token string
|
||||
}
|
||||
|
||||
// NewTokenAuthenticator creates a new authenticator that uses a PVE API Token
|
||||
// for authentication.
|
||||
func NewTokenAuthenticator(token string) (Authenticator, error) {
|
||||
return &tokenAuthenticator{
|
||||
username: strings.Split(token, "!")[0],
|
||||
token: token,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (t *tokenAuthenticator) IsRoot() bool {
|
||||
return t.username == rootUsername
|
||||
}
|
||||
|
||||
func (t *tokenAuthenticator) IsRootTicket() bool {
|
||||
// Logged using a token, therefore not a ticket login
|
||||
return false
|
||||
}
|
||||
|
||||
func (t *tokenAuthenticator) AuthenticateRequest(_ context.Context, req *http.Request) error {
|
||||
req.Header.Set("Authorization", "PVEAPIToken="+t.token)
|
||||
return nil
|
||||
}
|
@ -1,95 +0,0 @@
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package proxmox
|
||||
|
||||
import (
|
||||
"github.com/bpg/terraform-provider-proxmox/proxmox/access"
|
||||
"github.com/bpg/terraform-provider-proxmox/proxmox/api"
|
||||
"github.com/bpg/terraform-provider-proxmox/proxmox/cluster"
|
||||
"github.com/bpg/terraform-provider-proxmox/proxmox/nodes"
|
||||
"github.com/bpg/terraform-provider-proxmox/proxmox/pools"
|
||||
"github.com/bpg/terraform-provider-proxmox/proxmox/ssh"
|
||||
"github.com/bpg/terraform-provider-proxmox/proxmox/storage"
|
||||
"github.com/bpg/terraform-provider-proxmox/proxmox/version"
|
||||
)
|
||||
|
||||
// Client defines a client interface for the Proxmox Virtual Environment API.
|
||||
type Client interface {
|
||||
// Access returns a client for managing access control.
|
||||
Access() *access.Client
|
||||
|
||||
// Cluster returns a client for managing the cluster.
|
||||
Cluster() *cluster.Client
|
||||
|
||||
// Node returns a client for managing resources on a specific node.
|
||||
Node(nodeName string) *nodes.Client
|
||||
|
||||
// Pool returns a client for managing resource pools.
|
||||
Pool() *pools.Client
|
||||
|
||||
// Storage returns a client for managing storage.
|
||||
Storage() *storage.Client
|
||||
|
||||
// Version returns a client for getting the version of the Proxmox Virtual Environment API.
|
||||
Version() *version.Client
|
||||
|
||||
// API returns a lower-lever REST API client.
|
||||
API() api.Client
|
||||
|
||||
// SSH returns a lower-lever SSH client.
|
||||
SSH() ssh.Client
|
||||
}
|
||||
|
||||
type client struct {
|
||||
a api.Client
|
||||
s ssh.Client
|
||||
}
|
||||
|
||||
// NewClient creates a new API client.
|
||||
func NewClient(a api.Client, s ssh.Client) Client {
|
||||
return &client{a: a, s: s}
|
||||
}
|
||||
|
||||
// Access returns a client for managing access control.
|
||||
func (c *client) Access() *access.Client {
|
||||
return &access.Client{Client: c.a}
|
||||
}
|
||||
|
||||
// Cluster returns a client for managing the cluster.
|
||||
func (c *client) Cluster() *cluster.Client {
|
||||
return &cluster.Client{Client: c.a}
|
||||
}
|
||||
|
||||
// Node returns a client for managing resources on a specific node.
|
||||
func (c *client) Node(nodeName string) *nodes.Client {
|
||||
return &nodes.Client{Client: c.a, NodeName: nodeName}
|
||||
}
|
||||
|
||||
// Pool returns a client for managing resource pools.
|
||||
func (c *client) Pool() *pools.Client {
|
||||
return &pools.Client{Client: c.a}
|
||||
}
|
||||
|
||||
// Storage returns a client for managing storage.
|
||||
func (c *client) Storage() *storage.Client {
|
||||
return &storage.Client{Client: c.a}
|
||||
}
|
||||
|
||||
// Version returns a client for getting the version of the Proxmox Virtual Environment API.
|
||||
func (c *client) Version() *version.Client {
|
||||
return &version.Client{Client: c.a}
|
||||
}
|
||||
|
||||
// API returns a lower-lever REST API client.
|
||||
func (c *client) API() api.Client {
|
||||
return c.a
|
||||
}
|
||||
|
||||
// SSH returns a lower-lever SSH client.s.
|
||||
func (c *client) SSH() ssh.Client {
|
||||
return c.s
|
||||
}
|
@ -1,32 +0,0 @@
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package cluster
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/bpg/terraform-provider-proxmox/proxmox/api"
|
||||
clusterfirewall "github.com/bpg/terraform-provider-proxmox/proxmox/cluster/firewall"
|
||||
"github.com/bpg/terraform-provider-proxmox/proxmox/firewall"
|
||||
)
|
||||
|
||||
// Client is an interface for accessing the Proxmox cluster API.
|
||||
type Client struct {
|
||||
api.Client
|
||||
}
|
||||
|
||||
// ExpandPath expands a relative path to a full cluster API path.
|
||||
func (c *Client) ExpandPath(path string) string {
|
||||
return fmt.Sprintf("cluster/%s", path)
|
||||
}
|
||||
|
||||
// Firewall returns a client for managing the cluster firewall.
|
||||
func (c *Client) Firewall() clusterfirewall.API {
|
||||
return &clusterfirewall.Client{
|
||||
Client: firewall.Client{Client: c},
|
||||
}
|
||||
}
|
@ -1,143 +0,0 @@
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package cluster
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"sync"
|
||||
|
||||
"github.com/hashicorp/terraform-plugin-log/tflog"
|
||||
|
||||
"github.com/bpg/terraform-provider-proxmox/proxmox/api"
|
||||
)
|
||||
|
||||
const (
|
||||
getVMIDStep = 1
|
||||
)
|
||||
|
||||
// ErrVMDoesNotExist is returned when the VM identifier cannot be found on any cluster node.
|
||||
var ErrVMDoesNotExist = errors.New("unable to find VM identifier on any cluster node")
|
||||
|
||||
var (
|
||||
//nolint:gochecknoglobals
|
||||
getVMIDCounter = -1
|
||||
//nolint:gochecknoglobals
|
||||
getVMIDCounterMutex = &sync.Mutex{}
|
||||
)
|
||||
|
||||
// GetNextID retrieves the next free VM identifier for the cluster.
|
||||
func (c *Client) GetNextID(ctx context.Context, vmID *int) (*int, error) {
|
||||
reqBody := &NextIDRequestBody{
|
||||
VMID: vmID,
|
||||
}
|
||||
|
||||
resBody := &NextIDResponseBody{}
|
||||
|
||||
err := c.DoRequest(ctx, http.MethodGet, "cluster/nextid", reqBody, resBody)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error retrieving next VM ID: %w", err)
|
||||
}
|
||||
|
||||
if resBody.Data == nil {
|
||||
return nil, api.ErrNoDataObjectInResponse
|
||||
}
|
||||
|
||||
return (*int)(resBody.Data), nil
|
||||
}
|
||||
|
||||
// GetVMID retrieves the next available VM identifier.
|
||||
func (c *Client) GetVMID(ctx context.Context) (*int, error) {
|
||||
getVMIDCounterMutex.Lock()
|
||||
defer getVMIDCounterMutex.Unlock()
|
||||
|
||||
if getVMIDCounter < 0 {
|
||||
nextVMID, err := c.GetNextID(ctx, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if nextVMID == nil {
|
||||
return nil, errors.New("unable to retrieve the next available VM identifier")
|
||||
}
|
||||
|
||||
getVMIDCounter = *nextVMID + getVMIDStep
|
||||
|
||||
tflog.Debug(ctx, "next VM identifier", map[string]interface{}{
|
||||
"id": *nextVMID,
|
||||
})
|
||||
|
||||
return nextVMID, nil
|
||||
}
|
||||
|
||||
vmID := getVMIDCounter
|
||||
|
||||
for vmID <= 2147483637 {
|
||||
_, err := c.GetNextID(ctx, &vmID)
|
||||
if err != nil {
|
||||
vmID += getVMIDStep
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
getVMIDCounter = vmID + getVMIDStep
|
||||
|
||||
tflog.Debug(ctx, "next VM identifier", map[string]interface{}{
|
||||
"id": vmID,
|
||||
})
|
||||
|
||||
return &vmID, nil
|
||||
}
|
||||
|
||||
return nil, errors.New("unable to determine the next available VM identifier")
|
||||
}
|
||||
|
||||
// GetClusterResources retrieves current resources for cluster.
|
||||
func (c *Client) GetClusterResources(ctx context.Context, resourceType string) ([]*ResourcesListResponseData, error) {
|
||||
reqBody := &ResourcesListRequestBody{
|
||||
Type: resourceType,
|
||||
}
|
||||
resBody := &ResourcesListBody{}
|
||||
|
||||
err := c.DoRequest(ctx, http.MethodGet, "cluster/resources", reqBody, resBody)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get resources list of type (\"%s\") for cluster: %w", resourceType, err)
|
||||
}
|
||||
|
||||
if resBody.Data == nil {
|
||||
return nil, api.ErrNoDataObjectInResponse
|
||||
}
|
||||
|
||||
return resBody.Data, nil
|
||||
}
|
||||
|
||||
// GetClusterResourcesVM retrieves current VM resources for cluster.
|
||||
func (c *Client) GetClusterResourcesVM(ctx context.Context) ([]*ResourcesListResponseData, error) {
|
||||
return c.GetClusterResources(ctx, "vm")
|
||||
}
|
||||
|
||||
// GetVMNodeName gets node for specified vmID.
|
||||
func (c *Client) GetVMNodeName(ctx context.Context, vmID int) (*string, error) {
|
||||
allClusterVM, err := c.GetClusterResourcesVM(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if allClusterVM == nil {
|
||||
return nil, api.ErrNoDataObjectInResponse
|
||||
}
|
||||
|
||||
for _, v := range allClusterVM {
|
||||
if v.VMID == vmID {
|
||||
return &v.NodeName, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, ErrVMDoesNotExist
|
||||
}
|
@ -1,55 +0,0 @@
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package cluster
|
||||
|
||||
import (
|
||||
"github.com/bpg/terraform-provider-proxmox/internal/types"
|
||||
)
|
||||
|
||||
// NextIDRequestBody contains the data for a cluster next id request.
|
||||
type NextIDRequestBody struct {
|
||||
VMID *int `json:"vmid,omitempty" url:"vmid,omitempty"`
|
||||
}
|
||||
|
||||
// NextIDResponseBody contains the body from a cluster next id response.
|
||||
type NextIDResponseBody struct {
|
||||
Data *types.CustomInt `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
// ResourcesListBody contains the body from a cluste resource list response.
|
||||
type ResourcesListBody struct {
|
||||
Data []*ResourcesListResponseData `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
// ResourcesListRequestBody contains the body params to cluster resource list request.
|
||||
type ResourcesListRequestBody struct {
|
||||
Type string `json:"type" url:"type"`
|
||||
}
|
||||
|
||||
// ResourcesListResponseData contains the data from a cluster resource list body response.
|
||||
type ResourcesListResponseData struct {
|
||||
Type string `json:"type"`
|
||||
ID string `json:"id"`
|
||||
CgroupMode int `json:"cgroup-mode,omitempty"`
|
||||
Content int `json:"content,omitempty"`
|
||||
CPU float64 `json:"cpu,omitempty"`
|
||||
Disk int `json:"disk,omitempty"`
|
||||
HaState string `json:"hastate,omitempty"`
|
||||
Level string `json:"level,omitempty"`
|
||||
MaxCPU float64 `json:"maxcpu,omitempty"`
|
||||
MaxDisk int `json:"maxdisk,omitempty"`
|
||||
MaxMem int `json:"maxmem,omitempty"`
|
||||
Mem int `json:"mem,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
NodeName string `json:"node,omitempty"`
|
||||
PluginType string `json:"plugintype,omitempty"`
|
||||
PoolName string `json:"poolname,omitempty"`
|
||||
Status string `json:"status,omitempty"`
|
||||
Storage string `json:"storage,omitempty"`
|
||||
Uptime int `json:"uptime,omitempty"`
|
||||
VMID int `json:"vmid,omitempty"`
|
||||
}
|
@ -1,50 +0,0 @@
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package firewall
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/bpg/terraform-provider-proxmox/proxmox/firewall"
|
||||
)
|
||||
|
||||
// API is an interface for managing cluster firewall.
|
||||
type API interface {
|
||||
firewall.API
|
||||
SecurityGroup
|
||||
Options
|
||||
SecurityGroup(group string) firewall.Rule
|
||||
}
|
||||
|
||||
// Client is an interface for accessing the Proxmox cluster firewall API.
|
||||
type Client struct {
|
||||
firewall.Client
|
||||
}
|
||||
|
||||
type groupClient struct {
|
||||
firewall.Client
|
||||
Group string
|
||||
}
|
||||
|
||||
// SecurityGroup returns a client for managing a specific security group.
|
||||
func (c *Client) SecurityGroup(group string) firewall.Rule {
|
||||
// My head really hurts when I'm looking at this code
|
||||
// I'm not sure if this is the best way to do the required
|
||||
// interface composition and method "overrides", but it works.
|
||||
return &Client{
|
||||
Client: firewall.Client{
|
||||
Client: &groupClient{
|
||||
Client: c.Client,
|
||||
Group: group,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (c *groupClient) ExpandPath(_ string) string {
|
||||
return fmt.Sprintf("cluster/firewall/groups/%s", c.Group)
|
||||
}
|
@ -1,47 +0,0 @@
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package firewall
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/bpg/terraform-provider-proxmox/proxmox/api"
|
||||
)
|
||||
|
||||
// Options is an interface for managing global firewall options.
|
||||
type Options interface {
|
||||
SetGlobalOptions(ctx context.Context, d *OptionsPutRequestBody) error
|
||||
GetGlobalOptions(ctx context.Context) (*OptionsGetResponseData, error)
|
||||
}
|
||||
|
||||
// SetGlobalOptions sets the global firewall options.
|
||||
func (c *Client) SetGlobalOptions(ctx context.Context, d *OptionsPutRequestBody) error {
|
||||
err := c.DoRequest(ctx, http.MethodPut, "cluster/firewall/options", d, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error setting optionss: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetGlobalOptions retrieves the global firewall options.
|
||||
func (c *Client) GetGlobalOptions(ctx context.Context) (*OptionsGetResponseData, error) {
|
||||
resBody := &OptionsGetResponseBody{}
|
||||
|
||||
err := c.DoRequest(ctx, http.MethodGet, "cluster/firewall/options", nil, resBody)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error retrieving options: %w", err)
|
||||
}
|
||||
|
||||
if resBody.Data == nil {
|
||||
return nil, api.ErrNoDataObjectInResponse
|
||||
}
|
||||
|
||||
return resBody.Data, nil
|
||||
}
|
@ -1,109 +0,0 @@
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package firewall
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/bpg/terraform-provider-proxmox/internal/types"
|
||||
)
|
||||
|
||||
// OptionsPutRequestBody is the request body for the PUT /cluster/firewall/options API call.
|
||||
type OptionsPutRequestBody struct {
|
||||
EBTables *types.CustomBool `json:"ebtables,omitempty" url:"ebtables,omitempty,int"`
|
||||
Enable *types.CustomBool `json:"enable,omitempty" url:"enable,omitempty,int"`
|
||||
LogRateLimit *CustomLogRateLimit `json:"log_ratelimit,omitempty" url:"log_ratelimit,omitempty"`
|
||||
PolicyIn *string `json:"policy_in,omitempty" url:"policy_in,omitempty"`
|
||||
PolicyOut *string `json:"policy_out,omitempty" url:"policy_out,omitempty"`
|
||||
}
|
||||
|
||||
// CustomLogRateLimit is a custom type for the log_ratelimit field of the firewall optionss.
|
||||
type CustomLogRateLimit struct {
|
||||
Enable types.CustomBool `json:"enable,omitempty" url:"enable,omitempty,int"`
|
||||
Burst *int `json:"burst,omitempty" url:"burst,omitempty,int"`
|
||||
Rate *string `json:"rate,omitempty" url:"rate,omitempty"`
|
||||
}
|
||||
|
||||
// OptionsGetResponseBody is the response body for the GET /cluster/firewall/options API call.
|
||||
type OptionsGetResponseBody struct {
|
||||
Data *OptionsGetResponseData `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
// OptionsGetResponseData is the data field of the response body for the GET /cluster/firewall/options API call.
|
||||
type OptionsGetResponseData struct {
|
||||
EBTables *types.CustomBool `json:"ebtables" url:"ebtables, int"`
|
||||
Enable *types.CustomBool `json:"enable" url:"enable,int"`
|
||||
LogRateLimit *CustomLogRateLimit `json:"log_ratelimit" url:"log_ratelimit"`
|
||||
PolicyIn *string `json:"policy_in" url:"policy_in"`
|
||||
PolicyOut *string `json:"policy_out" url:"policy_out"`
|
||||
}
|
||||
|
||||
// EncodeValues converts a CustomWatchdogDevice struct to a URL vlaue.
|
||||
func (r *CustomLogRateLimit) EncodeValues(key string, v *url.Values) error {
|
||||
var values []string
|
||||
|
||||
if r.Enable {
|
||||
values = append(values, "enable=1")
|
||||
} else {
|
||||
values = append(values, "enable=0")
|
||||
}
|
||||
|
||||
if r.Burst != nil {
|
||||
values = append(values, fmt.Sprintf("burst=%d", *r.Burst))
|
||||
}
|
||||
|
||||
if r.Rate != nil {
|
||||
values = append(values, fmt.Sprintf("rate=%s", *r.Rate))
|
||||
}
|
||||
|
||||
v.Add(key, strings.Join(values, ","))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnmarshalJSON unmarshals a CustomLogRateLimit struct from JSON.
|
||||
func (r *CustomLogRateLimit) UnmarshalJSON(b []byte) error {
|
||||
var s string
|
||||
|
||||
err := json.Unmarshal(b, &s)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error unmarshaling json: %w", err)
|
||||
}
|
||||
|
||||
if s == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
pairs := strings.Split(s, ",")
|
||||
|
||||
for _, p := range pairs {
|
||||
v := strings.Split(strings.TrimSpace(p), "=")
|
||||
|
||||
if len(v) == 1 {
|
||||
r.Enable = v[0] == "1"
|
||||
} else if len(v) == 2 {
|
||||
switch v[0] {
|
||||
case "enable":
|
||||
r.Enable = v[1] == "1"
|
||||
case "burst":
|
||||
iv, err := strconv.Atoi(v[1])
|
||||
if err != nil {
|
||||
return fmt.Errorf("error converting burst to int: %w", err)
|
||||
}
|
||||
r.Burst = &iv
|
||||
case "rate":
|
||||
r.Rate = &v[1]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
@ -1,91 +0,0 @@
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package firewall
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"sort"
|
||||
|
||||
"github.com/bpg/terraform-provider-proxmox/proxmox/api"
|
||||
)
|
||||
|
||||
// SecurityGroup is an interface for the Proxmox security group API.
|
||||
type SecurityGroup interface {
|
||||
CreateGroup(ctx context.Context, d *GroupCreateRequestBody) error
|
||||
ListGroups(ctx context.Context) ([]*GroupListResponseData, error)
|
||||
UpdateGroup(ctx context.Context, d *GroupUpdateRequestBody) error
|
||||
DeleteGroup(ctx context.Context, group string) error
|
||||
}
|
||||
|
||||
func (c *Client) securityGroupsPath() string {
|
||||
return "cluster/firewall/groups"
|
||||
}
|
||||
|
||||
// CreateGroup create new security group.
|
||||
func (c *Client) CreateGroup(ctx context.Context, d *GroupCreateRequestBody) error {
|
||||
err := c.DoRequest(ctx, http.MethodPost, c.securityGroupsPath(), d, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error creating security group: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ListGroups retrieve list of security groups.
|
||||
func (c *Client) ListGroups(ctx context.Context) ([]*GroupListResponseData, error) {
|
||||
resBody := &GroupListResponseBody{}
|
||||
|
||||
err := c.DoRequest(ctx, http.MethodGet, c.securityGroupsPath(), nil, resBody)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error retrieving security groups: %w", err)
|
||||
}
|
||||
|
||||
if resBody.Data == nil {
|
||||
return nil, api.ErrNoDataObjectInResponse
|
||||
}
|
||||
|
||||
sort.Slice(resBody.Data, func(i, j int) bool {
|
||||
return resBody.Data[i].Group < resBody.Data[j].Group
|
||||
})
|
||||
|
||||
return resBody.Data, nil
|
||||
}
|
||||
|
||||
// UpdateGroup update security group.
|
||||
func (c *Client) UpdateGroup(ctx context.Context, d *GroupUpdateRequestBody) error {
|
||||
err := c.DoRequest(
|
||||
ctx,
|
||||
http.MethodPost,
|
||||
c.securityGroupsPath(),
|
||||
d,
|
||||
nil,
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error updating security group: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteGroup delete security group.
|
||||
func (c *Client) DeleteGroup(ctx context.Context, group string) error {
|
||||
err := c.DoRequest(
|
||||
ctx,
|
||||
http.MethodDelete,
|
||||
fmt.Sprintf("%s/%s", c.securityGroupsPath(), url.PathEscape(group)),
|
||||
nil,
|
||||
nil,
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error deleting security group '%s': %w", group, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
@ -1,35 +0,0 @@
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package firewall
|
||||
|
||||
// GroupCreateRequestBody contains the data for a security group create request.
|
||||
type GroupCreateRequestBody struct {
|
||||
Group string `json:"group" url:"group"`
|
||||
Comment *string `json:"comment,omitempty" url:"comment,omitempty"`
|
||||
Digest *string `json:"digest,omitempty" url:"digest,omitempty"`
|
||||
}
|
||||
|
||||
// GroupListResponseData contains the data from a group list response.
|
||||
type GroupListResponseData struct {
|
||||
Comment *string `json:"comment,omitempty" url:"comment,omitempty"`
|
||||
Group string `json:"group" url:"group"`
|
||||
Digest string `json:"digest" url:"digest"`
|
||||
}
|
||||
|
||||
// GroupListResponseBody contains the data from a group get response.
|
||||
type GroupListResponseBody struct {
|
||||
Data []*GroupListResponseData `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
// GroupUpdateRequestBody contains the data for a group update request.
|
||||
type GroupUpdateRequestBody struct {
|
||||
Group string `json:"group" url:"group"`
|
||||
|
||||
Comment *string `json:"comment,omitempty" url:"comment,omitempty"`
|
||||
ReName *string `json:"rename,omitempty" url:"rename,omitempty"`
|
||||
Digest *string `json:"digest,omitempty" url:"digest,omitempty"`
|
||||
}
|
@ -1,100 +0,0 @@
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package firewall
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"sort"
|
||||
|
||||
"github.com/bpg/terraform-provider-proxmox/proxmox/api"
|
||||
)
|
||||
|
||||
// Alias is an interface for managing firewall aliases.
|
||||
type Alias interface {
|
||||
CreateAlias(ctx context.Context, d *AliasCreateRequestBody) error
|
||||
DeleteAlias(ctx context.Context, name string) error
|
||||
GetAlias(ctx context.Context, name string) (*AliasGetResponseData, error)
|
||||
ListAliases(ctx context.Context) ([]*AliasGetResponseData, error)
|
||||
UpdateAlias(ctx context.Context, name string, d *AliasUpdateRequestBody) error
|
||||
}
|
||||
|
||||
func (c *Client) aliasesPath() string {
|
||||
return c.ExpandPath("firewall/aliases")
|
||||
}
|
||||
|
||||
func (c *Client) aliasPath(name string) string {
|
||||
return fmt.Sprintf("%s/%s", c.aliasesPath(), url.PathEscape(name))
|
||||
}
|
||||
|
||||
// CreateAlias create an alias.
|
||||
func (c *Client) CreateAlias(ctx context.Context, d *AliasCreateRequestBody) error {
|
||||
err := c.DoRequest(ctx, http.MethodPost, c.aliasesPath(), d, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error creating alias: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteAlias delete an alias.
|
||||
func (c *Client) DeleteAlias(ctx context.Context, name string) error {
|
||||
err := c.DoRequest(ctx, http.MethodDelete, c.aliasPath(name), nil, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error deleting alias '%s': %w", name, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetAlias retrieves an alias.
|
||||
func (c *Client) GetAlias(ctx context.Context, name string) (*AliasGetResponseData, error) {
|
||||
resBody := &AliasGetResponseBody{}
|
||||
|
||||
err := c.DoRequest(ctx, http.MethodGet, c.aliasPath(name), nil, resBody)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error retrieving alias '%s': %w", name, err)
|
||||
}
|
||||
|
||||
if resBody.Data == nil {
|
||||
return nil, api.ErrNoDataObjectInResponse
|
||||
}
|
||||
|
||||
return resBody.Data, nil
|
||||
}
|
||||
|
||||
// ListAliases retrieves a list of aliases.
|
||||
func (c *Client) ListAliases(ctx context.Context) ([]*AliasGetResponseData, error) {
|
||||
resBody := &AliasListResponseBody{}
|
||||
|
||||
err := c.DoRequest(ctx, http.MethodGet, c.aliasesPath(), nil, resBody)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error retrieving aliases: %w", err)
|
||||
}
|
||||
|
||||
if resBody.Data == nil {
|
||||
return nil, api.ErrNoDataObjectInResponse
|
||||
}
|
||||
|
||||
sort.Slice(resBody.Data, func(i, j int) bool {
|
||||
return resBody.Data[i].Name < resBody.Data[j].Name
|
||||
})
|
||||
|
||||
return resBody.Data, nil
|
||||
}
|
||||
|
||||
// UpdateAlias updates an alias.
|
||||
func (c *Client) UpdateAlias(ctx context.Context, name string, d *AliasUpdateRequestBody) error {
|
||||
err := c.DoRequest(ctx, http.MethodPut, c.aliasPath(name), d, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error updating alias '%s': %w", name, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
@ -1,40 +0,0 @@
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package firewall
|
||||
|
||||
// AliasCreateRequestBody contains the data for an alias create request.
|
||||
type AliasCreateRequestBody struct {
|
||||
Comment *string `json:"comment,omitempty" url:"comment,omitempty"`
|
||||
Name string `json:"name" url:"name"`
|
||||
CIDR string `json:"cidr" url:"cidr"`
|
||||
}
|
||||
|
||||
// AliasGetResponseBody contains the body from an alias get response.
|
||||
type AliasGetResponseBody struct {
|
||||
Data *AliasGetResponseData `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
// AliasGetResponseData contains the data from an alias get response.
|
||||
type AliasGetResponseData struct {
|
||||
Comment *string `json:"comment,omitempty" url:"comment,omitempty"`
|
||||
Name string `json:"name" url:"name"`
|
||||
CIDR string `json:"cidr" url:"cidr"`
|
||||
Digest *string `json:"digest" url:"digest"`
|
||||
IPVersion int `json:"ipversion" url:"ipversion"`
|
||||
}
|
||||
|
||||
// AliasListResponseBody contains the data from an alias get response.
|
||||
type AliasListResponseBody struct {
|
||||
Data []*AliasGetResponseData `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
// AliasUpdateRequestBody contains the data for an alias update request.
|
||||
type AliasUpdateRequestBody struct {
|
||||
Comment *string `json:"comment,omitempty" url:"comment,omitempty"`
|
||||
ReName string `json:"rename" url:"rename"`
|
||||
CIDR string `json:"cidr" url:"cidr"`
|
||||
}
|
@ -1,24 +0,0 @@
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package firewall
|
||||
|
||||
import (
|
||||
"github.com/bpg/terraform-provider-proxmox/proxmox/api"
|
||||
)
|
||||
|
||||
// API is an interface for the Proxmox firewall API.
|
||||
type API interface {
|
||||
Alias
|
||||
IPSet
|
||||
Rule
|
||||
Options
|
||||
}
|
||||
|
||||
// Client is an interface for accessing the Proxmox firewall API.
|
||||
type Client struct {
|
||||
api.Client
|
||||
}
|
@ -1,146 +0,0 @@
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Reference: https://pve.proxmox.com/pve-docs/api-viewer/#/cluster/firewall/ipset
|
||||
*/
|
||||
|
||||
package firewall
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"sort"
|
||||
|
||||
"github.com/bpg/terraform-provider-proxmox/proxmox/api"
|
||||
)
|
||||
|
||||
// IPSet is an interface for managing IP sets.
|
||||
type IPSet interface {
|
||||
CreateIPSet(ctx context.Context, d *IPSetCreateRequestBody) error
|
||||
AddCIDRToIPSet(ctx context.Context, id string, d IPSetGetResponseData) error
|
||||
UpdateIPSet(ctx context.Context, d *IPSetUpdateRequestBody) error
|
||||
DeleteIPSet(ctx context.Context, id string) error
|
||||
DeleteIPSetContent(ctx context.Context, id string, cidr string) error
|
||||
GetIPSetContent(ctx context.Context, id string) ([]*IPSetGetResponseData, error)
|
||||
ListIPSets(ctx context.Context) ([]*IPSetListResponseData, error)
|
||||
}
|
||||
|
||||
func (c *Client) ipsetPath() string {
|
||||
return c.ExpandPath("firewall/ipset")
|
||||
}
|
||||
|
||||
// CreateIPSet create an IPSet.
|
||||
func (c *Client) CreateIPSet(ctx context.Context, d *IPSetCreateRequestBody) error {
|
||||
err := c.DoRequest(ctx, http.MethodPost, c.ipsetPath(), d, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error creating IPSet: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddCIDRToIPSet adds IP or Network to IPSet.
|
||||
func (c *Client) AddCIDRToIPSet(ctx context.Context, id string, d IPSetGetResponseData) error {
|
||||
err := c.DoRequest(
|
||||
ctx,
|
||||
http.MethodPost,
|
||||
fmt.Sprintf("%s/%s", c.ipsetPath(), url.PathEscape(id)),
|
||||
&d,
|
||||
nil,
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error adding CIDR to IPSet: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateIPSet updates an IPSet.
|
||||
func (c *Client) UpdateIPSet(ctx context.Context, d *IPSetUpdateRequestBody) error {
|
||||
err := c.DoRequest(ctx, http.MethodPost, c.ipsetPath(), d, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error updating IPSet: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteIPSet delete an IPSet.
|
||||
func (c *Client) DeleteIPSet(ctx context.Context, id string) error {
|
||||
err := c.DoRequest(
|
||||
ctx,
|
||||
http.MethodDelete,
|
||||
fmt.Sprintf("%s/%s", c.ipsetPath(), url.PathEscape(id)),
|
||||
nil,
|
||||
nil,
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error deleting IPSet %s: %w", id, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteIPSetContent remove IP or Network from IPSet.
|
||||
func (c *Client) DeleteIPSetContent(ctx context.Context, id string, cidr string) error {
|
||||
err := c.DoRequest(
|
||||
ctx,
|
||||
http.MethodDelete,
|
||||
fmt.Sprintf("%s/%s/%s", c.ipsetPath(), url.PathEscape(id), url.PathEscape(cidr)),
|
||||
nil,
|
||||
nil,
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error deleting IPSet content %s: %w", id, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetIPSetContent retrieve a list of IPSet content.
|
||||
func (c *Client) GetIPSetContent(ctx context.Context, id string) ([]*IPSetGetResponseData, error) {
|
||||
resBody := &IPSetGetResponseBody{}
|
||||
|
||||
err := c.DoRequest(
|
||||
ctx,
|
||||
http.MethodGet,
|
||||
fmt.Sprintf("%s/%s", c.ipsetPath(), url.PathEscape(id)),
|
||||
nil,
|
||||
resBody,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error getting IPSet content: %w", err)
|
||||
}
|
||||
|
||||
if resBody.Data == nil {
|
||||
return nil, api.ErrNoDataObjectInResponse
|
||||
}
|
||||
|
||||
return resBody.Data, nil
|
||||
}
|
||||
|
||||
// ListIPSets retrieves list of IPSets.
|
||||
func (c *Client) ListIPSets(ctx context.Context) ([]*IPSetListResponseData, error) {
|
||||
resBody := &IPSetListResponseBody{}
|
||||
|
||||
err := c.DoRequest(ctx, http.MethodGet, c.ipsetPath(), nil, resBody)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error getting IPSet list: %w", err)
|
||||
}
|
||||
|
||||
if resBody.Data == nil {
|
||||
return nil, api.ErrNoDataObjectInResponse
|
||||
}
|
||||
|
||||
sort.Slice(resBody.Data, func(i, j int) bool {
|
||||
return resBody.Data[i].Name < resBody.Data[j].Name
|
||||
})
|
||||
|
||||
return resBody.Data, nil
|
||||
}
|
@ -1,50 +0,0 @@
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package firewall
|
||||
|
||||
import (
|
||||
"github.com/bpg/terraform-provider-proxmox/internal/types"
|
||||
)
|
||||
|
||||
// IPSetListResponseBody contains the data from an IPSet get response.
|
||||
type IPSetListResponseBody struct {
|
||||
Data []*IPSetListResponseData `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
// IPSetCreateRequestBody contains the data for an IPSet create request.
|
||||
type IPSetCreateRequestBody struct {
|
||||
Comment string `json:"comment,omitempty" url:"comment,omitempty"`
|
||||
Name string `json:"name" url:"name"`
|
||||
}
|
||||
|
||||
// IPSetGetResponseBody contains the body from an IPSet get response.
|
||||
type IPSetGetResponseBody struct {
|
||||
Data []*IPSetGetResponseData `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
// IPSetGetResponseData contains the data from an IPSet get response.
|
||||
type IPSetGetResponseData struct {
|
||||
CIDR string `json:"cidr" url:"cidr"`
|
||||
NoMatch *types.CustomBool `json:"nomatch,omitempty" url:"nomatch,omitempty,int"`
|
||||
Comment *string `json:"comment,omitempty" url:"comment,omitempty"`
|
||||
}
|
||||
|
||||
// IPSetUpdateRequestBody contains the data for an IPSet update request.
|
||||
type IPSetUpdateRequestBody struct {
|
||||
ReName string `json:"rename,omitempty" url:"rename,omitempty"`
|
||||
Comment *string `json:"comment,omitempty" url:"comment,omitempty"`
|
||||
Name string `json:"name" url:"name"`
|
||||
}
|
||||
|
||||
// IPSetListResponseData contains list of IPSets from.
|
||||
type IPSetListResponseData struct {
|
||||
Comment *string `json:"comment,omitempty" url:"comment,omitempty"`
|
||||
Name string `json:"name" url:"name"`
|
||||
}
|
||||
|
||||
// IPSetContent is an array of IPSetGetResponseData.
|
||||
type IPSetContent []IPSetGetResponseData
|
@ -1,60 +0,0 @@
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package firewall
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
|
||||
|
||||
"github.com/bpg/terraform-provider-proxmox/proxmox/api"
|
||||
)
|
||||
|
||||
// Options is an interface for the Proxmox firewall options API.
|
||||
type Options interface {
|
||||
GetOptionsID() string
|
||||
SetOptions(ctx context.Context, d *OptionsPutRequestBody) error
|
||||
GetOptions(ctx context.Context) (*OptionsGetResponseData, error)
|
||||
}
|
||||
|
||||
func (c *Client) optionsPath() string {
|
||||
return c.ExpandPath("firewall/options")
|
||||
}
|
||||
|
||||
// GetOptionsID returns the ID of the options object.
|
||||
func (c *Client) GetOptionsID() string {
|
||||
return "options-" + strconv.Itoa(schema.HashString(c.optionsPath()))
|
||||
}
|
||||
|
||||
// SetOptions sets the options object.
|
||||
func (c *Client) SetOptions(ctx context.Context, d *OptionsPutRequestBody) error {
|
||||
err := c.DoRequest(ctx, http.MethodPut, c.optionsPath(), d, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error setting optionss: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetOptions retrieves the options object.
|
||||
func (c *Client) GetOptions(ctx context.Context) (*OptionsGetResponseData, error) {
|
||||
resBody := &OptionsGetResponseBody{}
|
||||
|
||||
err := c.DoRequest(ctx, http.MethodGet, c.optionsPath(), nil, resBody)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error retrieving options: %w", err)
|
||||
}
|
||||
|
||||
if resBody.Data == nil {
|
||||
return nil, api.ErrNoDataObjectInResponse
|
||||
}
|
||||
|
||||
return resBody.Data, nil
|
||||
}
|
@ -1,44 +0,0 @@
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package firewall
|
||||
|
||||
import (
|
||||
"github.com/bpg/terraform-provider-proxmox/internal/types"
|
||||
)
|
||||
|
||||
// OptionsPutRequestBody is the request body for the PUT /cluster/firewall/options API call.
|
||||
type OptionsPutRequestBody struct {
|
||||
DHCP *types.CustomBool `json:"dhcp,omitempty" url:"dhcp,omitempty,int"`
|
||||
Enable *types.CustomBool `json:"enable,omitempty" url:"enable,omitempty,int"`
|
||||
IPFilter *types.CustomBool `json:"ipfilter,omitempty" url:"ipfilter,omitempty,int"`
|
||||
LogLevelIN *string `json:"log_level_in,omitempty" url:"log_level_in,omitempty"`
|
||||
LogLevelOUT *string `json:"log_level_out,omitempty" url:"log_level_out,omitempty"`
|
||||
MACFilter *types.CustomBool `json:"macfilter,omitempty" url:"macfilter,omitempty,int"`
|
||||
NDP *types.CustomBool `json:"ndp,omitempty" url:"ndp,omitempty,int"`
|
||||
PolicyIn *string `json:"policy_in,omitempty" url:"policy_in,omitempty"`
|
||||
PolicyOut *string `json:"policy_out,omitempty" url:"policy_out,omitempty"`
|
||||
RAdv *types.CustomBool `json:"radv,omitempty" url:"radv,omitempty,int"`
|
||||
}
|
||||
|
||||
// OptionsGetResponseBody is the response body for the GET /cluster/firewall/options API call.
|
||||
type OptionsGetResponseBody struct {
|
||||
Data *OptionsGetResponseData `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
// OptionsGetResponseData is the data field of the response body for the GET /cluster/firewall/options API call.
|
||||
type OptionsGetResponseData struct {
|
||||
DHCP *types.CustomBool `json:"dhcp" url:"dhcp,int"`
|
||||
Enable *types.CustomBool `json:"enable" url:"enable,int"`
|
||||
IPFilter *types.CustomBool `json:"ipfilter" url:"ipfilter,int"`
|
||||
LogLevelIN *string `json:"log_level_in" url:"log_level_in"`
|
||||
LogLevelOUT *string `json:"log_level_out" url:"log_level_out"`
|
||||
MACFilter *types.CustomBool `json:"macfilter" url:"macfilter,int"`
|
||||
NDP *types.CustomBool `json:"ndp" url:"ndp,int"`
|
||||
PolicyIn *string `json:"policy_in" url:"policy_in"`
|
||||
PolicyOut *string `json:"policy_out" url:"policy_out"`
|
||||
RAdv *types.CustomBool `json:"radv" url:"radv,int"`
|
||||
}
|
@ -1,120 +0,0 @@
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package firewall
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
|
||||
|
||||
"github.com/bpg/terraform-provider-proxmox/internal/types"
|
||||
"github.com/bpg/terraform-provider-proxmox/proxmox/api"
|
||||
)
|
||||
|
||||
// Rule is an interface for the Proxmox firewall rule API.
|
||||
type Rule interface {
|
||||
GetRulesID() string
|
||||
CreateRule(ctx context.Context, d *RuleCreateRequestBody) error
|
||||
GetRule(ctx context.Context, pos int) (*RuleGetResponseData, error)
|
||||
ListRules(ctx context.Context) ([]*RuleListResponseData, error)
|
||||
UpdateRule(ctx context.Context, pos int, d *RuleUpdateRequestBody) error
|
||||
DeleteRule(ctx context.Context, pos int) error
|
||||
}
|
||||
|
||||
// BaseRule is the base struct for firewall rules.
|
||||
type BaseRule struct {
|
||||
Comment *string `json:"comment,omitempty" url:"comment,omitempty"`
|
||||
Dest *string `json:"dest,omitempty" url:"dest,omitempty"`
|
||||
Digest *string `json:"digest,omitempty" url:"digest,omitempty"`
|
||||
DPort *string `json:"dport,omitempty" url:"dport,omitempty"`
|
||||
Enable *types.CustomBool `json:"enable,omitempty" url:"enable,omitempty,int"`
|
||||
ICMPType *string `json:"icmp-type,omitempty" url:"icmp-type,omitempty"`
|
||||
IFace *string `json:"iface,omitempty" url:"iface,omitempty"`
|
||||
Log *string `json:"log,omitempty" url:"log,omitempty"`
|
||||
Macro *string `json:"macro,omitempty" url:"macro,omitempty"`
|
||||
Proto *string `json:"proto,omitempty" url:"proto,omitempty"`
|
||||
Source *string `json:"source,omitempty" url:"source,omitempty"`
|
||||
SPort *string `json:"sport,omitempty" url:"sport,omitempty"`
|
||||
}
|
||||
|
||||
func (c *Client) rulesPath() string {
|
||||
return c.ExpandPath("firewall/rules")
|
||||
}
|
||||
|
||||
func (c *Client) rulePath(pos int) string {
|
||||
return fmt.Sprintf("%s/%d", c.rulesPath(), pos)
|
||||
}
|
||||
|
||||
// GetRulesID returns the ID of the rules object.
|
||||
func (c *Client) GetRulesID() string {
|
||||
return "rule-" + strconv.Itoa(schema.HashString(c.rulesPath()))
|
||||
}
|
||||
|
||||
// CreateRule creates a firewall rule.
|
||||
func (c *Client) CreateRule(ctx context.Context, d *RuleCreateRequestBody) error {
|
||||
err := c.DoRequest(ctx, http.MethodPost, c.rulesPath(), d, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error creating firewall rule: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetRule retrieves a firewall rule.
|
||||
func (c *Client) GetRule(ctx context.Context, pos int) (*RuleGetResponseData, error) {
|
||||
resBody := &RuleGetResponseBody{}
|
||||
|
||||
err := c.DoRequest(ctx, http.MethodGet, c.rulePath(pos), nil, resBody)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error retrieving firewall rule %d: %w", pos, err)
|
||||
}
|
||||
|
||||
if resBody.Data == nil {
|
||||
return nil, api.ErrNoDataObjectInResponse
|
||||
}
|
||||
|
||||
return resBody.Data, nil
|
||||
}
|
||||
|
||||
// ListRules retrieves a list of firewall rules.
|
||||
func (c *Client) ListRules(ctx context.Context) ([]*RuleListResponseData, error) {
|
||||
resBody := &RuleListResponseBody{}
|
||||
|
||||
err := c.DoRequest(ctx, http.MethodGet, c.rulesPath(), nil, resBody)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error retrieving firewall rules: %w", err)
|
||||
}
|
||||
|
||||
if resBody.Data == nil {
|
||||
return nil, api.ErrNoDataObjectInResponse
|
||||
}
|
||||
|
||||
return resBody.Data, nil
|
||||
}
|
||||
|
||||
// UpdateRule updates a firewall rule.
|
||||
func (c *Client) UpdateRule(ctx context.Context, pos int, d *RuleUpdateRequestBody) error {
|
||||
err := c.DoRequest(ctx, http.MethodPut, c.rulePath(pos), d, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error updating firewall rule %d: %w", pos, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteRule deletes a firewall rule.
|
||||
func (c *Client) DeleteRule(ctx context.Context, pos int) error {
|
||||
err := c.DoRequest(ctx, http.MethodDelete, c.rulePath(pos), nil, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error deleting firewall rule %d: %w", pos, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
@ -1,53 +0,0 @@
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package firewall
|
||||
|
||||
// RuleCreateRequestBody contains the data for a firewall rule create request.
|
||||
type RuleCreateRequestBody struct {
|
||||
BaseRule
|
||||
|
||||
Action string `json:"action" url:"action"`
|
||||
Type string `json:"type" url:"type"`
|
||||
|
||||
Group *string `json:"group,omitempty" url:"group,omitempty"`
|
||||
}
|
||||
|
||||
// RuleGetResponseBody contains the body from a firewall rule get response.
|
||||
type RuleGetResponseBody struct {
|
||||
Data *RuleGetResponseData `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
// RuleGetResponseData contains the data from a firewall rule get response.
|
||||
type RuleGetResponseData struct {
|
||||
BaseRule
|
||||
|
||||
// NOTE: This is `int` in the PVE API docs, but it's actually a string in the response.
|
||||
Pos string `json:"pos" url:"pos"`
|
||||
Action string `json:"action" url:"action"`
|
||||
Type string `json:"type" url:"type"`
|
||||
}
|
||||
|
||||
// RuleListResponseBody contains the data from a firewall rule get response.
|
||||
type RuleListResponseBody struct {
|
||||
Data []*RuleListResponseData `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
// RuleListResponseData contains the data from a firewall rule get response.
|
||||
type RuleListResponseData struct {
|
||||
Pos int `json:"pos" url:"pos"`
|
||||
}
|
||||
|
||||
// RuleUpdateRequestBody contains the data for a firewall rule update request.
|
||||
type RuleUpdateRequestBody struct {
|
||||
BaseRule
|
||||
|
||||
Pos *int `json:"pos,omitempty" url:"pos,omitempty"`
|
||||
Action *string `json:"action,omitempty" url:"action,omitempty"`
|
||||
Type *string `json:"type,omitempty" url:"type,omitempty"`
|
||||
|
||||
Group *string `json:"group,omitempty" url:"group,omitempty"`
|
||||
}
|
@ -1,51 +0,0 @@
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package nodes
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/bpg/terraform-provider-proxmox/proxmox/api"
|
||||
)
|
||||
|
||||
// DeleteCertificate deletes the custom certificate for a node.
|
||||
func (c *Client) DeleteCertificate(ctx context.Context, d *CertificateDeleteRequestBody) error {
|
||||
err := c.DoRequest(ctx, http.MethodDelete, c.ExpandPath("certificates/custom"), d, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error deleting certificate: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ListCertificates retrieves the list of certificates for a node.
|
||||
func (c *Client) ListCertificates(ctx context.Context) (*[]CertificateListResponseData, error) {
|
||||
resBody := &CertificateListResponseBody{}
|
||||
|
||||
err := c.DoRequest(ctx, http.MethodGet, c.ExpandPath("certificates/info"), nil, resBody)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error retrieving certificate list: %w", err)
|
||||
}
|
||||
|
||||
if resBody.Data == nil {
|
||||
return nil, api.ErrNoDataObjectInResponse
|
||||
}
|
||||
|
||||
return resBody.Data, nil
|
||||
}
|
||||
|
||||
// UpdateCertificate updates the custom certificate for a node.
|
||||
func (c *Client) UpdateCertificate(ctx context.Context, d *CertificateUpdateRequestBody) error {
|
||||
err := c.DoRequest(ctx, http.MethodPost, c.ExpandPath("certificates/custom"), d, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error updating certificate: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
@ -1,43 +0,0 @@
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package nodes
|
||||
|
||||
import (
|
||||
"github.com/bpg/terraform-provider-proxmox/internal/types"
|
||||
)
|
||||
|
||||
// CertificateDeleteRequestBody contains the data for a custom certificate delete request.
|
||||
type CertificateDeleteRequestBody struct {
|
||||
Restart *types.CustomBool `json:"restart,omitempty" url:"restart,omitempty,int"`
|
||||
}
|
||||
|
||||
// CertificateListResponseBody contains the body from a certificate list response.
|
||||
type CertificateListResponseBody struct {
|
||||
Data *[]CertificateListResponseData `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
// CertificateListResponseData contains the data from a certificate list response.
|
||||
type CertificateListResponseData struct {
|
||||
Certificates *string `json:"pem,omitempty"`
|
||||
FileName *string `json:"filename,omitempty"`
|
||||
Fingerprint *string `json:"fingerprint,omitempty"`
|
||||
Issuer *string `json:"issuer,omitempty"`
|
||||
NotAfter *types.CustomTimestamp `json:"notafter,omitempty"`
|
||||
NotBefore *types.CustomTimestamp `json:"notbefore,omitempty"`
|
||||
PublicKeyBits *int `json:"public-key-bits,omitempty"`
|
||||
PublicKeyType *string `json:"public-key-type,omitempty"`
|
||||
Subject *string `json:"subject,omitempty"`
|
||||
SubjectAlternativeNames *[]string `json:"san,omitempty"`
|
||||
}
|
||||
|
||||
// CertificateUpdateRequestBody contains the body for a custom certificate update request.
|
||||
type CertificateUpdateRequestBody struct {
|
||||
Certificates string `json:"certificates" url:"certificates"`
|
||||
Force *types.CustomBool `json:"force,omitempty" url:"force,omitempty,int"`
|
||||
PrivateKey *string `json:"key,omitempty" url:"key,omitempty"`
|
||||
Restart *types.CustomBool `json:"restart,omitempty" url:"restart,omitempty,int"`
|
||||
}
|
@ -1,51 +0,0 @@
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package nodes
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
|
||||
"github.com/bpg/terraform-provider-proxmox/proxmox/api"
|
||||
"github.com/bpg/terraform-provider-proxmox/proxmox/nodes/containers"
|
||||
"github.com/bpg/terraform-provider-proxmox/proxmox/nodes/tasks"
|
||||
"github.com/bpg/terraform-provider-proxmox/proxmox/nodes/vms"
|
||||
)
|
||||
|
||||
// Client is an interface for accessing the Proxmox node API.
|
||||
type Client struct {
|
||||
api.Client
|
||||
NodeName string
|
||||
}
|
||||
|
||||
// ExpandPath expands a relative path to a full node API path.
|
||||
func (c *Client) ExpandPath(path string) string {
|
||||
return fmt.Sprintf("nodes/%s/%s", url.PathEscape(c.NodeName), path)
|
||||
}
|
||||
|
||||
// Container returns a client for managing a specific container.
|
||||
func (c *Client) Container(vmID int) *containers.Client {
|
||||
return &containers.Client{
|
||||
Client: c,
|
||||
VMID: vmID,
|
||||
}
|
||||
}
|
||||
|
||||
// VM returns a client for managing a specific VM.
|
||||
func (c *Client) VM(vmID int) *vms.Client {
|
||||
return &vms.Client{
|
||||
Client: c,
|
||||
VMID: vmID,
|
||||
}
|
||||
}
|
||||
|
||||
// Tasks returns a client for managing VM tasks.
|
||||
func (c *Client) Tasks() *tasks.Client {
|
||||
return &tasks.Client{
|
||||
Client: c,
|
||||
}
|
||||
}
|
@ -1,42 +0,0 @@
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package containers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/bpg/terraform-provider-proxmox/proxmox/api"
|
||||
"github.com/bpg/terraform-provider-proxmox/proxmox/firewall"
|
||||
containerfirewall "github.com/bpg/terraform-provider-proxmox/proxmox/nodes/containers/firewall"
|
||||
)
|
||||
|
||||
// Client is an interface for accessing the Proxmox container API.
|
||||
type Client struct {
|
||||
api.Client
|
||||
VMID int
|
||||
}
|
||||
|
||||
func (c *Client) basePath() string {
|
||||
return c.Client.ExpandPath("lxc")
|
||||
}
|
||||
|
||||
// ExpandPath expands a relative path to a full container API path.
|
||||
func (c *Client) ExpandPath(path string) string {
|
||||
ep := fmt.Sprintf("%s/%d", c.basePath(), c.VMID)
|
||||
if path != "" {
|
||||
ep = fmt.Sprintf("%s/%s", ep, path)
|
||||
}
|
||||
|
||||
return ep
|
||||
}
|
||||
|
||||
// Firewall returns a client for managing the container firewall.
|
||||
func (c *Client) Firewall() firewall.API {
|
||||
return &containerfirewall.Client{
|
||||
Client: firewall.Client{Client: c},
|
||||
}
|
||||
}
|
@ -1,202 +0,0 @@
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package containers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/bpg/terraform-provider-proxmox/proxmox/api"
|
||||
)
|
||||
|
||||
// CloneContainer clones a container.
|
||||
func (c *Client) CloneContainer(ctx context.Context, d *CloneRequestBody) error {
|
||||
err := c.DoRequest(ctx, http.MethodPost, c.ExpandPath("/clone"), d, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error cloning container: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateContainer creates a container.
|
||||
func (c *Client) CreateContainer(ctx context.Context, d *CreateRequestBody) error {
|
||||
err := c.DoRequest(ctx, http.MethodPost, c.basePath(), d, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error creating container: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteContainer deletes a container.
|
||||
func (c *Client) DeleteContainer(ctx context.Context) error {
|
||||
err := c.DoRequest(ctx, http.MethodDelete, c.ExpandPath(""), nil, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error deleting container: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetContainer retrieves a container.
|
||||
func (c *Client) GetContainer(ctx context.Context) (*GetResponseData, error) {
|
||||
resBody := &GetResponseBody{}
|
||||
|
||||
err := c.DoRequest(ctx, http.MethodGet, c.ExpandPath("config"), nil, resBody)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error retrieving container: %w", err)
|
||||
}
|
||||
|
||||
if resBody.Data == nil {
|
||||
return nil, api.ErrNoDataObjectInResponse
|
||||
}
|
||||
|
||||
return resBody.Data, nil
|
||||
}
|
||||
|
||||
// GetContainerStatus retrieves the status for a container.
|
||||
func (c *Client) GetContainerStatus(ctx context.Context) (*GetStatusResponseData, error) {
|
||||
resBody := &GetStatusResponseBody{}
|
||||
|
||||
err := c.DoRequest(ctx, http.MethodGet, c.ExpandPath("status/current"), nil, resBody)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error retrieving container status: %w", err)
|
||||
}
|
||||
|
||||
if resBody.Data == nil {
|
||||
return nil, api.ErrNoDataObjectInResponse
|
||||
}
|
||||
|
||||
return resBody.Data, nil
|
||||
}
|
||||
|
||||
// RebootContainer reboots a container.
|
||||
func (c *Client) RebootContainer(ctx context.Context, d *RebootRequestBody) error {
|
||||
err := c.DoRequest(ctx, http.MethodPost, c.ExpandPath("status/reboot"), d, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error rebooting container: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ShutdownContainer shuts down a container.
|
||||
func (c *Client) ShutdownContainer(ctx context.Context, d *ShutdownRequestBody) error {
|
||||
err := c.DoRequest(ctx, http.MethodPost, c.ExpandPath("status/shutdown"), d, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error shutting down container: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// StartContainer starts a container.
|
||||
func (c *Client) StartContainer(ctx context.Context) error {
|
||||
err := c.DoRequest(ctx, http.MethodPost, c.ExpandPath("status/start"), nil, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error starting container: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// StopContainer stops a container immediately.
|
||||
func (c *Client) StopContainer(ctx context.Context) error {
|
||||
err := c.DoRequest(ctx, http.MethodPost, c.ExpandPath("status/stop"), nil, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error stopping container: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateContainer updates a container.
|
||||
func (c *Client) UpdateContainer(ctx context.Context, d *UpdateRequestBody) error {
|
||||
err := c.DoRequest(ctx, http.MethodPut, c.ExpandPath("config"), d, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error updating container: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// WaitForContainerState waits for a container to reach a specific state.
|
||||
func (c *Client) WaitForContainerState(ctx context.Context, state string, timeout int, delay int) error {
|
||||
state = strings.ToLower(state)
|
||||
|
||||
timeDelay := int64(delay)
|
||||
timeMax := float64(timeout)
|
||||
timeStart := time.Now()
|
||||
timeElapsed := timeStart.Sub(timeStart)
|
||||
|
||||
for timeElapsed.Seconds() < timeMax {
|
||||
if int64(timeElapsed.Seconds())%timeDelay == 0 {
|
||||
data, err := c.GetContainerStatus(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error retrieving container status: %w", err)
|
||||
}
|
||||
|
||||
if data.Status == state {
|
||||
return nil
|
||||
}
|
||||
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
|
||||
timeElapsed = time.Since(timeStart)
|
||||
|
||||
if ctx.Err() != nil {
|
||||
return fmt.Errorf("context error: %w", ctx.Err())
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Errorf(
|
||||
"timeout while waiting for container \"%d\" to enter the state \"%s\"",
|
||||
c.VMID,
|
||||
state,
|
||||
)
|
||||
}
|
||||
|
||||
// WaitForContainerLock waits for a container lock to be released.
|
||||
func (c *Client) WaitForContainerLock(ctx context.Context, timeout int, delay int, ignoreErrorResponse bool) error {
|
||||
timeDelay := int64(delay)
|
||||
timeMax := float64(timeout)
|
||||
timeStart := time.Now()
|
||||
timeElapsed := timeStart.Sub(timeStart)
|
||||
|
||||
for timeElapsed.Seconds() < timeMax {
|
||||
if int64(timeElapsed.Seconds())%timeDelay == 0 {
|
||||
data, err := c.GetContainerStatus(ctx)
|
||||
|
||||
if err != nil {
|
||||
if !ignoreErrorResponse {
|
||||
return fmt.Errorf("error retrieving container status: %w", err)
|
||||
}
|
||||
} else if data.Lock == nil || *data.Lock == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
|
||||
timeElapsed = time.Since(timeStart)
|
||||
|
||||
if ctx.Err() != nil {
|
||||
return fmt.Errorf("context error: %w", ctx.Err())
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Errorf("timeout while waiting for container \"%d\" to become unlocked", c.VMID)
|
||||
}
|
@ -1,811 +0,0 @@
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package containers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/bpg/terraform-provider-proxmox/internal/types"
|
||||
)
|
||||
|
||||
// CloneRequestBody contains the data for an container clone request.
|
||||
type CloneRequestBody struct {
|
||||
BandwidthLimit *int `json:"bwlimit,omitempty" url:"bwlimit,omitempty"`
|
||||
Description *string `json:"description,omitempty" url:"description,omitempty"`
|
||||
FullCopy *types.CustomBool `json:"full,omitempty" url:"full,omitempty,int"`
|
||||
Hostname *string `json:"hostname,omitempty" url:"hostname,omitempty"`
|
||||
PoolID *string `json:"pool,omitempty" url:"pool,omitempty"`
|
||||
SnapshotName *string `json:"snapname,omitempty" url:"snapname,omitempty"`
|
||||
TargetNodeName *string `json:"target,omitempty" url:"target,omitempty"`
|
||||
TargetStorage *string `json:"storage,omitempty" url:"storage,omitempty"`
|
||||
VMIDNew int `json:"newid" url:"newid"`
|
||||
}
|
||||
|
||||
// CreateRequestBody contains the data for a user create request.
|
||||
type CreateRequestBody struct {
|
||||
BandwidthLimit *float64 `json:"bwlimit,omitempty" url:"bwlimit,omitempty"`
|
||||
ConsoleEnabled *types.CustomBool `json:"console,omitempty" url:"console,omitempty,int"`
|
||||
ConsoleMode *string `json:"cmode,omitempty" url:"cmode,omitempty"`
|
||||
CPUArchitecture *string `json:"arch,omitempty" url:"arch,omitempty"`
|
||||
CPUCores *int `json:"cores,omitempty" url:"cores,omitempty"`
|
||||
CPULimit *int `json:"cpulimit,omitempty" url:"cpulimit,omitempty"`
|
||||
CPUUnits *int `json:"cpuunits,omitempty" url:"cpuunits,omitempty"`
|
||||
DatastoreID *string `json:"storage,omitempty" url:"storage,omitempty"`
|
||||
DedicatedMemory *int `json:"memory,omitempty" url:"memory,omitempty"`
|
||||
Delete []string `json:"delete,omitempty" url:"delete,omitempty"`
|
||||
Description *string `json:"description,omitempty" url:"description,omitempty"`
|
||||
DNSDomain *string `json:"searchdomain,omitempty" url:"searchdomain,omitempty"`
|
||||
DNSServer *string `json:"nameserver,omitempty" url:"nameserver,omitempty"`
|
||||
Features *CustomFeatures `json:"features,omitempty" url:"features,omitempty"`
|
||||
Force *types.CustomBool `json:"force,omitempty" url:"force,omitempty,int"`
|
||||
HookScript *string `json:"hookscript,omitempty" url:"hookscript,omitempty"`
|
||||
Hostname *string `json:"hostname,omitempty" url:"hostname,omitempty"`
|
||||
IgnoreUnpackErrors *types.CustomBool `json:"ignore-unpack-errors,omitempty" url:"force,omitempty,int"`
|
||||
Lock *string `json:"lock,omitempty" url:"lock,omitempty,int"`
|
||||
MountPoints CustomMountPointArray `json:"mp,omitempty" url:"mp,omitempty,numbered"`
|
||||
NetworkInterfaces CustomNetworkInterfaceArray `json:"net,omitempty" url:"net,omitempty,numbered"`
|
||||
OSTemplateFileVolume *string `json:"ostemplate,omitempty" url:"ostemplate,omitempty"`
|
||||
OSType *string `json:"ostype,omitempty" url:"ostype,omitempty"`
|
||||
Password *string `json:"password,omitempty" url:"password,omitempty"`
|
||||
PoolID *string `json:"pool,omitempty" url:"pool,omitempty"`
|
||||
Protection *types.CustomBool `json:"protection,omitempty" url:"protection,omitempty,int"`
|
||||
Restore *types.CustomBool `json:"restore,omitempty" url:"restore,omitempty,int"`
|
||||
RootFS *CustomRootFS `json:"rootfs,omitempty" url:"rootfs,omitempty"`
|
||||
SSHKeys *CustomSSHKeys `json:"ssh-public-keys,omitempty" url:"ssh-public-keys,omitempty"`
|
||||
Start *types.CustomBool `json:"start,omitempty" url:"start,omitempty,int"`
|
||||
StartOnBoot *types.CustomBool `json:"onboot,omitempty" url:"onboot,omitempty,int"`
|
||||
StartupBehavior *CustomStartupBehavior `json:"startup,omitempty" url:"startup,omitempty"`
|
||||
Swap *int `json:"swap,omitempty" url:"swap,omitempty"`
|
||||
Tags *string `json:"tags,omitempty" url:"tags,omitempty"`
|
||||
Template *types.CustomBool `json:"template,omitempty" url:"template,omitempty,int"`
|
||||
TTY *int `json:"tty,omitempty" url:"tty,omitempty"`
|
||||
Unique *types.CustomBool `json:"unique,omitempty" url:"unique,omitempty,int"`
|
||||
Unprivileged *types.CustomBool `json:"unprivileged,omitempty" url:"unprivileged,omitempty,int"`
|
||||
VMID *int `json:"vmid,omitempty" url:"vmid,omitempty"`
|
||||
}
|
||||
|
||||
// CustomFeatures contains the values for the "features" property.
|
||||
type CustomFeatures struct {
|
||||
FUSE *types.CustomBool `json:"fuse,omitempty" url:"fuse,omitempty,int"`
|
||||
KeyControl *types.CustomBool `json:"keyctl,omitempty" url:"keyctl,omitempty,int"`
|
||||
MountTypes *[]string `json:"mount,omitempty" url:"mount,omitempty"`
|
||||
Nesting *types.CustomBool `json:"nesting,omitempty" url:"nesting,omitempty,int"`
|
||||
}
|
||||
|
||||
// CustomMountPoint contains the values for the "mp[n]" properties.
|
||||
type CustomMountPoint struct {
|
||||
ACL *types.CustomBool `json:"acl,omitempty" url:"acl,omitempty,int"`
|
||||
Backup *types.CustomBool `json:"backup,omitempty" url:"backup,omitempty,int"`
|
||||
DiskSize *string `json:"size,omitempty" url:"size,omitempty"`
|
||||
Enabled bool `json:"-" url:"-"`
|
||||
MountOptions *[]string `json:"mountoptions,omitempty" url:"mountoptions,omitempty"`
|
||||
MountPoint string `json:"mp" url:"mp"`
|
||||
Quota *types.CustomBool `json:"quota,omitempty" url:"quota,omitempty,int"`
|
||||
ReadOnly *types.CustomBool `json:"ro,omitempty" url:"ro,omitempty,int"`
|
||||
Replicate *types.CustomBool `json:"replicate,omitempty" url:"replicate,omitempty,int"`
|
||||
Shared *types.CustomBool `json:"shared,omitempty" url:"shared,omitempty,int"`
|
||||
Volume string `json:"volume" url:"volume"`
|
||||
}
|
||||
|
||||
// CustomMountPointArray is an array of CustomMountPoint.
|
||||
type CustomMountPointArray []CustomMountPoint
|
||||
|
||||
// CustomNetworkInterface contains the values for the "net[n]" properties.
|
||||
type CustomNetworkInterface struct {
|
||||
Bridge *string `json:"bridge,omitempty" url:"bridge,omitempty"`
|
||||
Enabled bool `json:"-" url:"-"`
|
||||
Firewall *types.CustomBool `json:"firewall,omitempty" url:"firewall,omitempty,int"`
|
||||
IPv4Address *string `json:"ip,omitempty" url:"ip,omitempty"`
|
||||
IPv4Gateway *string `json:"gw,omitempty" url:"gw,omitempty"`
|
||||
IPv6Address *string `json:"ip6,omitempty" url:"ip6,omitempty"`
|
||||
IPv6Gateway *string `json:"gw6,omitempty" url:"gw6,omitempty"`
|
||||
MACAddress *string `json:"hwaddr,omitempty" url:"hwaddr,omitempty"`
|
||||
MTU *int `json:"mtu,omitempty" url:"mtu,omitempty"`
|
||||
Name string `json:"name" url:"name"`
|
||||
RateLimit *float64 `json:"rate,omitempty" url:"rate,omitempty"`
|
||||
Tag *int `json:"tag,omitempty" url:"tag,omitempty"`
|
||||
Trunks *[]int `json:"trunks,omitempty" url:"trunks,omitempty"`
|
||||
Type *string `json:"type,omitempty" url:"type,omitempty"`
|
||||
}
|
||||
|
||||
// CustomNetworkInterfaceArray is an array of CustomNetworkInterface.
|
||||
type CustomNetworkInterfaceArray []CustomNetworkInterface
|
||||
|
||||
// CustomRootFS contains the values for the "rootfs" property.
|
||||
type CustomRootFS struct {
|
||||
ACL *types.CustomBool `json:"acl,omitempty" url:"acl,omitempty,int"`
|
||||
Size *types.DiskSize `json:"size,omitempty" url:"size,omitempty"`
|
||||
MountOptions *[]string `json:"mountoptions,omitempty" url:"mountoptions,omitempty"`
|
||||
Quota *types.CustomBool `json:"quota,omitempty" url:"quota,omitempty,int"`
|
||||
ReadOnly *types.CustomBool `json:"ro,omitempty" url:"ro,omitempty,int"`
|
||||
Replicate *types.CustomBool `json:"replicate,omitempty" url:"replicate,omitempty,int"`
|
||||
Shared *types.CustomBool `json:"shared,omitempty" url:"shared,omitempty,int"`
|
||||
Volume string `json:"volume" url:"volume"`
|
||||
}
|
||||
|
||||
// CustomSSHKeys contains the values for the "ssh-public-keys" property.
|
||||
type CustomSSHKeys []string
|
||||
|
||||
// CustomStartupBehavior contains the values for the "startup" property.
|
||||
type CustomStartupBehavior struct {
|
||||
Down *int `json:"down,omitempty" url:"down,omitempty"`
|
||||
Order *int `json:"order,omitempty" url:"order,omitempty"`
|
||||
Up *int `json:"up,omitempty" url:"up,omitempty"`
|
||||
}
|
||||
|
||||
// GetResponseBody contains the body from a user get response.
|
||||
type GetResponseBody struct {
|
||||
Data *GetResponseData `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
// GetResponseData contains the data from a user get response.
|
||||
type GetResponseData struct {
|
||||
ConsoleEnabled *types.CustomBool `json:"console,omitempty"`
|
||||
ConsoleMode *string `json:"cmode,omitempty"`
|
||||
CPUArchitecture *string `json:"arch,omitempty"`
|
||||
CPUCores *int `json:"cores,omitempty"`
|
||||
CPULimit *int `json:"cpulimit,omitempty"`
|
||||
CPUUnits *int `json:"cpuunits,omitempty"`
|
||||
DedicatedMemory *int `json:"memory,omitempty"`
|
||||
Description *string `json:"description,omitempty"`
|
||||
Digest string `json:"digest"`
|
||||
DNSDomain *string `json:"searchdomain,omitempty"`
|
||||
DNSServer *string `json:"nameserver,omitempty"`
|
||||
Features *CustomFeatures `json:"features,omitempty"`
|
||||
HookScript *string `json:"hookscript,omitempty"`
|
||||
Hostname *string `json:"hostname,omitempty"`
|
||||
Lock *types.CustomBool `json:"lock,omitempty"`
|
||||
LXCConfiguration *[][2]string `json:"lxc,omitempty"`
|
||||
MountPoint0 CustomMountPoint `json:"mp0,omitempty"`
|
||||
MountPoint1 CustomMountPoint `json:"mp1,omitempty"`
|
||||
MountPoint2 CustomMountPoint `json:"mp2,omitempty"`
|
||||
MountPoint3 CustomMountPoint `json:"mp3,omitempty"`
|
||||
NetworkInterface0 *CustomNetworkInterface `json:"net0,omitempty"`
|
||||
NetworkInterface1 *CustomNetworkInterface `json:"net1,omitempty"`
|
||||
NetworkInterface2 *CustomNetworkInterface `json:"net2,omitempty"`
|
||||
NetworkInterface3 *CustomNetworkInterface `json:"net3,omitempty"`
|
||||
NetworkInterface4 *CustomNetworkInterface `json:"net4,omitempty"`
|
||||
NetworkInterface5 *CustomNetworkInterface `json:"net5,omitempty"`
|
||||
NetworkInterface6 *CustomNetworkInterface `json:"net6,omitempty"`
|
||||
NetworkInterface7 *CustomNetworkInterface `json:"net7,omitempty"`
|
||||
OSType *string `json:"ostype,omitempty"`
|
||||
Protection *types.CustomBool `json:"protection,omitempty"`
|
||||
RootFS *CustomRootFS `json:"rootfs,omitempty"`
|
||||
StartOnBoot *types.CustomBool `json:"onboot,omitempty"`
|
||||
StartupBehavior *CustomStartupBehavior `json:"startup,omitempty"`
|
||||
Swap *int `json:"swap,omitempty"`
|
||||
Tags *string `json:"tags,omitempty"`
|
||||
Template *types.CustomBool `json:"template,omitempty"`
|
||||
TTY *int `json:"tty,omitempty"`
|
||||
Unprivileged *types.CustomBool `json:"unprivileged,omitempty"`
|
||||
}
|
||||
|
||||
// GetStatusResponseBody contains the body from a container get status response.
|
||||
type GetStatusResponseBody struct {
|
||||
Data *GetStatusResponseData `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
// GetStatusResponseData contains the data from a container get status response.
|
||||
type GetStatusResponseData struct {
|
||||
CPUCount *float64 `json:"cpus,omitempty"`
|
||||
Lock *string `json:"lock,omitempty"`
|
||||
MemoryAllocation *int `json:"maxmem,omitempty"`
|
||||
Name *string `json:"name,omitempty"`
|
||||
RootDiskSize *interface{} `json:"maxdisk,omitempty"`
|
||||
Status string `json:"status,omitempty"`
|
||||
SwapAllocation *int `json:"maxswap,omitempty"`
|
||||
Tags *string `json:"tags,omitempty"`
|
||||
Uptime *int `json:"uptime,omitempty"`
|
||||
VMID *int `json:"vmid,omitempty"`
|
||||
}
|
||||
|
||||
// RebootRequestBody contains the body for a container reboot request.
|
||||
type RebootRequestBody struct {
|
||||
Timeout *int `json:"timeout,omitempty" url:"timeout,omitempty"`
|
||||
}
|
||||
|
||||
// ShutdownRequestBody contains the body for a container shutdown request.
|
||||
type ShutdownRequestBody struct {
|
||||
ForceStop *types.CustomBool `json:"forceStop,omitempty" url:"forceStop,omitempty,int"`
|
||||
Timeout *int `json:"timeout,omitempty" url:"timeout,omitempty"`
|
||||
}
|
||||
|
||||
// UpdateRequestBody contains the data for an user update request.
|
||||
type UpdateRequestBody CreateRequestBody
|
||||
|
||||
// EncodeValues converts a ContainerCustomFeatures struct to a URL value.
|
||||
func (r *CustomFeatures) EncodeValues(key string, v *url.Values) error {
|
||||
var values []string
|
||||
|
||||
if r.FUSE != nil {
|
||||
if *r.FUSE {
|
||||
values = append(values, "fuse=1")
|
||||
} else {
|
||||
values = append(values, "fuse=0")
|
||||
}
|
||||
}
|
||||
|
||||
if r.KeyControl != nil {
|
||||
if *r.KeyControl {
|
||||
values = append(values, "keyctl=1")
|
||||
} else {
|
||||
values = append(values, "keyctl=0")
|
||||
}
|
||||
}
|
||||
|
||||
if r.MountTypes != nil {
|
||||
if len(*r.MountTypes) > 0 {
|
||||
values = append(values, fmt.Sprintf("mount=%s", strings.Join(*r.MountTypes, ";")))
|
||||
}
|
||||
}
|
||||
|
||||
if r.Nesting != nil {
|
||||
if *r.Nesting {
|
||||
values = append(values, "nesting=1")
|
||||
} else {
|
||||
values = append(values, "nesting=0")
|
||||
}
|
||||
}
|
||||
|
||||
if len(values) > 0 {
|
||||
v.Add(key, strings.Join(values, ","))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// EncodeValues converts a CustomMountPoint struct to a URL value.
|
||||
func (r *CustomMountPoint) EncodeValues(key string, v *url.Values) error {
|
||||
var values []string
|
||||
|
||||
if r.ACL != nil {
|
||||
if *r.ACL {
|
||||
values = append(values, "acl=%d")
|
||||
} else {
|
||||
values = append(values, "acl=0")
|
||||
}
|
||||
}
|
||||
|
||||
if r.Backup != nil {
|
||||
if *r.Backup {
|
||||
values = append(values, "backup=1")
|
||||
} else {
|
||||
values = append(values, "backup=0")
|
||||
}
|
||||
}
|
||||
|
||||
if r.DiskSize != nil {
|
||||
values = append(values, fmt.Sprintf("size=%s", *r.DiskSize))
|
||||
}
|
||||
|
||||
if r.MountOptions != nil {
|
||||
if len(*r.MountOptions) > 0 {
|
||||
values = append(values, fmt.Sprintf("mount=%s", strings.Join(*r.MountOptions, ";")))
|
||||
}
|
||||
}
|
||||
|
||||
values = append(values, fmt.Sprintf("mp=%s", r.MountPoint))
|
||||
|
||||
if r.Quota != nil {
|
||||
if *r.Quota {
|
||||
values = append(values, "quota=1")
|
||||
} else {
|
||||
values = append(values, "quota=0")
|
||||
}
|
||||
}
|
||||
|
||||
if r.ReadOnly != nil {
|
||||
if *r.ReadOnly {
|
||||
values = append(values, "ro=1")
|
||||
} else {
|
||||
values = append(values, "ro=0")
|
||||
}
|
||||
}
|
||||
|
||||
if r.Replicate != nil {
|
||||
if *r.ReadOnly {
|
||||
values = append(values, "replicate=1")
|
||||
} else {
|
||||
values = append(values, "replicate=0")
|
||||
}
|
||||
}
|
||||
|
||||
if r.Shared != nil {
|
||||
if *r.Shared {
|
||||
values = append(values, "shared=1")
|
||||
} else {
|
||||
values = append(values, "shared=0")
|
||||
}
|
||||
}
|
||||
|
||||
values = append(values, fmt.Sprintf("volume=%s", r.Volume))
|
||||
|
||||
if len(values) > 0 {
|
||||
v.Add(key, strings.Join(values, ","))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// EncodeValues converts a CustomMountPointArray array to multiple URL values.
|
||||
func (r CustomMountPointArray) EncodeValues(
|
||||
key string,
|
||||
v *url.Values,
|
||||
) error {
|
||||
for i, d := range r {
|
||||
if err := d.EncodeValues(fmt.Sprintf("%s%d", key, i), v); err != nil {
|
||||
return fmt.Errorf("failed to encode CustomMountPointArray: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// EncodeValues converts a CustomNetworkInterface struct to a URL value.
|
||||
func (r *CustomNetworkInterface) EncodeValues(
|
||||
key string,
|
||||
v *url.Values,
|
||||
) error {
|
||||
var values []string
|
||||
|
||||
if r.Bridge != nil {
|
||||
values = append(values, fmt.Sprintf("bridge=%s", *r.Bridge))
|
||||
}
|
||||
|
||||
if r.Firewall != nil {
|
||||
if *r.Firewall {
|
||||
values = append(values, "firewall=1")
|
||||
} else {
|
||||
values = append(values, "firewall=0")
|
||||
}
|
||||
}
|
||||
|
||||
if r.IPv4Address != nil {
|
||||
values = append(values, fmt.Sprintf("ip=%s", *r.IPv4Address))
|
||||
}
|
||||
|
||||
if r.IPv4Gateway != nil {
|
||||
values = append(values, fmt.Sprintf("gw=%s", *r.IPv4Gateway))
|
||||
}
|
||||
|
||||
if r.IPv6Address != nil {
|
||||
values = append(values, fmt.Sprintf("ip6=%s", *r.IPv6Address))
|
||||
}
|
||||
|
||||
if r.IPv6Gateway != nil {
|
||||
values = append(values, fmt.Sprintf("gw6=%s", *r.IPv6Gateway))
|
||||
}
|
||||
|
||||
if r.MACAddress != nil {
|
||||
values = append(values, fmt.Sprintf("hwaddr=%s", *r.MACAddress))
|
||||
}
|
||||
|
||||
if r.MTU != nil {
|
||||
values = append(values, fmt.Sprintf("mtu=%d", *r.MTU))
|
||||
}
|
||||
|
||||
values = append(values, fmt.Sprintf("name=%s", r.Name))
|
||||
|
||||
if r.RateLimit != nil {
|
||||
values = append(values, fmt.Sprintf("rate=%.2f", *r.RateLimit))
|
||||
}
|
||||
|
||||
if r.Tag != nil {
|
||||
values = append(values, fmt.Sprintf("tag=%d", *r.Tag))
|
||||
}
|
||||
|
||||
if r.Trunks != nil && len(*r.Trunks) > 0 {
|
||||
sTrunks := make([]string, len(*r.Trunks))
|
||||
|
||||
for i, v := range *r.Trunks {
|
||||
sTrunks[i] = strconv.Itoa(v)
|
||||
}
|
||||
|
||||
values = append(values, fmt.Sprintf("trunks=%s", strings.Join(sTrunks, ";")))
|
||||
}
|
||||
|
||||
if r.Type != nil {
|
||||
values = append(values, fmt.Sprintf("type=%s", *r.Type))
|
||||
}
|
||||
|
||||
if len(values) > 0 {
|
||||
v.Add(key, strings.Join(values, ","))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// EncodeValues converts a CustomNetworkInterfaceArray array to multiple URL values.
|
||||
func (r CustomNetworkInterfaceArray) EncodeValues(
|
||||
key string,
|
||||
v *url.Values,
|
||||
) error {
|
||||
for i, d := range r {
|
||||
if err := d.EncodeValues(fmt.Sprintf("%s%d", key, i), v); err != nil {
|
||||
return fmt.Errorf("failed to encode CustomNetworkInterfaceArray: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// EncodeValues converts a CustomRootFS struct to a URL value.
|
||||
func (r *CustomRootFS) EncodeValues(key string, v *url.Values) error {
|
||||
var values []string
|
||||
|
||||
if r.ACL != nil {
|
||||
if *r.ACL {
|
||||
values = append(values, "acl=%d")
|
||||
} else {
|
||||
values = append(values, "acl=0")
|
||||
}
|
||||
}
|
||||
|
||||
if r.Size != nil {
|
||||
values = append(values, fmt.Sprintf("size=%s", *r.Size))
|
||||
}
|
||||
|
||||
if r.MountOptions != nil {
|
||||
if len(*r.MountOptions) > 0 {
|
||||
values = append(values, fmt.Sprintf("mount=%s", strings.Join(*r.MountOptions, ";")))
|
||||
}
|
||||
}
|
||||
|
||||
if r.Quota != nil {
|
||||
if *r.Quota {
|
||||
values = append(values, "quota=1")
|
||||
} else {
|
||||
values = append(values, "quota=0")
|
||||
}
|
||||
}
|
||||
|
||||
if r.ReadOnly != nil {
|
||||
if *r.ReadOnly {
|
||||
values = append(values, "ro=1")
|
||||
} else {
|
||||
values = append(values, "ro=0")
|
||||
}
|
||||
}
|
||||
|
||||
if r.Replicate != nil {
|
||||
if *r.ReadOnly {
|
||||
values = append(values, "replicate=1")
|
||||
} else {
|
||||
values = append(values, "replicate=0")
|
||||
}
|
||||
}
|
||||
|
||||
if r.Shared != nil {
|
||||
if *r.Shared {
|
||||
values = append(values, "shared=1")
|
||||
} else {
|
||||
values = append(values, "shared=0")
|
||||
}
|
||||
}
|
||||
|
||||
values = append(values, fmt.Sprintf("volume=%s", r.Volume))
|
||||
|
||||
if len(values) > 0 {
|
||||
v.Add(key, strings.Join(values, ","))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// EncodeValues converts a CustomSSHKeys array to a URL value.
|
||||
func (r CustomSSHKeys) EncodeValues(key string, v *url.Values) error {
|
||||
v.Add(key, strings.Join(r, "\n"))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// EncodeValues converts a CustomStartupBehavior struct to a URL value.
|
||||
func (r *CustomStartupBehavior) EncodeValues(
|
||||
key string,
|
||||
v *url.Values,
|
||||
) error {
|
||||
var values []string
|
||||
|
||||
if r.Down != nil {
|
||||
values = append(values, fmt.Sprintf("down=%d", *r.Down))
|
||||
}
|
||||
|
||||
if r.Order != nil {
|
||||
values = append(values, fmt.Sprintf("order=%d", *r.Order))
|
||||
}
|
||||
|
||||
if r.Up != nil {
|
||||
values = append(values, fmt.Sprintf("up=%d", *r.Up))
|
||||
}
|
||||
|
||||
if len(values) > 0 {
|
||||
v.Add(key, strings.Join(values, ","))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnmarshalJSON converts a ContainerCustomFeatures string to an object.
|
||||
func (r *CustomFeatures) UnmarshalJSON(b []byte) error {
|
||||
var s string
|
||||
|
||||
err := json.Unmarshal(b, &s)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to unmarshal ContainerCustomFeatures: %w", err)
|
||||
}
|
||||
|
||||
pairs := strings.Split(s, ",")
|
||||
|
||||
for _, p := range pairs {
|
||||
v := strings.Split(strings.TrimSpace(p), "=")
|
||||
|
||||
if len(v) == 2 {
|
||||
switch v[0] {
|
||||
case "fuse":
|
||||
bv := types.CustomBool(v[1] == "1")
|
||||
r.FUSE = &bv
|
||||
case "keyctl":
|
||||
bv := types.CustomBool(v[1] == "1")
|
||||
r.KeyControl = &bv
|
||||
case "mount":
|
||||
if v[1] != "" {
|
||||
a := strings.Split(v[1], ";")
|
||||
r.MountTypes = &a
|
||||
} else {
|
||||
var a []string
|
||||
r.MountTypes = &a
|
||||
}
|
||||
case "nesting":
|
||||
bv := types.CustomBool(v[1] == "1")
|
||||
r.Nesting = &bv
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnmarshalJSON converts a CustomMountPoint string to an object.
|
||||
func (r *CustomMountPoint) UnmarshalJSON(b []byte) error {
|
||||
var s string
|
||||
|
||||
err := json.Unmarshal(b, &s)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to unmarshal CustomMountPoint: %w", err)
|
||||
}
|
||||
|
||||
pairs := strings.Split(s, ",")
|
||||
|
||||
for _, p := range pairs {
|
||||
v := strings.Split(strings.TrimSpace(p), "=")
|
||||
|
||||
if len(v) == 1 {
|
||||
r.Volume = v[0]
|
||||
} else if len(v) == 2 {
|
||||
switch v[0] {
|
||||
case "acl":
|
||||
bv := types.CustomBool(v[1] == "1")
|
||||
r.ACL = &bv
|
||||
case "backup":
|
||||
bv := types.CustomBool(v[1] == "1")
|
||||
r.Backup = &bv
|
||||
case "mountoptions":
|
||||
if v[1] != "" {
|
||||
a := strings.Split(v[1], ";")
|
||||
r.MountOptions = &a
|
||||
} else {
|
||||
var a []string
|
||||
r.MountOptions = &a
|
||||
}
|
||||
case "mp":
|
||||
r.MountPoint = v[1]
|
||||
case "quota":
|
||||
bv := types.CustomBool(v[1] == "1")
|
||||
r.Quota = &bv
|
||||
case "ro":
|
||||
bv := types.CustomBool(v[1] == "1")
|
||||
r.ReadOnly = &bv
|
||||
case "replicate":
|
||||
bv := types.CustomBool(v[1] == "1")
|
||||
r.Replicate = &bv
|
||||
case "shared":
|
||||
bv := types.CustomBool(v[1] == "1")
|
||||
r.Shared = &bv
|
||||
case "size":
|
||||
r.DiskSize = &v[1]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnmarshalJSON converts a CustomNetworkInterface string to an object.
|
||||
func (r *CustomNetworkInterface) UnmarshalJSON(b []byte) error {
|
||||
var s string
|
||||
|
||||
er := json.Unmarshal(b, &s)
|
||||
if er != nil {
|
||||
return fmt.Errorf("unable to unmarshal CustomNetworkInterface: %w", er)
|
||||
}
|
||||
|
||||
pairs := strings.Split(s, ",")
|
||||
|
||||
for _, p := range pairs {
|
||||
v := strings.Split(strings.TrimSpace(p), "=")
|
||||
|
||||
//nolint:nestif
|
||||
if len(v) == 1 {
|
||||
r.Name = v[0]
|
||||
} else if len(v) == 2 {
|
||||
switch v[0] {
|
||||
case "bridge":
|
||||
r.Bridge = &v[1]
|
||||
case "firewall":
|
||||
bv := types.CustomBool(v[1] == "1")
|
||||
r.Firewall = &bv
|
||||
case "gw":
|
||||
r.IPv4Gateway = &v[1]
|
||||
case "gw6":
|
||||
r.IPv6Gateway = &v[1]
|
||||
case "ip":
|
||||
r.IPv4Address = &v[1]
|
||||
case "ip6":
|
||||
r.IPv6Address = &v[1]
|
||||
case "hwaddr":
|
||||
r.MACAddress = &v[1]
|
||||
case "mtu":
|
||||
iv, err := strconv.Atoi(v[1])
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to unmarshal 'mtu': %w", err)
|
||||
}
|
||||
|
||||
r.MTU = &iv
|
||||
case "name":
|
||||
r.Name = v[1]
|
||||
case "rate":
|
||||
fv, err := strconv.ParseFloat(v[1], 64)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to unmarshal 'rate': %w", err)
|
||||
}
|
||||
|
||||
r.RateLimit = &fv
|
||||
case "tag":
|
||||
iv, err := strconv.Atoi(v[1])
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to unmarshal 'tag': %w", err)
|
||||
}
|
||||
|
||||
r.Tag = &iv
|
||||
case "trunks":
|
||||
var err error
|
||||
if v[1] != "" {
|
||||
trunks := strings.Split(v[1], ";")
|
||||
a := make([]int, len(trunks))
|
||||
|
||||
for ti, tv := range trunks {
|
||||
a[ti], err = strconv.Atoi(tv)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to unmarshal 'trunks': %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
r.Trunks = &a
|
||||
} else {
|
||||
var a []int
|
||||
r.Trunks = &a
|
||||
}
|
||||
case "type":
|
||||
r.Type = &v[1]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnmarshalJSON converts a CustomRootFS string to an object.
|
||||
func (r *CustomRootFS) UnmarshalJSON(b []byte) error {
|
||||
var s string
|
||||
|
||||
err := json.Unmarshal(b, &s)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to unmarshal CustomRootFS: %w", err)
|
||||
}
|
||||
|
||||
pairs := strings.Split(s, ",")
|
||||
|
||||
for _, p := range pairs {
|
||||
v := strings.Split(strings.TrimSpace(p), "=")
|
||||
|
||||
if len(v) == 1 {
|
||||
r.Volume = v[0]
|
||||
} else if len(v) == 2 {
|
||||
switch v[0] {
|
||||
case "acl":
|
||||
bv := types.CustomBool(v[1] == "1")
|
||||
r.ACL = &bv
|
||||
case "mountoptions":
|
||||
if v[1] != "" {
|
||||
a := strings.Split(v[1], ";")
|
||||
r.MountOptions = &a
|
||||
} else {
|
||||
var a []string
|
||||
r.MountOptions = &a
|
||||
}
|
||||
case "quota":
|
||||
bv := types.CustomBool(v[1] == "1")
|
||||
r.Quota = &bv
|
||||
case "ro":
|
||||
bv := types.CustomBool(v[1] == "1")
|
||||
r.ReadOnly = &bv
|
||||
case "replicate":
|
||||
bv := types.CustomBool(v[1] == "1")
|
||||
r.Replicate = &bv
|
||||
case "shared":
|
||||
bv := types.CustomBool(v[1] == "1")
|
||||
r.Shared = &bv
|
||||
case "size":
|
||||
r.Size = new(types.DiskSize)
|
||||
err := r.Size.UnmarshalJSON([]byte(v[1]))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to unmarshal disk size: %w", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnmarshalJSON converts a CustomStartupBehavior string to an object.
|
||||
func (r *CustomStartupBehavior) UnmarshalJSON(b []byte) error {
|
||||
var s string
|
||||
|
||||
err := json.Unmarshal(b, &s)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to unmarshal CustomStartupBehavior: %w", err)
|
||||
}
|
||||
|
||||
pairs := strings.Split(s, ",")
|
||||
|
||||
for _, p := range pairs {
|
||||
v := strings.Split(strings.TrimSpace(p), "=")
|
||||
|
||||
if len(v) == 2 {
|
||||
switch v[0] {
|
||||
case "down":
|
||||
iv, err := strconv.Atoi(v[1])
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to unmarshal 'down': %w", err)
|
||||
}
|
||||
|
||||
r.Down = &iv
|
||||
case "order":
|
||||
iv, err := strconv.Atoi(v[1])
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to unmarshal 'order': %w", err)
|
||||
}
|
||||
|
||||
r.Order = &iv
|
||||
case "up":
|
||||
iv, err := strconv.Atoi(v[1])
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to unmarshal 'up': %w", err)
|
||||
}
|
||||
|
||||
r.Up = &iv
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
@ -1,16 +0,0 @@
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package firewall
|
||||
|
||||
import (
|
||||
"github.com/bpg/terraform-provider-proxmox/proxmox/firewall"
|
||||
)
|
||||
|
||||
// Client is an interface for accessing the Proxmox container firewall API.
|
||||
type Client struct {
|
||||
firewall.Client
|
||||
}
|
@ -1,41 +0,0 @@
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package nodes
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/bpg/terraform-provider-proxmox/proxmox/api"
|
||||
)
|
||||
|
||||
// GetDNS retrieves the DNS configuration for a node.
|
||||
func (c *Client) GetDNS(ctx context.Context) (*DNSGetResponseData, error) {
|
||||
resBody := &DNSGetResponseBody{}
|
||||
|
||||
err := c.DoRequest(ctx, http.MethodGet, c.ExpandPath("dns"), nil, resBody)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error retrieving DNS configuration: %w", err)
|
||||
}
|
||||
|
||||
if resBody.Data == nil {
|
||||
return nil, api.ErrNoDataObjectInResponse
|
||||
}
|
||||
|
||||
return resBody.Data, nil
|
||||
}
|
||||
|
||||
// UpdateDNS updates the DNS configuration for a node.
|
||||
func (c *Client) UpdateDNS(ctx context.Context, d *DNSUpdateRequestBody) error {
|
||||
err := c.DoRequest(ctx, http.MethodPut, c.ExpandPath("dns"), d, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error updating DNS configuration: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
@ -1,28 +0,0 @@
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package nodes
|
||||
|
||||
// DNSGetResponseBody contains the body from a DNS get response.
|
||||
type DNSGetResponseBody struct {
|
||||
Data *DNSGetResponseData `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
// DNSGetResponseData contains the data from a DNS get response.
|
||||
type DNSGetResponseData struct {
|
||||
Server1 *string `json:"dns1,omitempty" url:"dns1,omitempty"`
|
||||
Server2 *string `json:"dns2,omitempty" url:"dns2,omitempty"`
|
||||
Server3 *string `json:"dns3,omitempty" url:"dns3,omitempty"`
|
||||
SearchDomain *string `json:"search,omitempty" url:"search,omitempty"`
|
||||
}
|
||||
|
||||
// DNSUpdateRequestBody contains the body for a DNS update request.
|
||||
type DNSUpdateRequestBody struct {
|
||||
Server1 *string `json:"dns1,omitempty" url:"dns1,omitempty"`
|
||||
Server2 *string `json:"dns2,omitempty" url:"dns2,omitempty"`
|
||||
Server3 *string `json:"dns3,omitempty" url:"dns3,omitempty"`
|
||||
SearchDomain *string `json:"search,omitempty" url:"search,omitempty"`
|
||||
}
|
@ -1,41 +0,0 @@
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package nodes
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/bpg/terraform-provider-proxmox/proxmox/api"
|
||||
)
|
||||
|
||||
// GetHosts retrieves the Hosts configuration for a node.
|
||||
func (c *Client) GetHosts(ctx context.Context) (*HostsGetResponseData, error) {
|
||||
resBody := &HostsGetResponseBody{}
|
||||
|
||||
err := c.DoRequest(ctx, http.MethodGet, c.ExpandPath("hosts"), nil, resBody)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error retrieving hosts configuration: %w", err)
|
||||
}
|
||||
|
||||
if resBody.Data == nil {
|
||||
return nil, api.ErrNoDataObjectInResponse
|
||||
}
|
||||
|
||||
return resBody.Data, nil
|
||||
}
|
||||
|
||||
// UpdateHosts updates the Hosts configuration for a node.
|
||||
func (c *Client) UpdateHosts(ctx context.Context, d *HostsUpdateRequestBody) error {
|
||||
err := c.DoRequest(ctx, http.MethodPost, c.ExpandPath("hosts"), d, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error updating hosts configuration: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
@ -1,24 +0,0 @@
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package nodes
|
||||
|
||||
// HostsGetResponseBody contains the body from a hosts get response.
|
||||
type HostsGetResponseBody struct {
|
||||
Data *HostsGetResponseData `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
// HostsGetResponseData contains the data from a hosts get response.
|
||||
type HostsGetResponseData struct {
|
||||
Data string `json:"data"`
|
||||
Digest *string `json:"digest,omitempty"`
|
||||
}
|
||||
|
||||
// HostsUpdateRequestBody contains the body for a hosts update request.
|
||||
type HostsUpdateRequestBody struct {
|
||||
Data string `json:"data" url:"data"`
|
||||
Digest *string `json:"digest,omitempty" url:"digest,omitempty"`
|
||||
}
|
@ -1,125 +0,0 @@
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package nodes
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"sort"
|
||||
|
||||
"github.com/bpg/terraform-provider-proxmox/proxmox/api"
|
||||
)
|
||||
|
||||
const (
|
||||
networkReloadTimeoutSec = 5
|
||||
)
|
||||
|
||||
// ListNetworkInterfaces retrieves a list of network interfaces for a specific nodes.
|
||||
func (c *Client) ListNetworkInterfaces(ctx context.Context) ([]*NetworkInterfaceListResponseData, error) {
|
||||
resBody := &NetworkInterfaceListResponseBody{}
|
||||
|
||||
err := c.DoRequest(ctx, http.MethodGet, c.ExpandPath("network"), nil, resBody)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get network interfaces for node \"%s\": %w", c.NodeName, err)
|
||||
}
|
||||
|
||||
if resBody.Data == nil {
|
||||
return nil, api.ErrNoDataObjectInResponse
|
||||
}
|
||||
|
||||
sort.Slice(resBody.Data, func(i, j int) bool {
|
||||
return resBody.Data[i].Priority < resBody.Data[j].Priority
|
||||
})
|
||||
|
||||
return resBody.Data, nil
|
||||
}
|
||||
|
||||
// CreateNetworkInterface creates a network interface for a specific node.
|
||||
func (c *Client) CreateNetworkInterface(ctx context.Context, d *NetworkInterfaceCreateUpdateRequestBody) error {
|
||||
err := c.DoRequest(ctx, http.MethodPost, c.ExpandPath("network"), d, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf(
|
||||
"failed to create network interface \"%s\" for node \"%s\": %w",
|
||||
d.Iface, c.NodeName, err,
|
||||
)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ReloadNetworkConfiguration reloads the network configuration for a specific node.
|
||||
func (c *Client) ReloadNetworkConfiguration(ctx context.Context) error {
|
||||
resBody := &ReloadNetworkResponseBody{}
|
||||
|
||||
err := c.DoRequest(ctx, http.MethodPut, c.ExpandPath("network"), nil, resBody)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to reload network configuration for node \"%s\": %w", c.NodeName, err)
|
||||
}
|
||||
|
||||
if resBody.Data == nil {
|
||||
return api.ErrNoDataObjectInResponse
|
||||
}
|
||||
|
||||
err = c.Tasks().WaitForTask(ctx, *resBody.Data, networkReloadTimeoutSec, 1)
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// RevertNetworkConfiguration reverts the network configuration changes for a specific node.
|
||||
func (c *Client) RevertNetworkConfiguration(ctx context.Context) error {
|
||||
err := c.DoRequest(ctx, http.MethodDelete, c.ExpandPath("network"), nil, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to revert network configuration for node \"%s\": %w", c.NodeName, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateNetworkInterface updates a network interface for a specific node.
|
||||
func (c *Client) UpdateNetworkInterface(
|
||||
ctx context.Context,
|
||||
iface string,
|
||||
d *NetworkInterfaceCreateUpdateRequestBody,
|
||||
) error {
|
||||
err := c.DoRequest(
|
||||
ctx,
|
||||
http.MethodPut,
|
||||
c.ExpandPath(fmt.Sprintf("network/%s", url.PathEscape(iface))),
|
||||
d,
|
||||
nil,
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to update network interface \"%s\" for node \"%s\": %w",
|
||||
d.Iface, c.NodeName, err,
|
||||
)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteNetworkInterface deletes a network interface configuration for a specific node.
|
||||
func (c *Client) DeleteNetworkInterface(ctx context.Context, iface string) error {
|
||||
err := c.DoRequest(
|
||||
ctx,
|
||||
http.MethodDelete,
|
||||
c.ExpandPath(fmt.Sprintf("network/%s", url.PathEscape(iface))),
|
||||
nil,
|
||||
nil,
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to delete network interface \"%s\" for node \"%s\": %w",
|
||||
iface, c.NodeName, err,
|
||||
)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
@ -1,86 +0,0 @@
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package nodes
|
||||
|
||||
import (
|
||||
"github.com/bpg/terraform-provider-proxmox/internal/types"
|
||||
)
|
||||
|
||||
// NetworkInterfaceListResponseBody contains the body from a node network interface list response.
|
||||
type NetworkInterfaceListResponseBody struct {
|
||||
Data []*NetworkInterfaceListResponseData `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
// NetworkInterfaceListResponseData contains the data from a node network interface list response.
|
||||
type NetworkInterfaceListResponseData struct {
|
||||
// There seems to be inconsistency in the APIs between certain versions of Proxmox.
|
||||
// See https://github.com/bpg/terraform-provider-proxmox/issues/410
|
||||
// BridgeFD *int `json:"bridge_fd,omitempty"`
|
||||
|
||||
Active *types.CustomBool `json:"active,omitempty"`
|
||||
Address *string `json:"address,omitempty"`
|
||||
Address6 *string `json:"address6,omitempty"`
|
||||
Autostart *types.CustomBool `json:"autostart,omitempty"`
|
||||
BridgePorts *string `json:"bridge_ports,omitempty"`
|
||||
BridgeSTP *string `json:"bridge_stp,omitempty"`
|
||||
BridgeVIDs *string `json:"bridge_vids,omitempty"`
|
||||
BridgeVLANAware *types.CustomBool `json:"bridge_vlan_aware,omitempty"`
|
||||
CIDR *string `json:"cidr,omitempty"`
|
||||
CIDR6 *string `json:"cidr6,omitempty"`
|
||||
Comments *string `json:"comments,omitempty"`
|
||||
Exists *types.CustomBool `json:"exists,omitempty"`
|
||||
Families *[]string `json:"families,omitempty"`
|
||||
Gateway *string `json:"gateway,omitempty"`
|
||||
Gateway6 *string `json:"gateway6,omitempty"`
|
||||
Iface string `json:"iface"`
|
||||
MethodIPv4 *string `json:"method,omitempty"`
|
||||
MethodIPv6 *string `json:"method6,omitempty"`
|
||||
MTU *string `json:"mtu,omitempty"`
|
||||
Netmask *string `json:"netmask,omitempty"`
|
||||
VLANID *string `json:"vlan-id,omitempty"`
|
||||
VLANRawDevice *string `json:"vlan-raw-device,omitempty"`
|
||||
Priority int `json:"priority"`
|
||||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
// NetworkInterfaceCreateUpdateRequestBody contains the body for a node network interface create / update request.
|
||||
type NetworkInterfaceCreateUpdateRequestBody struct {
|
||||
Iface string `json:"iface" url:"iface"`
|
||||
Type string `json:"type" url:"type"`
|
||||
|
||||
Address *string `json:"address,omitempty" url:"address,omitempty"`
|
||||
Address6 *string `json:"address6,omitempty" url:"address6,omitempty"`
|
||||
Autostart *types.CustomBool `json:"autostart,omitempty" url:"autostart,omitempty,int"`
|
||||
BondPrimary *string `json:"bond-primary,omitempty" url:"bond-primary,omitempty"`
|
||||
BondMode *string `json:"bond_mode,omitempty" url:"bond_mode,omitempty"`
|
||||
BondXmitHashPolicy *string `json:"bond_xmit_hash_policy,omitempty" url:"bond_xmit_hash_policy,omitempty"`
|
||||
BridgePorts *string `json:"bridge_ports,omitempty" url:"bridge_ports,omitempty"`
|
||||
BridgeVLANAware *types.CustomBool `json:"bridge_vlan_aware,omitempty" url:"bridge_vlan_aware,omitempty,int"`
|
||||
CIDR *string `json:"cidr,omitempty" url:"cidr,omitempty"`
|
||||
CIDR6 *string `json:"cidr6,omitempty" url:"cidr6,omitempty"`
|
||||
Comments *string `json:"comments,omitempty" url:"comments,omitempty"`
|
||||
Comments6 *string `json:"comments6,omitempty" url:"comments6,omitempty"`
|
||||
Gateway *string `json:"gateway,omitempty" url:"gateway,omitempty"`
|
||||
Gateway6 *string `json:"gateway6,omitempty" url:"gateway6,omitempty"`
|
||||
Delete *[]string `json:"delete,omitempty" url:"delete,omitempty"`
|
||||
MTU *int64 `json:"mtu,omitempty" url:"mtu,omitempty"`
|
||||
Netmask *string `json:"netmask,omitempty" url:"netmask,omitempty"`
|
||||
Netmask6 *string `json:"netmask6,omitempty" url:"netmask6,omitempty"`
|
||||
OVSBonds *string `json:"ovs_bonds,omitempty" url:"ovs_bonds,omitempty"`
|
||||
OVSBridge *string `json:"ovs_bridge,omitempty" url:"ovs_bridge,omitempty"`
|
||||
OVSOptions *string `json:"ovs_options,omitempty" url:"ovs_options,omitempty"`
|
||||
OVSPorts *string `json:"ovs_ports,omitempty" url:"ovs_ports,omitempty"`
|
||||
OVSTag *string `json:"ovs_tag,omitempty" url:"ovs_tag,omitempty"`
|
||||
Slaves *string `json:"slaves,omitempty" url:"slaves,omitempty"`
|
||||
VLANID *int64 `json:"vlan_id,omitempty" url:"vlan_id,omitempty"`
|
||||
VLANRawDevice *string `json:"vlan-raw-device,omitempty" url:"vlan-raw-device,omitempty"`
|
||||
}
|
||||
|
||||
// ReloadNetworkResponseBody contains the body from a node network reload response.
|
||||
type ReloadNetworkResponseBody struct {
|
||||
Data *string `json:"data,omitempty"`
|
||||
}
|
@ -1,62 +0,0 @@
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package nodes
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"sort"
|
||||
|
||||
"github.com/bpg/terraform-provider-proxmox/proxmox/api"
|
||||
)
|
||||
|
||||
// ListNodes retrieves a list of nodes.
|
||||
func (c *Client) ListNodes(ctx context.Context) ([]*ListResponseData, error) {
|
||||
resBody := &ListResponseBody{}
|
||||
|
||||
err := c.DoRequest(ctx, http.MethodGet, "nodes", nil, resBody)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get nodes: %w", err)
|
||||
}
|
||||
|
||||
if resBody.Data == nil {
|
||||
return nil, api.ErrNoDataObjectInResponse
|
||||
}
|
||||
|
||||
sort.Slice(resBody.Data, func(i, j int) bool {
|
||||
return resBody.Data[i].Name < resBody.Data[j].Name
|
||||
})
|
||||
|
||||
return resBody.Data, nil
|
||||
}
|
||||
|
||||
// GetTime retrieves the time information for a node.
|
||||
func (c *Client) GetTime(ctx context.Context) (*GetTimeResponseData, error) {
|
||||
resBody := &GetTimeResponseBody{}
|
||||
|
||||
err := c.DoRequest(ctx, http.MethodGet, c.ExpandPath("time"), nil, resBody)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get time information for node \"%s\": %w", c.NodeName, err)
|
||||
}
|
||||
|
||||
if resBody.Data == nil {
|
||||
return nil, api.ErrNoDataObjectInResponse
|
||||
}
|
||||
|
||||
return resBody.Data, nil
|
||||
}
|
||||
|
||||
// UpdateTime updates the time on a node.
|
||||
func (c *Client) UpdateTime(ctx context.Context, d *UpdateTimeRequestBody) error {
|
||||
err := c.DoRequest(ctx, http.MethodPut, c.ExpandPath("time"), d, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to update node time: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
@ -1,70 +0,0 @@
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package nodes
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/url"
|
||||
|
||||
"github.com/bpg/terraform-provider-proxmox/internal/types"
|
||||
)
|
||||
|
||||
// CustomCommands contains an array of commands to execute.
|
||||
type CustomCommands []string
|
||||
|
||||
// ExecuteRequestBody contains the data for a node execute request.
|
||||
type ExecuteRequestBody struct {
|
||||
Commands CustomCommands `json:"commands" url:"commands"`
|
||||
}
|
||||
|
||||
// GetTimeResponseBody contains the body from a node time zone get response.
|
||||
type GetTimeResponseBody struct {
|
||||
Data *GetTimeResponseData `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
// GetTimeResponseData contains the data from a node list response.
|
||||
type GetTimeResponseData struct {
|
||||
LocalTime types.CustomTimestamp `json:"localtime"`
|
||||
TimeZone string `json:"timezone"`
|
||||
UTCTime types.CustomTimestamp `json:"time"`
|
||||
}
|
||||
|
||||
// ListResponseBody contains the body from a node list response.
|
||||
type ListResponseBody struct {
|
||||
Data []*ListResponseData `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
// ListResponseData contains the data from a node list response.
|
||||
type ListResponseData struct {
|
||||
CPUCount *int `json:"maxcpu,omitempty"`
|
||||
CPUUtilization *float64 `json:"cpu,omitempty"`
|
||||
MemoryAvailable *int `json:"maxmem,omitempty"`
|
||||
MemoryUsed *int `json:"mem,omitempty"`
|
||||
Name string `json:"node"`
|
||||
SSLFingerprint *string `json:"ssl_fingerprint,omitempty"`
|
||||
Status *string `json:"status"`
|
||||
SupportLevel *string `json:"level,omitempty"`
|
||||
Uptime *int `json:"uptime"`
|
||||
}
|
||||
|
||||
// UpdateTimeRequestBody contains the body for a node time update request.
|
||||
type UpdateTimeRequestBody struct {
|
||||
TimeZone string `json:"timezone" url:"timezone"`
|
||||
}
|
||||
|
||||
// EncodeValues converts a CustomCommands array to a JSON encoded URL value.
|
||||
func (r CustomCommands) EncodeValues(key string, v *url.Values) error {
|
||||
jsonArrayBytes, err := json.Marshal(r)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error marshalling CustomCommands array: %w", err)
|
||||
}
|
||||
|
||||
v.Add(key, string(jsonArrayBytes))
|
||||
|
||||
return nil
|
||||
}
|
@ -1,280 +0,0 @@
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package nodes
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"sort"
|
||||
|
||||
"github.com/hashicorp/terraform-plugin-log/tflog"
|
||||
|
||||
"github.com/bpg/terraform-provider-proxmox/proxmox/api"
|
||||
)
|
||||
|
||||
// DeleteDatastoreFile deletes a file in a datastore.
|
||||
func (c *Client) DeleteDatastoreFile(
|
||||
ctx context.Context,
|
||||
datastoreID, volumeID string,
|
||||
) error {
|
||||
err := c.DoRequest(
|
||||
ctx,
|
||||
http.MethodDelete,
|
||||
c.ExpandPath(
|
||||
fmt.Sprintf(
|
||||
"storage/%s/content/%s",
|
||||
url.PathEscape(datastoreID),
|
||||
url.PathEscape(volumeID),
|
||||
),
|
||||
),
|
||||
nil,
|
||||
nil,
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error deleting file %s from datastore %s: %w", volumeID, datastoreID, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetDatastoreStatus gets status information for a given datastore.
|
||||
func (c *Client) GetDatastoreStatus(
|
||||
ctx context.Context,
|
||||
datastoreID string,
|
||||
) (*DatastoreGetStatusResponseData, error) {
|
||||
resBody := &DatastoreGetStatusResponseBody{}
|
||||
|
||||
err := c.DoRequest(
|
||||
ctx,
|
||||
http.MethodGet,
|
||||
c.ExpandPath(
|
||||
fmt.Sprintf(
|
||||
"storage/%s/status",
|
||||
url.PathEscape(datastoreID),
|
||||
),
|
||||
),
|
||||
nil,
|
||||
resBody,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error retrieving status for datastore %s: %w", datastoreID, err)
|
||||
}
|
||||
|
||||
if resBody.Data == nil {
|
||||
return nil, api.ErrNoDataObjectInResponse
|
||||
}
|
||||
|
||||
return resBody.Data, nil
|
||||
}
|
||||
|
||||
// ListDatastoreFiles retrieves a list of the files in a datastore.
|
||||
func (c *Client) ListDatastoreFiles(
|
||||
ctx context.Context,
|
||||
datastoreID string,
|
||||
) ([]*DatastoreFileListResponseData, error) {
|
||||
resBody := &DatastoreFileListResponseBody{}
|
||||
|
||||
err := c.DoRequest(
|
||||
ctx,
|
||||
http.MethodGet,
|
||||
c.ExpandPath(
|
||||
fmt.Sprintf(
|
||||
"storage/%s/content",
|
||||
url.PathEscape(datastoreID),
|
||||
),
|
||||
),
|
||||
nil,
|
||||
resBody,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error retrieving files from datastore %s: %w", datastoreID, err)
|
||||
}
|
||||
|
||||
if resBody.Data == nil {
|
||||
return nil, api.ErrNoDataObjectInResponse
|
||||
}
|
||||
|
||||
sort.Slice(resBody.Data, func(i, j int) bool {
|
||||
return resBody.Data[i].VolumeID < resBody.Data[j].VolumeID
|
||||
})
|
||||
|
||||
return resBody.Data, nil
|
||||
}
|
||||
|
||||
// ListDatastores retrieves a list of nodes.
|
||||
func (c *Client) ListDatastores(
|
||||
ctx context.Context,
|
||||
d *DatastoreListRequestBody,
|
||||
) ([]*DatastoreListResponseData, error) {
|
||||
resBody := &DatastoreListResponseBody{}
|
||||
|
||||
err := c.DoRequest(
|
||||
ctx,
|
||||
http.MethodGet,
|
||||
c.ExpandPath("storage"),
|
||||
d,
|
||||
resBody,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error retrieving datastores: %w", err)
|
||||
}
|
||||
|
||||
if resBody.Data == nil {
|
||||
return nil, api.ErrNoDataObjectInResponse
|
||||
}
|
||||
|
||||
sort.Slice(resBody.Data, func(i, j int) bool {
|
||||
return resBody.Data[i].ID < resBody.Data[j].ID
|
||||
})
|
||||
|
||||
return resBody.Data, nil
|
||||
}
|
||||
|
||||
// APIUpload uploads a file to a datastore using the Proxmox API.
|
||||
func (c *Client) APIUpload(
|
||||
ctx context.Context,
|
||||
datastoreID string,
|
||||
d *api.FileUploadRequest,
|
||||
) (*DatastoreUploadResponseBody, error) {
|
||||
tflog.Debug(ctx, "uploading file to datastore using PVE API", map[string]interface{}{
|
||||
"file_name": d.FileName,
|
||||
"content_type": d.ContentType,
|
||||
})
|
||||
|
||||
r, w := io.Pipe()
|
||||
|
||||
defer func(r *io.PipeReader) {
|
||||
err := r.Close()
|
||||
if err != nil {
|
||||
tflog.Error(ctx, "failed to close pipe reader", map[string]interface{}{
|
||||
"error": err,
|
||||
})
|
||||
}
|
||||
}(r)
|
||||
|
||||
m := multipart.NewWriter(w)
|
||||
|
||||
go func() {
|
||||
defer func(w *io.PipeWriter) {
|
||||
err := w.Close()
|
||||
if err != nil {
|
||||
tflog.Error(ctx, "failed to close pipe writer", map[string]interface{}{
|
||||
"error": err,
|
||||
})
|
||||
}
|
||||
}(w)
|
||||
defer func(m *multipart.Writer) {
|
||||
err := m.Close()
|
||||
if err != nil {
|
||||
tflog.Error(ctx, "failed to close multipart writer", map[string]interface{}{
|
||||
"error": err,
|
||||
})
|
||||
}
|
||||
}(m)
|
||||
|
||||
err := m.WriteField("content", d.ContentType)
|
||||
if err != nil {
|
||||
tflog.Error(ctx, "failed to write 'content' field", map[string]interface{}{
|
||||
"error": err,
|
||||
})
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
part, err := m.CreateFormFile("filename", d.FileName)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
_, err = io.Copy(part, d.File)
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
// We need to store the multipart content in a temporary file to avoid using high amounts of memory.
|
||||
// This is necessary due to Proxmox VE not supporting chunked transfers in v6.1 and earlier versions.
|
||||
tempMultipartFile, err := os.CreateTemp("", "multipart")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create temporary file: %w", err)
|
||||
}
|
||||
|
||||
tempMultipartFileName := tempMultipartFile.Name()
|
||||
|
||||
_, err = io.Copy(tempMultipartFile, r)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to copy multipart data to temporary file: %w", err)
|
||||
}
|
||||
|
||||
err = tempMultipartFile.Close()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to close temporary file: %w", err)
|
||||
}
|
||||
|
||||
defer func(name string) {
|
||||
e := os.Remove(name)
|
||||
if e != nil {
|
||||
tflog.Error(ctx, "failed to remove temporary file", map[string]interface{}{
|
||||
"error": e,
|
||||
})
|
||||
}
|
||||
}(tempMultipartFileName)
|
||||
|
||||
// Now that the multipart data is stored in a file, we can go ahead and do an HTTP POST request.
|
||||
fileReader, err := os.Open(tempMultipartFileName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to open temporary file: %w", err)
|
||||
}
|
||||
|
||||
defer func(fileReader *os.File) {
|
||||
e := fileReader.Close()
|
||||
if e != nil {
|
||||
tflog.Error(ctx, "failed to close file reader", map[string]interface{}{
|
||||
"error": e,
|
||||
})
|
||||
}
|
||||
}(fileReader)
|
||||
|
||||
fileInfo, err := fileReader.Stat()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get file info: %w", err)
|
||||
}
|
||||
|
||||
fileSize := fileInfo.Size()
|
||||
|
||||
reqBody := &api.MultiPartData{
|
||||
Boundary: m.Boundary(),
|
||||
Reader: fileReader,
|
||||
Size: &fileSize,
|
||||
}
|
||||
|
||||
resBody := &DatastoreUploadResponseBody{}
|
||||
err = c.DoRequest(
|
||||
ctx,
|
||||
http.MethodPost,
|
||||
c.ExpandPath(
|
||||
fmt.Sprintf(
|
||||
"storage/%s/upload",
|
||||
url.PathEscape(datastoreID),
|
||||
),
|
||||
),
|
||||
reqBody,
|
||||
resBody,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error uploading file to datastore %s: %w", datastoreID, err)
|
||||
}
|
||||
|
||||
return resBody, nil
|
||||
}
|
@ -1,77 +0,0 @@
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package nodes
|
||||
|
||||
import (
|
||||
"github.com/bpg/terraform-provider-proxmox/internal/types"
|
||||
)
|
||||
|
||||
// DatastoreFileListResponseBody contains the body from a datastore content list response.
|
||||
type DatastoreFileListResponseBody struct {
|
||||
Data []*DatastoreFileListResponseData `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
// DatastoreFileListResponseData contains the data from a datastore content list response.
|
||||
type DatastoreFileListResponseData struct {
|
||||
ContentType string `json:"content"`
|
||||
FileFormat string `json:"format"`
|
||||
FileSize int `json:"size"`
|
||||
ParentVolumeID *string `json:"parent,omitempty"`
|
||||
SpaceUsed *int `json:"used,omitempty"`
|
||||
VMID *int `json:"vmid,omitempty"`
|
||||
VolumeID string `json:"volid"`
|
||||
}
|
||||
|
||||
// DatastoreGetStatusResponseBody contains the body from a datastore status get request.
|
||||
type DatastoreGetStatusResponseBody struct {
|
||||
Data *DatastoreGetStatusResponseData `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
// DatastoreGetStatusResponseData contains the data from a datastore status get request.
|
||||
type DatastoreGetStatusResponseData struct {
|
||||
Active *types.CustomBool `json:"active,omitempty"`
|
||||
AvailableBytes *int64 `json:"avail,omitempty"`
|
||||
Content *types.CustomCommaSeparatedList `json:"content,omitempty" url:"content,omitempty,comma"`
|
||||
Enabled *types.CustomBool `json:"enabled,omitempty"`
|
||||
Shared *types.CustomBool `json:"shared,omitempty"`
|
||||
TotalBytes *int64 `json:"total,omitempty"`
|
||||
Type *string `json:"type,omitempty"`
|
||||
UsedBytes *int64 `json:"used,omitempty"`
|
||||
}
|
||||
|
||||
// DatastoreListRequestBody contains the body for a datastore list request.
|
||||
type DatastoreListRequestBody struct {
|
||||
ContentTypes types.CustomCommaSeparatedList `json:"content,omitempty" url:"content,omitempty,comma"`
|
||||
Enabled *types.CustomBool `json:"enabled,omitempty" url:"enabled,omitempty,int"`
|
||||
Format *types.CustomBool `json:"format,omitempty" url:"format,omitempty,int"`
|
||||
ID *string `json:"storage,omitempty" url:"storage,omitempty"`
|
||||
Target *string `json:"target,omitempty" url:"target,omitempty"`
|
||||
}
|
||||
|
||||
// DatastoreListResponseBody contains the body from a datastore list response.
|
||||
type DatastoreListResponseBody struct {
|
||||
Data []*DatastoreListResponseData `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
// DatastoreListResponseData contains the data from a datastore list response.
|
||||
type DatastoreListResponseData struct {
|
||||
Active *types.CustomBool `json:"active,omitempty"`
|
||||
ContentTypes *types.CustomCommaSeparatedList `json:"content,omitempty"`
|
||||
Enabled *types.CustomBool `json:"enabled,omitempty"`
|
||||
ID string `json:"storage,omitempty"`
|
||||
Shared *types.CustomBool `json:"shared,omitempty"`
|
||||
SpaceAvailable *int `json:"avail,omitempty"`
|
||||
SpaceTotal *int `json:"total,omitempty"`
|
||||
SpaceUsed *int `json:"used,omitempty"`
|
||||
SpaceUsedPercentage *float64 `json:"used_fraction,omitempty"`
|
||||
Type string `json:"type,omitempty"`
|
||||
}
|
||||
|
||||
// DatastoreUploadResponseBody contains the body from a datastore upload response.
|
||||
type DatastoreUploadResponseBody struct {
|
||||
UploadID *string `json:"data,omitempty"`
|
||||
}
|
@ -1,25 +0,0 @@
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package tasks
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/bpg/terraform-provider-proxmox/proxmox/api"
|
||||
)
|
||||
|
||||
// Client is an interface for performing requests against the Proxmox 'tasks' API.
|
||||
type Client struct {
|
||||
api.Client
|
||||
}
|
||||
|
||||
// ExpandPath expands a path relative to the client's base path.
|
||||
func (c *Client) ExpandPath(path string) string {
|
||||
return c.Client.ExpandPath(
|
||||
fmt.Sprintf("tasks/%s", path),
|
||||
)
|
||||
}
|
@ -1,86 +0,0 @@
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package tasks
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"github.com/bpg/terraform-provider-proxmox/proxmox/api"
|
||||
)
|
||||
|
||||
// GetTaskStatus retrieves the status of a task.
|
||||
func (c *Client) GetTaskStatus(ctx context.Context, upid string) (*GetTaskStatusResponseData, error) {
|
||||
resBody := &GetTaskStatusResponseBody{}
|
||||
|
||||
err := c.DoRequest(
|
||||
ctx,
|
||||
http.MethodGet,
|
||||
c.ExpandPath(fmt.Sprintf("%s/status", url.PathEscape(upid))),
|
||||
nil,
|
||||
resBody,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error retrievinf task status: %w", err)
|
||||
}
|
||||
|
||||
if resBody.Data == nil {
|
||||
return nil, api.ErrNoDataObjectInResponse
|
||||
}
|
||||
|
||||
return resBody.Data, nil
|
||||
}
|
||||
|
||||
// WaitForTask waits for a specific task to complete.
|
||||
func (c *Client) WaitForTask(ctx context.Context, upid string, timeoutSec, delaySec int) error {
|
||||
timeDelay := int64(delaySec)
|
||||
timeMax := float64(timeoutSec)
|
||||
timeStart := time.Now()
|
||||
timeElapsed := timeStart.Sub(timeStart)
|
||||
|
||||
for timeElapsed.Seconds() < timeMax {
|
||||
if int64(timeElapsed.Seconds())%timeDelay == 0 {
|
||||
status, err := c.GetTaskStatus(ctx, upid)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if status.Status != "running" {
|
||||
if status.ExitCode != "OK" {
|
||||
return fmt.Errorf(
|
||||
"task \"%s\" failed to complete with exit code: %s",
|
||||
upid,
|
||||
status.ExitCode,
|
||||
)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
|
||||
timeElapsed = time.Since(timeStart)
|
||||
|
||||
if ctx.Err() != nil {
|
||||
return fmt.Errorf(
|
||||
"context error while waiting for task \"%s\" to complete: %w",
|
||||
upid, ctx.Err(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Errorf(
|
||||
"timeout while waiting for task \"%s\" to complete",
|
||||
upid,
|
||||
)
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package tasks
|
||||
|
||||
// GetTaskStatusResponseBody contains the body from a node get task status response.
|
||||
type GetTaskStatusResponseBody struct {
|
||||
Data *GetTaskStatusResponseData `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
// GetTaskStatusResponseData contains the data from a node get task status response.
|
||||
type GetTaskStatusResponseData struct {
|
||||
PID int `json:"pid,omitempty"`
|
||||
Status string `json:"status,omitempty"`
|
||||
ExitCode string `json:"exitstatus,omitempty"`
|
||||
}
|
@ -1,50 +0,0 @@
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package vms
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/bpg/terraform-provider-proxmox/proxmox/api"
|
||||
"github.com/bpg/terraform-provider-proxmox/proxmox/firewall"
|
||||
"github.com/bpg/terraform-provider-proxmox/proxmox/nodes/tasks"
|
||||
vmfirewall "github.com/bpg/terraform-provider-proxmox/proxmox/nodes/vms/firewall"
|
||||
)
|
||||
|
||||
// Client is an interface for accessing the Proxmox VM API.
|
||||
type Client struct {
|
||||
api.Client
|
||||
VMID int
|
||||
}
|
||||
|
||||
func (c *Client) basePath() string {
|
||||
return c.Client.ExpandPath("qemu")
|
||||
}
|
||||
|
||||
// ExpandPath expands a relative path to a full VM API path.
|
||||
func (c *Client) ExpandPath(path string) string {
|
||||
ep := fmt.Sprintf("%s/%d", c.basePath(), c.VMID)
|
||||
if path != "" {
|
||||
ep = fmt.Sprintf("%s/%s", ep, path)
|
||||
}
|
||||
|
||||
return ep
|
||||
}
|
||||
|
||||
// Tasks returns a client for managing VM tasks.
|
||||
func (c *Client) Tasks() *tasks.Client {
|
||||
return &tasks.Client{
|
||||
Client: c.Client,
|
||||
}
|
||||
}
|
||||
|
||||
// Firewall returns a client for managing the VM firewall.
|
||||
func (c *Client) Firewall() firewall.API {
|
||||
return &vmfirewall.Client{
|
||||
Client: firewall.Client{Client: c},
|
||||
}
|
||||
}
|
@ -1,16 +0,0 @@
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package firewall
|
||||
|
||||
import (
|
||||
"github.com/bpg/terraform-provider-proxmox/proxmox/firewall"
|
||||
)
|
||||
|
||||
// Client is an interface for accessing the Proxmox VM firewall API.
|
||||
type Client struct {
|
||||
firewall.Client
|
||||
}
|
@ -1,581 +0,0 @@
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package vms
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/terraform-plugin-log/tflog"
|
||||
|
||||
"github.com/bpg/terraform-provider-proxmox/proxmox/api"
|
||||
)
|
||||
|
||||
// CloneVM clones a virtual machine.
|
||||
func (c *Client) CloneVM(ctx context.Context, retries int, d *CloneRequestBody, timeout int) error {
|
||||
var err error
|
||||
|
||||
resBody := &MoveDiskResponseBody{}
|
||||
|
||||
// just a guard in case someone sets retries to 0 unknowingly
|
||||
if retries <= 0 {
|
||||
retries = 1
|
||||
}
|
||||
|
||||
for i := 0; i < retries; i++ {
|
||||
err = c.DoRequest(ctx, http.MethodPost, c.ExpandPath("clone"), d, resBody)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("error cloning VM: %w", err)
|
||||
}
|
||||
|
||||
if resBody.Data == nil {
|
||||
return api.ErrNoDataObjectInResponse
|
||||
}
|
||||
|
||||
err = c.Tasks().WaitForTask(ctx, *resBody.Data, timeout, 5)
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
time.Sleep(10 * time.Second)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("error waiting for VM clone: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateVM creates a virtual machine.
|
||||
func (c *Client) CreateVM(ctx context.Context, d *CreateRequestBody, timeout int) error {
|
||||
taskID, err := c.CreateVMAsync(ctx, d)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = c.Tasks().WaitForTask(ctx, *taskID, timeout, 1)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("error waiting for VM creation: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateVMAsync creates a virtual machine asynchronously.
|
||||
func (c *Client) CreateVMAsync(ctx context.Context, d *CreateRequestBody) (*string, error) {
|
||||
resBody := &CreateResponseBody{}
|
||||
|
||||
err := c.DoRequest(ctx, http.MethodPost, c.basePath(), d, resBody)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error creating VM: %w", err)
|
||||
}
|
||||
|
||||
if resBody.Data == nil {
|
||||
return nil, api.ErrNoDataObjectInResponse
|
||||
}
|
||||
|
||||
return resBody.Data, nil
|
||||
}
|
||||
|
||||
// DeleteVM deletes a virtual machine.
|
||||
func (c *Client) DeleteVM(ctx context.Context) error {
|
||||
err := c.DoRequest(ctx, http.MethodDelete, c.ExpandPath("?destroy-unreferenced-disks=1&purge=1"), nil, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error deleting VM: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetVM retrieves a virtual machine.
|
||||
func (c *Client) GetVM(ctx context.Context) (*GetResponseData, error) {
|
||||
resBody := &GetResponseBody{}
|
||||
|
||||
err := c.DoRequest(ctx, http.MethodGet, c.ExpandPath("config"), nil, resBody)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error retrieving VM: %w", err)
|
||||
}
|
||||
|
||||
if resBody.Data == nil {
|
||||
return nil, api.ErrNoDataObjectInResponse
|
||||
}
|
||||
|
||||
return resBody.Data, nil
|
||||
}
|
||||
|
||||
// GetVMNetworkInterfacesFromAgent retrieves the network interfaces reported by the QEMU agent.
|
||||
func (c *Client) GetVMNetworkInterfacesFromAgent(ctx context.Context) (*GetQEMUNetworkInterfacesResponseData, error) {
|
||||
resBody := &GetQEMUNetworkInterfacesResponseBody{}
|
||||
|
||||
err := c.DoRequest(ctx, http.MethodGet, c.ExpandPath("agent/network-get-interfaces"), nil, resBody)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error retrieving VM network interfaces from agent: %w", err)
|
||||
}
|
||||
|
||||
if resBody.Data == nil {
|
||||
return nil, api.ErrNoDataObjectInResponse
|
||||
}
|
||||
|
||||
return resBody.Data, nil
|
||||
}
|
||||
|
||||
// GetVMStatus retrieves the status for a virtual machine.
|
||||
func (c *Client) GetVMStatus(ctx context.Context) (*GetStatusResponseData, error) {
|
||||
resBody := &GetStatusResponseBody{}
|
||||
|
||||
err := c.DoRequest(ctx, http.MethodGet, c.ExpandPath("status/current"), nil, resBody)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error retrieving VM status: %w", err)
|
||||
}
|
||||
|
||||
if resBody.Data == nil {
|
||||
return nil, api.ErrNoDataObjectInResponse
|
||||
}
|
||||
|
||||
return resBody.Data, nil
|
||||
}
|
||||
|
||||
// MigrateVM migrates a virtual machine.
|
||||
func (c *Client) MigrateVM(ctx context.Context, d *MigrateRequestBody, timeout int) error {
|
||||
taskID, err := c.MigrateVMAsync(ctx, d)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = c.Tasks().WaitForTask(ctx, *taskID, timeout, 5)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error waiting for VM migration: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// MigrateVMAsync migrates a virtual machine asynchronously.
|
||||
func (c *Client) MigrateVMAsync(ctx context.Context, d *MigrateRequestBody) (*string, error) {
|
||||
resBody := &MigrateResponseBody{}
|
||||
|
||||
err := c.DoRequest(ctx, http.MethodPost, c.ExpandPath("migrate"), d, resBody)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error migrating VM: %w", err)
|
||||
}
|
||||
|
||||
if resBody.Data == nil {
|
||||
return nil, api.ErrNoDataObjectInResponse
|
||||
}
|
||||
|
||||
return resBody.Data, nil
|
||||
}
|
||||
|
||||
// MoveVMDisk moves a virtual machine disk.
|
||||
func (c *Client) MoveVMDisk(ctx context.Context, d *MoveDiskRequestBody, timeout int) error {
|
||||
taskID, err := c.MoveVMDiskAsync(ctx, d)
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "you can't move to the same storage with same format") {
|
||||
// if someone tries to move to the same storage, the move is considered to be successful
|
||||
return nil
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
err = c.Tasks().WaitForTask(ctx, *taskID, timeout, 5)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error waiting for VM disk move: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// MoveVMDiskAsync moves a virtual machine disk asynchronously.
|
||||
func (c *Client) MoveVMDiskAsync(ctx context.Context, d *MoveDiskRequestBody) (*string, error) {
|
||||
resBody := &MoveDiskResponseBody{}
|
||||
|
||||
err := c.DoRequest(ctx, http.MethodPost, c.ExpandPath("move_disk"), d, resBody)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error moving VM disk: %w", err)
|
||||
}
|
||||
|
||||
if resBody.Data == nil {
|
||||
return nil, api.ErrNoDataObjectInResponse
|
||||
}
|
||||
|
||||
return resBody.Data, nil
|
||||
}
|
||||
|
||||
// ListVMs retrieves a list of virtual machines.
|
||||
func (c *Client) ListVMs(ctx context.Context) ([]*ListResponseData, error) {
|
||||
resBody := &ListResponseBody{}
|
||||
|
||||
err := c.DoRequest(ctx, http.MethodGet, c.basePath(), nil, resBody)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error retrieving VMs: %w", err)
|
||||
}
|
||||
|
||||
if resBody.Data == nil {
|
||||
return nil, api.ErrNoDataObjectInResponse
|
||||
}
|
||||
|
||||
return resBody.Data, nil
|
||||
}
|
||||
|
||||
// RebootVM reboots a virtual machine.
|
||||
func (c *Client) RebootVM(ctx context.Context, d *RebootRequestBody, timeout int) error {
|
||||
taskID, err := c.RebootVMAsync(ctx, d)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = c.Tasks().WaitForTask(ctx, *taskID, timeout, 5)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error waiting for VM reboot: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// RebootVMAsync reboots a virtual machine asynchronously.
|
||||
func (c *Client) RebootVMAsync(ctx context.Context, d *RebootRequestBody) (*string, error) {
|
||||
resBody := &RebootResponseBody{}
|
||||
|
||||
err := c.DoRequest(ctx, http.MethodPost, c.ExpandPath("status/reboot"), d, resBody)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error rebooting VM: %w", err)
|
||||
}
|
||||
|
||||
if resBody.Data == nil {
|
||||
return nil, api.ErrNoDataObjectInResponse
|
||||
}
|
||||
|
||||
return resBody.Data, nil
|
||||
}
|
||||
|
||||
// ResizeVMDisk resizes a virtual machine disk.
|
||||
func (c *Client) ResizeVMDisk(ctx context.Context, d *ResizeDiskRequestBody) error {
|
||||
var err error
|
||||
|
||||
tflog.Debug(ctx, "resize disk", map[string]interface{}{
|
||||
"disk": d.Disk,
|
||||
"size": d.Size,
|
||||
})
|
||||
|
||||
for i := 0; i < 5; i++ {
|
||||
err = c.DoRequest(
|
||||
ctx,
|
||||
http.MethodPut,
|
||||
c.ExpandPath("resize"),
|
||||
d,
|
||||
nil,
|
||||
)
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
tflog.Debug(ctx, "resize disk failed", map[string]interface{}{
|
||||
"retry": i,
|
||||
})
|
||||
time.Sleep(5 * time.Second)
|
||||
|
||||
if ctx.Err() != nil {
|
||||
return fmt.Errorf("error resizing VM disk: %w", ctx.Err())
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("error resizing VM disk: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ShutdownVM shuts down a virtual machine.
|
||||
func (c *Client) ShutdownVM(ctx context.Context, d *ShutdownRequestBody, timeout int) error {
|
||||
taskID, err := c.ShutdownVMAsync(ctx, d)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = c.Tasks().WaitForTask(ctx, *taskID, timeout, 5)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error waiting for VM shutdown: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ShutdownVMAsync shuts down a virtual machine asynchronously.
|
||||
func (c *Client) ShutdownVMAsync(ctx context.Context, d *ShutdownRequestBody) (*string, error) {
|
||||
resBody := &ShutdownResponseBody{}
|
||||
|
||||
err := c.DoRequest(ctx, http.MethodPost, c.ExpandPath("status/shutdown"), d, resBody)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error shutting down VM: %w", err)
|
||||
}
|
||||
|
||||
if resBody.Data == nil {
|
||||
return nil, api.ErrNoDataObjectInResponse
|
||||
}
|
||||
|
||||
return resBody.Data, nil
|
||||
}
|
||||
|
||||
// StartVM starts a virtual machine.
|
||||
func (c *Client) StartVM(ctx context.Context, timeout int) error {
|
||||
taskID, err := c.StartVMAsync(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = c.Tasks().WaitForTask(ctx, *taskID, timeout, 5)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error waiting for VM start: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// StartVMAsync starts a virtual machine asynchronously.
|
||||
func (c *Client) StartVMAsync(ctx context.Context) (*string, error) {
|
||||
resBody := &StartResponseBody{}
|
||||
|
||||
err := c.DoRequest(ctx, http.MethodPost, c.ExpandPath("status/start"), nil, resBody)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error starting VM: %w", err)
|
||||
}
|
||||
|
||||
if resBody.Data == nil {
|
||||
return nil, api.ErrNoDataObjectInResponse
|
||||
}
|
||||
|
||||
return resBody.Data, nil
|
||||
}
|
||||
|
||||
// StopVM stops a virtual machine.
|
||||
func (c *Client) StopVM(ctx context.Context, timeout int) error {
|
||||
taskID, err := c.StopVMAsync(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = c.Tasks().WaitForTask(ctx, *taskID, timeout, 5)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error waiting for VM stop: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// StopVMAsync stops a virtual machine asynchronously.
|
||||
func (c *Client) StopVMAsync(ctx context.Context) (*string, error) {
|
||||
resBody := &StopResponseBody{}
|
||||
|
||||
err := c.DoRequest(ctx, http.MethodPost, c.ExpandPath("status/stop"), nil, resBody)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error stopping VM: %w", err)
|
||||
}
|
||||
|
||||
if resBody.Data == nil {
|
||||
return nil, api.ErrNoDataObjectInResponse
|
||||
}
|
||||
|
||||
return resBody.Data, nil
|
||||
}
|
||||
|
||||
// UpdateVM updates a virtual machine.
|
||||
func (c *Client) UpdateVM(ctx context.Context, d *UpdateRequestBody) error {
|
||||
err := c.DoRequest(ctx, http.MethodPut, c.ExpandPath("config"), d, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error updating VM: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateVMAsync updates a virtual machine asynchronously.
|
||||
func (c *Client) UpdateVMAsync(ctx context.Context, d *UpdateRequestBody) (*string, error) {
|
||||
resBody := &UpdateAsyncResponseBody{}
|
||||
|
||||
err := c.DoRequest(ctx, http.MethodPost, c.ExpandPath("config"), d, resBody)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error updating VM: %w", err)
|
||||
}
|
||||
|
||||
if resBody.Data == nil {
|
||||
return nil, api.ErrNoDataObjectInResponse
|
||||
}
|
||||
|
||||
return resBody.Data, nil
|
||||
}
|
||||
|
||||
// WaitForNetworkInterfacesFromVMAgent waits for a virtual machine's QEMU agent to publish the network interfaces.
|
||||
func (c *Client) WaitForNetworkInterfacesFromVMAgent(
|
||||
ctx context.Context,
|
||||
timeout int,
|
||||
delay int,
|
||||
waitForIP bool,
|
||||
) (*GetQEMUNetworkInterfacesResponseData, error) {
|
||||
timeDelay := int64(delay)
|
||||
timeMax := float64(timeout)
|
||||
timeStart := time.Now()
|
||||
timeElapsed := timeStart.Sub(timeStart)
|
||||
|
||||
for timeElapsed.Seconds() < timeMax {
|
||||
//nolint:nestif
|
||||
if int64(timeElapsed.Seconds())%timeDelay == 0 {
|
||||
data, err := c.GetVMNetworkInterfacesFromAgent(ctx)
|
||||
|
||||
if err == nil && data != nil && data.Result != nil {
|
||||
hasAnyGlobalUnicast := false
|
||||
|
||||
if waitForIP {
|
||||
for _, nic := range *data.Result {
|
||||
if nic.Name == "lo" {
|
||||
continue
|
||||
}
|
||||
|
||||
if nic.IPAddresses == nil ||
|
||||
(nic.IPAddresses != nil && len(*nic.IPAddresses) == 0) {
|
||||
break
|
||||
}
|
||||
|
||||
for _, addr := range *nic.IPAddresses {
|
||||
if ip := net.ParseIP(addr.Address); ip != nil && ip.IsGlobalUnicast() {
|
||||
hasAnyGlobalUnicast = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if hasAnyGlobalUnicast {
|
||||
return data, err
|
||||
}
|
||||
}
|
||||
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
|
||||
timeElapsed = time.Since(timeStart)
|
||||
|
||||
if ctx.Err() != nil {
|
||||
return nil, fmt.Errorf("error waiting for VM network interfaces: %w", ctx.Err())
|
||||
}
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf(
|
||||
"timeout while waiting for the QEMU agent on VM \"%d\" to publish the network interfaces",
|
||||
c.VMID,
|
||||
)
|
||||
}
|
||||
|
||||
// WaitForNoNetworkInterfacesFromVMAgent waits for a virtual machine's QEMU agent to unpublish the network interfaces.
|
||||
func (c *Client) WaitForNoNetworkInterfacesFromVMAgent(ctx context.Context, timeout int, delay int) error {
|
||||
timeDelay := int64(delay)
|
||||
timeMax := float64(timeout)
|
||||
timeStart := time.Now()
|
||||
timeElapsed := timeStart.Sub(timeStart)
|
||||
|
||||
for timeElapsed.Seconds() < timeMax {
|
||||
if int64(timeElapsed.Seconds())%timeDelay == 0 {
|
||||
_, err := c.GetVMNetworkInterfacesFromAgent(ctx)
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
|
||||
timeElapsed = time.Since(timeStart)
|
||||
|
||||
if ctx.Err() != nil {
|
||||
return fmt.Errorf("error waiting for VM network interfaces: %w", ctx.Err())
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Errorf(
|
||||
"timeout while waiting for the QEMU agent on VM \"%d\" to unpublish the network interfaces",
|
||||
c.VMID,
|
||||
)
|
||||
}
|
||||
|
||||
// WaitForVMConfigUnlock waits for a virtual machine configuration to become unlocked.
|
||||
func (c *Client) WaitForVMConfigUnlock(ctx context.Context, timeout int, delay int, ignoreErrorResponse bool) error {
|
||||
timeDelay := int64(delay)
|
||||
timeMax := float64(timeout)
|
||||
timeStart := time.Now()
|
||||
timeElapsed := timeStart.Sub(timeStart)
|
||||
|
||||
for timeElapsed.Seconds() < timeMax {
|
||||
if int64(timeElapsed.Seconds())%timeDelay == 0 {
|
||||
data, err := c.GetVMStatus(ctx)
|
||||
|
||||
if err != nil {
|
||||
if !ignoreErrorResponse {
|
||||
return err
|
||||
}
|
||||
} else if data.Lock == nil || *data.Lock == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
|
||||
timeElapsed = time.Since(timeStart)
|
||||
|
||||
if ctx.Err() != nil {
|
||||
return fmt.Errorf("error waiting for VM configuration to become unlocked: %w", ctx.Err())
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Errorf("timeout while waiting for VM \"%d\" configuration to become unlocked", c.VMID)
|
||||
}
|
||||
|
||||
// WaitForVMState waits for a virtual machine to reach a specific state.
|
||||
func (c *Client) WaitForVMState(ctx context.Context, state string, timeout int, delay int) error {
|
||||
state = strings.ToLower(state)
|
||||
|
||||
timeDelay := int64(delay)
|
||||
timeMax := float64(timeout)
|
||||
timeStart := time.Now()
|
||||
timeElapsed := timeStart.Sub(timeStart)
|
||||
|
||||
for timeElapsed.Seconds() < timeMax {
|
||||
if int64(timeElapsed.Seconds())%timeDelay == 0 {
|
||||
data, err := c.GetVMStatus(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if data.Status == state {
|
||||
return nil
|
||||
}
|
||||
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
|
||||
timeElapsed = time.Since(timeStart)
|
||||
|
||||
if ctx.Err() != nil {
|
||||
return fmt.Errorf("error waiting for VM state: %w", ctx.Err())
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Errorf("timeout while waiting for VM \"%d\" to enter the state \"%s\"", c.VMID, state)
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -1,60 +0,0 @@
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package vms
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/bpg/terraform-provider-proxmox/internal/types"
|
||||
)
|
||||
|
||||
func TestCustomStorageDevice_UnmarshalJSON(t *testing.T) {
|
||||
ds8gig := types.DiskSizeFromGigabytes(8)
|
||||
tests := []struct {
|
||||
name string
|
||||
line string
|
||||
want *CustomStorageDevice
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "simple volume",
|
||||
line: `"local-lvm:vm-2041-disk-0,discard=on,ssd=1,iothread=1,size=8G"`,
|
||||
want: &CustomStorageDevice{
|
||||
Discard: types.StrPtr("on"),
|
||||
Enabled: true,
|
||||
FileVolume: "local-lvm:vm-2041-disk-0",
|
||||
IOThread: types.BoolPtr(true),
|
||||
Size: &ds8gig,
|
||||
SSD: types.BoolPtr(true),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "raw volume type",
|
||||
line: `"nfs:2041/vm-2041-disk-0.raw,discard=ignore,ssd=1,iothread=1,size=8G"`,
|
||||
want: &CustomStorageDevice{
|
||||
Discard: types.StrPtr("ignore"),
|
||||
Enabled: true,
|
||||
FileVolume: "nfs:2041/vm-2041-disk-0.raw",
|
||||
Format: types.StrPtr("raw"),
|
||||
IOThread: types.BoolPtr(true),
|
||||
Size: &ds8gig,
|
||||
SSD: types.BoolPtr(true),
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
r := &CustomStorageDevice{}
|
||||
if err := r.UnmarshalJSON([]byte(tt.line)); (err != nil) != tt.wantErr {
|
||||
t.Errorf("UnmarshalJSON() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
require.Equal(t, tt.want, r)
|
||||
})
|
||||
}
|
||||
}
|
@ -1,16 +0,0 @@
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package pools
|
||||
|
||||
import (
|
||||
"github.com/bpg/terraform-provider-proxmox/proxmox/api"
|
||||
)
|
||||
|
||||
// Client is an interface for accessing the Proxmox pools API.
|
||||
type Client struct {
|
||||
api.Client
|
||||
}
|
@ -1,87 +0,0 @@
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package pools
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"sort"
|
||||
|
||||
"github.com/bpg/terraform-provider-proxmox/proxmox/api"
|
||||
)
|
||||
|
||||
// CreatePool creates a pool.
|
||||
func (c *Client) CreatePool(ctx context.Context, d *PoolCreateRequestBody) error {
|
||||
err := c.DoRequest(ctx, http.MethodPost, "pools", d, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error creating pool: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeletePool deletes a pool.
|
||||
func (c *Client) DeletePool(ctx context.Context, id string) error {
|
||||
err := c.DoRequest(ctx, http.MethodDelete, fmt.Sprintf("pools/%s", url.PathEscape(id)), nil, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error deleting pool: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetPool retrieves a pool.
|
||||
func (c *Client) GetPool(ctx context.Context, id string) (*PoolGetResponseData, error) {
|
||||
resBody := &PoolGetResponseBody{}
|
||||
|
||||
err := c.DoRequest(ctx, http.MethodGet, fmt.Sprintf("pools/%s", url.PathEscape(id)), nil, resBody)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error getting pool: %w", err)
|
||||
}
|
||||
|
||||
if resBody.Data == nil {
|
||||
return nil, api.ErrNoDataObjectInResponse
|
||||
}
|
||||
|
||||
sort.Slice(resBody.Data.Members, func(i, j int) bool {
|
||||
return resBody.Data.Members[i].ID < resBody.Data.Members[j].ID
|
||||
})
|
||||
|
||||
return resBody.Data, nil
|
||||
}
|
||||
|
||||
// ListPools retrieves a list of pools.
|
||||
func (c *Client) ListPools(ctx context.Context) ([]*PoolListResponseData, error) {
|
||||
resBody := &PoolListResponseBody{}
|
||||
|
||||
err := c.DoRequest(ctx, http.MethodGet, "pools", nil, resBody)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error listing pools: %w", err)
|
||||
}
|
||||
|
||||
if resBody.Data == nil {
|
||||
return nil, api.ErrNoDataObjectInResponse
|
||||
}
|
||||
|
||||
sort.Slice(resBody.Data, func(i, j int) bool {
|
||||
return resBody.Data[i].ID < resBody.Data[j].ID
|
||||
})
|
||||
|
||||
return resBody.Data, nil
|
||||
}
|
||||
|
||||
// UpdatePool updates a pool.
|
||||
func (c *Client) UpdatePool(ctx context.Context, id string, d *PoolUpdateRequestBody) error {
|
||||
err := c.DoRequest(ctx, http.MethodPut, fmt.Sprintf("pools/%s", url.PathEscape(id)), d, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error updating pool: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
@ -1,49 +0,0 @@
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package pools
|
||||
|
||||
// PoolCreateRequestBody contains the data for a pool create request.
|
||||
type PoolCreateRequestBody struct {
|
||||
Comment *string `json:"comment,omitempty" url:"comment,omitempty"`
|
||||
ID string `json:"groupid" url:"poolid"`
|
||||
}
|
||||
|
||||
// PoolGetResponseBody contains the body from a pool get response.
|
||||
type PoolGetResponseBody struct {
|
||||
Data *PoolGetResponseData `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
// PoolGetResponseData contains the data from a pool get response.
|
||||
type PoolGetResponseData struct {
|
||||
Comment *string `json:"comment,omitempty"`
|
||||
Members []VirtualEnvironmentPoolGetResponseMembers `json:"members,omitempty"`
|
||||
}
|
||||
|
||||
// VirtualEnvironmentPoolGetResponseMembers contains the members data from a pool get response.
|
||||
type VirtualEnvironmentPoolGetResponseMembers struct {
|
||||
ID string `json:"id"`
|
||||
Node string `json:"node"`
|
||||
DatastoreID *string `json:"storage,omitempty"`
|
||||
Type string `json:"type"`
|
||||
VMID *int `json:"vmid"`
|
||||
}
|
||||
|
||||
// PoolListResponseBody contains the body from a pool list response.
|
||||
type PoolListResponseBody struct {
|
||||
Data []*PoolListResponseData `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
// PoolListResponseData contains the data from a pool list response.
|
||||
type PoolListResponseData struct {
|
||||
Comment *string `json:"comment,omitempty"`
|
||||
ID string `json:"poolid"`
|
||||
}
|
||||
|
||||
// PoolUpdateRequestBody contains the data for an pool update request.
|
||||
type PoolUpdateRequestBody struct {
|
||||
Comment *string `json:"comment,omitempty" url:"comment,omitempty"`
|
||||
}
|
@ -1,343 +0,0 @@
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package ssh
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/terraform-plugin-log/tflog"
|
||||
"github.com/pkg/sftp"
|
||||
"github.com/skeema/knownhosts"
|
||||
"golang.org/x/crypto/ssh"
|
||||
"golang.org/x/crypto/ssh/agent"
|
||||
|
||||
"github.com/bpg/terraform-provider-proxmox/proxmox/api"
|
||||
"github.com/bpg/terraform-provider-proxmox/utils"
|
||||
)
|
||||
|
||||
// Client is an interface for performing SSH requests against the Proxmox Nodes.
|
||||
type Client interface {
|
||||
// ExecuteNodeCommands executes a command on a node.
|
||||
ExecuteNodeCommands(ctx context.Context, nodeName string, commands []string) error
|
||||
|
||||
// NodeUpload uploads a file to a node.
|
||||
NodeUpload(ctx context.Context, nodeName string,
|
||||
remoteFileDir string, fileUploadRequest *api.FileUploadRequest) error
|
||||
}
|
||||
|
||||
type client struct {
|
||||
username string
|
||||
password string
|
||||
agent bool
|
||||
agentSocket string
|
||||
nodeLookup NodeResolver
|
||||
}
|
||||
|
||||
// NewClient creates a new SSH client.
|
||||
func NewClient(
|
||||
username string, password string,
|
||||
agent bool, agentSocket string,
|
||||
nodeLookup NodeResolver,
|
||||
) (Client, error) {
|
||||
//goland:noinspection GoBoolExpressions
|
||||
if agent && runtime.GOOS != "linux" && runtime.GOOS != "darwin" && runtime.GOOS != "freebsd" {
|
||||
return nil, errors.New(
|
||||
"the ssh agent flag is only supported on POSIX systems, please set it to 'false'" +
|
||||
" or remove it from your provider configuration",
|
||||
)
|
||||
}
|
||||
|
||||
if nodeLookup == nil {
|
||||
return nil, errors.New("node lookup is required")
|
||||
}
|
||||
|
||||
return &client{
|
||||
username: username,
|
||||
password: password,
|
||||
agent: agent,
|
||||
agentSocket: agentSocket,
|
||||
nodeLookup: nodeLookup,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ExecuteNodeCommands executes commands on a given node.
|
||||
func (c *client) ExecuteNodeCommands(ctx context.Context, nodeName string, commands []string) error {
|
||||
ip, err := c.nodeLookup.Resolve(ctx, nodeName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to find node endpoint: %w", err)
|
||||
}
|
||||
|
||||
tflog.Debug(ctx, "executing commands on the node using SSH", map[string]interface{}{
|
||||
"node_address": ip,
|
||||
"commands": commands,
|
||||
})
|
||||
|
||||
closeOrLogError := utils.CloseOrLogError(ctx)
|
||||
|
||||
sshClient, err := c.openNodeShell(ctx, ip)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer closeOrLogError(sshClient)
|
||||
|
||||
sshSession, err := sshClient.NewSession()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create SSH session: %w", err)
|
||||
}
|
||||
|
||||
defer closeOrLogError(sshSession)
|
||||
|
||||
script := strings.Join(commands, " && \\\n")
|
||||
|
||||
output, err := sshSession.CombinedOutput(
|
||||
fmt.Sprintf(
|
||||
"/bin/bash -c '%s'",
|
||||
strings.ReplaceAll(script, "'", "'\"'\"'"),
|
||||
),
|
||||
)
|
||||
if err != nil {
|
||||
return errors.New(string(output))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *client) NodeUpload(
|
||||
ctx context.Context,
|
||||
nodeName string,
|
||||
remoteFileDir string,
|
||||
d *api.FileUploadRequest,
|
||||
) error {
|
||||
ip, err := c.nodeLookup.Resolve(ctx, nodeName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to find node endpoint: %w", err)
|
||||
}
|
||||
|
||||
tflog.Debug(ctx, "uploading file to the node datastore using SFTP", map[string]interface{}{
|
||||
"node_address": ip,
|
||||
"remote_dir": remoteFileDir,
|
||||
"file_name": d.FileName,
|
||||
"content_type": d.ContentType,
|
||||
})
|
||||
|
||||
fileInfo, err := d.File.Stat()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get file info: %w", err)
|
||||
}
|
||||
|
||||
fileSize := fileInfo.Size()
|
||||
|
||||
sshClient, err := c.openNodeShell(ctx, ip)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to open SSH client: %w", err)
|
||||
}
|
||||
|
||||
defer func(sshClient *ssh.Client) {
|
||||
e := sshClient.Close()
|
||||
if e != nil {
|
||||
tflog.Error(ctx, "failed to close SSH client", map[string]interface{}{
|
||||
"error": e,
|
||||
})
|
||||
}
|
||||
}(sshClient)
|
||||
|
||||
if d.ContentType != "" {
|
||||
remoteFileDir = filepath.Join(remoteFileDir, d.ContentType)
|
||||
}
|
||||
|
||||
remoteFilePath := strings.ReplaceAll(filepath.Join(remoteFileDir, d.FileName), `\`, `/`)
|
||||
|
||||
sftpClient, err := sftp.NewClient(sshClient)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create SFTP client: %w", err)
|
||||
}
|
||||
|
||||
defer func(sftpClient *sftp.Client) {
|
||||
e := sftpClient.Close()
|
||||
if e != nil {
|
||||
tflog.Error(ctx, "failed to close SFTP client", map[string]interface{}{
|
||||
"error": e,
|
||||
})
|
||||
}
|
||||
}(sftpClient)
|
||||
|
||||
err = sftpClient.MkdirAll(remoteFileDir)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create directory %s: %w", remoteFileDir, err)
|
||||
}
|
||||
|
||||
remoteFile, err := sftpClient.Create(remoteFilePath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create file %s: %w", remoteFilePath, err)
|
||||
}
|
||||
|
||||
defer func(remoteFile *sftp.File) {
|
||||
e := remoteFile.Close()
|
||||
if e != nil {
|
||||
tflog.Error(ctx, "failed to close remote file", map[string]interface{}{
|
||||
"error": e,
|
||||
})
|
||||
}
|
||||
}(remoteFile)
|
||||
|
||||
bytesUploaded, err := remoteFile.ReadFrom(d.File)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to upload file %s: %w", remoteFilePath, err)
|
||||
}
|
||||
|
||||
if bytesUploaded != fileSize {
|
||||
return fmt.Errorf("failed to upload file %s: uploaded %d bytes, expected %d bytes",
|
||||
remoteFilePath, bytesUploaded, fileSize)
|
||||
}
|
||||
|
||||
tflog.Debug(ctx, "uploaded file to datastore", map[string]interface{}{
|
||||
"remote_file_path": remoteFilePath,
|
||||
"size": bytesUploaded,
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// openNodeShell establishes a new SSH connection to a node.
|
||||
func (c *client) openNodeShell(ctx context.Context, nodeAddress string) (*ssh.Client, error) {
|
||||
homeDir, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to determine the home directory: %w", err)
|
||||
}
|
||||
|
||||
sshHost := fmt.Sprintf("%s:22", nodeAddress)
|
||||
|
||||
sshPath := path.Join(homeDir, ".ssh")
|
||||
if _, err = os.Stat(sshPath); os.IsNotExist(err) {
|
||||
e := os.Mkdir(sshPath, 0o700)
|
||||
if e != nil {
|
||||
return nil, fmt.Errorf("failed to create %s: %w", sshPath, e)
|
||||
}
|
||||
}
|
||||
|
||||
khPath := path.Join(sshPath, "known_hosts")
|
||||
if _, err = os.Stat(khPath); os.IsNotExist(err) {
|
||||
e := os.WriteFile(khPath, []byte{}, 0o600)
|
||||
if e != nil {
|
||||
return nil, fmt.Errorf("failed to create %s: %w", khPath, e)
|
||||
}
|
||||
}
|
||||
|
||||
kh, err := knownhosts.New(khPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read %s: %w", khPath, err)
|
||||
}
|
||||
|
||||
// Create a custom permissive hostkey callback which still errors on hosts
|
||||
// with changed keys, but allows unknown hosts and adds them to known_hosts
|
||||
cb := ssh.HostKeyCallback(func(hostname string, remote net.Addr, key ssh.PublicKey) error {
|
||||
kherr := kh(hostname, remote, key)
|
||||
if knownhosts.IsHostKeyChanged(kherr) {
|
||||
return fmt.Errorf("REMOTE HOST IDENTIFICATION HAS CHANGED for host %s! This may indicate a MitM attack", hostname)
|
||||
}
|
||||
|
||||
if knownhosts.IsHostUnknown(kherr) {
|
||||
f, ferr := os.OpenFile(khPath, os.O_APPEND|os.O_WRONLY, 0o600)
|
||||
if ferr == nil {
|
||||
defer utils.CloseOrLogError(ctx)(f)
|
||||
ferr = knownhosts.WriteKnownHost(f, hostname, remote, key)
|
||||
}
|
||||
if ferr == nil {
|
||||
tflog.Info(ctx, fmt.Sprintf("Added host %s to known_hosts", hostname))
|
||||
} else {
|
||||
tflog.Error(ctx, fmt.Sprintf("Failed to add host %s to known_hosts", hostname), map[string]interface{}{
|
||||
"error": kherr,
|
||||
})
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return kherr
|
||||
})
|
||||
|
||||
sshConfig := &ssh.ClientConfig{
|
||||
User: c.username,
|
||||
Auth: []ssh.AuthMethod{ssh.Password(c.password)},
|
||||
HostKeyCallback: cb,
|
||||
HostKeyAlgorithms: kh.HostKeyAlgorithms(sshHost),
|
||||
}
|
||||
|
||||
tflog.Info(ctx, fmt.Sprintf("agent is set to %t", c.agent))
|
||||
|
||||
var sshClient *ssh.Client
|
||||
if c.agent {
|
||||
sshClient, err = c.createSSHClientAgent(ctx, cb, kh, sshHost)
|
||||
if err != nil {
|
||||
tflog.Error(ctx, "Failed ssh connection through agent, "+
|
||||
"falling back to password authentication",
|
||||
map[string]interface{}{
|
||||
"error": err,
|
||||
})
|
||||
} else {
|
||||
return sshClient, nil
|
||||
}
|
||||
}
|
||||
|
||||
sshClient, err = ssh.Dial("tcp", sshHost, sshConfig)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to dial %s: %w", sshHost, err)
|
||||
}
|
||||
|
||||
tflog.Debug(ctx, "SSH connection established", map[string]interface{}{
|
||||
"host": sshHost,
|
||||
"user": c.username,
|
||||
})
|
||||
|
||||
return sshClient, nil
|
||||
}
|
||||
|
||||
// createSSHClientAgent establishes an ssh connection through the agent authentication mechanism.
|
||||
func (c *client) createSSHClientAgent(
|
||||
ctx context.Context,
|
||||
cb ssh.HostKeyCallback,
|
||||
kh knownhosts.HostKeyCallback,
|
||||
sshHost string,
|
||||
) (*ssh.Client, error) {
|
||||
if c.agentSocket == "" {
|
||||
return nil, errors.New("failed connecting to SSH agent socket: the socket file is not defined, " +
|
||||
"authentication will fall back to password")
|
||||
}
|
||||
|
||||
conn, err := net.Dial("unix", c.agentSocket)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed connecting to SSH auth socket '%s': %w", c.agentSocket, err)
|
||||
}
|
||||
|
||||
ag := agent.NewClient(conn)
|
||||
|
||||
sshConfig := &ssh.ClientConfig{
|
||||
User: c.username,
|
||||
Auth: []ssh.AuthMethod{ssh.PublicKeysCallback(ag.Signers), ssh.Password(c.password)},
|
||||
HostKeyCallback: cb,
|
||||
HostKeyAlgorithms: kh.HostKeyAlgorithms(sshHost),
|
||||
}
|
||||
|
||||
sshClient, err := ssh.Dial("tcp", sshHost, sshConfig)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to dial %s: %w", sshHost, err)
|
||||
}
|
||||
|
||||
tflog.Debug(ctx, "SSH connection established", map[string]interface{}{
|
||||
"host": sshHost,
|
||||
"user": c.username,
|
||||
})
|
||||
|
||||
return sshClient, nil
|
||||
}
|
@ -1,16 +0,0 @@
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package ssh
|
||||
|
||||
import (
|
||||
"context"
|
||||
)
|
||||
|
||||
// NodeResolver is an interface for resolving node names to IP addresses to use for SSH connection.
|
||||
type NodeResolver interface {
|
||||
Resolve(ctx context.Context, nodeName string) (string, error)
|
||||
}
|
@ -1,16 +0,0 @@
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package storage
|
||||
|
||||
import (
|
||||
"github.com/bpg/terraform-provider-proxmox/proxmox/api"
|
||||
)
|
||||
|
||||
// Client is an interface for accessing the Proxmox storage API.
|
||||
type Client struct {
|
||||
api.Client
|
||||
}
|
@ -1,54 +0,0 @@
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package storage
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
// GetDatastore retrieves information about a datastore.
|
||||
/*
|
||||
Using undocumented API endpoints is not recommended, but sometimes it's the only way to get things done.
|
||||
$ pvesh get /storage/local
|
||||
┌─────────┬───────────────────────────────────────────┐
|
||||
│ key │ value │
|
||||
╞═════════╪═══════════════════════════════════════════╡
|
||||
│ content │ images,vztmpl,iso,backup,snippets,rootdir │
|
||||
├─────────┼───────────────────────────────────────────┤
|
||||
│ digest │ 5b65ede80f34631d6039e6922845cfa4abc956be │
|
||||
├─────────┼───────────────────────────────────────────┤
|
||||
│ path │ /var/lib/vz │
|
||||
├─────────┼───────────────────────────────────────────┤
|
||||
│ shared │ 0 │
|
||||
├─────────┼───────────────────────────────────────────┤
|
||||
│ storage │ local │
|
||||
├─────────┼───────────────────────────────────────────┤
|
||||
│ type │ dir │
|
||||
└─────────┴───────────────────────────────────────────┘.
|
||||
*/
|
||||
func (c *Client) GetDatastore(
|
||||
ctx context.Context,
|
||||
datastoreID string,
|
||||
) (*DatastoreGetResponseData, error) {
|
||||
resBody := &DatastoreGetResponseBody{}
|
||||
|
||||
err := c.DoRequest(
|
||||
ctx,
|
||||
http.MethodGet,
|
||||
fmt.Sprintf("storage/%s", url.PathEscape(datastoreID)),
|
||||
nil,
|
||||
resBody,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error retrieving datastore %s: %w", datastoreID, err)
|
||||
}
|
||||
|
||||
return resBody.Data, nil
|
||||
}
|
@ -1,26 +0,0 @@
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package storage
|
||||
|
||||
import (
|
||||
"github.com/bpg/terraform-provider-proxmox/internal/types"
|
||||
)
|
||||
|
||||
// DatastoreGetResponseBody contains the body from a datastore get response.
|
||||
type DatastoreGetResponseBody struct {
|
||||
Data *DatastoreGetResponseData `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
// DatastoreGetResponseData contains the data from a datastore get response.
|
||||
type DatastoreGetResponseData struct {
|
||||
Content types.CustomCommaSeparatedList `json:"content,omitempty" url:"content,omitempty,comma"`
|
||||
Digest *string `json:"digest,omitempty"`
|
||||
Path *string `json:"path,omitempty"`
|
||||
Shared *types.CustomBool `json:"shared,omitempty"`
|
||||
Storage *string `json:"storage,omitempty"`
|
||||
Type *string `json:"type,omitempty"`
|
||||
}
|
@ -1,16 +0,0 @@
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package version
|
||||
|
||||
import (
|
||||
"github.com/bpg/terraform-provider-proxmox/proxmox/api"
|
||||
)
|
||||
|
||||
// Client is an interface for accessing the Proxmox version API.
|
||||
type Client struct {
|
||||
api.Client
|
||||
}
|
@ -1,31 +0,0 @@
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package version
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/bpg/terraform-provider-proxmox/proxmox/api"
|
||||
)
|
||||
|
||||
// Version retrieves the version information.
|
||||
func (c *Client) Version(ctx context.Context) (*ResponseData, error) {
|
||||
resBody := &ResponseBody{}
|
||||
|
||||
err := c.DoRequest(ctx, http.MethodGet, "version", nil, resBody)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get version information: %w", err)
|
||||
}
|
||||
|
||||
if resBody.Data == nil {
|
||||
return nil, api.ErrNoDataObjectInResponse
|
||||
}
|
||||
|
||||
return resBody.Data, nil
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package version
|
||||
|
||||
// ResponseBody contains the body from a version response.
|
||||
type ResponseBody struct {
|
||||
Data *ResponseData `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
// ResponseData contains the data from a version response.
|
||||
type ResponseData struct {
|
||||
Keyboard string `json:"keyboard"`
|
||||
Release string `json:"release"`
|
||||
RepositoryID string `json:"repoid"`
|
||||
Version string `json:"version"`
|
||||
}
|
@ -9,31 +9,31 @@ package proxmoxtf
|
||||
import (
|
||||
"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
|
||||
}
|
||||
|
@ -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"
|
||||
)
|
||||
|
@ -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 (
|
||||
|
@ -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,
|
||||
|
@ -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 (
|
||||
|
@ -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,
|
||||
})
|
||||
}
|
||||
|
@ -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 (
|
||||
|
@ -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,
|
||||
|
@ -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 (
|
||||
|
@ -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,
|
||||
})
|
||||
}
|
||||
|
@ -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
Loading…
Reference in New Issue
Block a user