mirror of
https://github.com/bpg/terraform-provider-proxmox.git
synced 2025-06-29 18:21:10 +00:00
feat: API client cleanup and refactoring (#323)
* cleanup 1
* continue refactoring
* more refactoring
* move VMs under nodes
* move container and other apis under nodes
* cleanups
* enabled revive.exported linter & add comments to exported stuff
* enable godot linter
* enable wsl linter
* enable thelper linter
* enable govet linter
* cleanup after rebase
* cleanup after rebase
* extract SSH ops into a separate interface
* fix linter error
* move ssh code to its own package
* cleaning up VirtualEnvironmentClient receivers
* on the finish line
* not sure what else I forgot... 🤔
* fix ssh connection and upload
* renaming client interfaces
* final cleanups
This commit is contained in:
parent
23585570ab
commit
1f006aa82b
@ -9,6 +9,9 @@ issues:
|
||||
# Maximum count of issues with the same text. Set to 0 to disable.
|
||||
# Default is 3.
|
||||
max-same-issues: 0
|
||||
include:
|
||||
- EXC0012
|
||||
- EXC0014
|
||||
exclude-rules:
|
||||
# Exclude duplicate code and function length and complexity checking in test
|
||||
# files (due to common repeats and long functions in test code)
|
||||
@ -43,6 +46,11 @@ linters-settings:
|
||||
funlen:
|
||||
lines: 80
|
||||
statements: 60
|
||||
errcheck:
|
||||
check-blank: true
|
||||
ignoretests: true
|
||||
revive:
|
||||
exported: ["checkPrivateReceivers"]
|
||||
linters:
|
||||
enable-all: true
|
||||
disable:
|
||||
@ -53,6 +61,7 @@ linters:
|
||||
- ifshort
|
||||
- interfacer
|
||||
- maligned
|
||||
- nosnakecase
|
||||
- rowserrcheck
|
||||
- scopelint
|
||||
- structcheck
|
||||
@ -63,24 +72,16 @@ linters:
|
||||
- forcetypeassert
|
||||
- funlen
|
||||
- gocognit
|
||||
- govet
|
||||
# others
|
||||
- exhaustivestruct
|
||||
- exhaustruct
|
||||
- gci
|
||||
- gochecknoinits
|
||||
- godot
|
||||
- goerr113
|
||||
- gomnd
|
||||
- grouper
|
||||
- ireturn
|
||||
- maintidx
|
||||
- nlreturn
|
||||
- nonamedreturns
|
||||
- nosnakecase
|
||||
- tagliatelle
|
||||
- testpackage
|
||||
- thelper
|
||||
- varnamelen
|
||||
- wsl
|
||||
fast: false
|
||||
|
50
proxmox/access/acl.go
Normal file
50
proxmox/access/acl.go
Normal file
@ -0,0 +1,50 @@
|
||||
/*
|
||||
* 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,18 +1,20 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
/*
|
||||
* 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/. */
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package proxmox
|
||||
package access
|
||||
|
||||
import "github.com/bpg/terraform-provider-proxmox/proxmox/types"
|
||||
|
||||
// VirtualEnvironmentACLGetResponseBody contains the body from an access control list response.
|
||||
type VirtualEnvironmentACLGetResponseBody struct {
|
||||
Data []*VirtualEnvironmentACLGetResponseData `json:"data,omitempty"`
|
||||
// ACLGetResponseBody contains the body from an access control list response.
|
||||
type ACLGetResponseBody struct {
|
||||
Data []*ACLGetResponseData `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
// VirtualEnvironmentACLGetResponseData contains the data from an access control list response.
|
||||
type VirtualEnvironmentACLGetResponseData struct {
|
||||
// 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"`
|
||||
@ -20,8 +22,8 @@ type VirtualEnvironmentACLGetResponseData struct {
|
||||
UserOrGroupID string `json:"ugid"`
|
||||
}
|
||||
|
||||
// VirtualEnvironmentACLUpdateRequestBody contains the data for an access control list update request.
|
||||
type VirtualEnvironmentACLUpdateRequestBody struct {
|
||||
// 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"`
|
23
proxmox/access/client.go
Normal file
23
proxmox/access/client.go
Normal file
@ -0,0 +1,23 @@
|
||||
/*
|
||||
* 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)
|
||||
}
|
93
proxmox/access/groups.go
Normal file
93
proxmox/access/groups.go
Normal file
@ -0,0 +1,93 @@
|
||||
/*
|
||||
* 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
|
||||
}
|
40
proxmox/access/groups_types.go
Normal file
40
proxmox/access/groups_types.go
Normal file
@ -0,0 +1,40 @@
|
||||
/*
|
||||
* 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"`
|
||||
}
|
100
proxmox/access/roles.go
Normal file
100
proxmox/access/roles.go
Normal file
@ -0,0 +1,100 @@
|
||||
/*
|
||||
* 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"
|
||||
"github.com/bpg/terraform-provider-proxmox/proxmox/types"
|
||||
)
|
||||
|
||||
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
|
||||
}
|
37
proxmox/access/roles_types.go
Normal file
37
proxmox/access/roles_types.go
Normal file
@ -0,0 +1,37 @@
|
||||
/*
|
||||
* 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/proxmox/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"`
|
||||
}
|
128
proxmox/access/users.go
Normal file
128
proxmox/access/users.go
Normal file
@ -0,0 +1,128 @@
|
||||
/*
|
||||
* 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/proxmox/api"
|
||||
"github.com/bpg/terraform-provider-proxmox/proxmox/types"
|
||||
)
|
||||
|
||||
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,19 +1,21 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
/*
|
||||
* 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/. */
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package proxmox
|
||||
package access
|
||||
|
||||
import "github.com/bpg/terraform-provider-proxmox/proxmox/types"
|
||||
|
||||
// VirtualEnvironmentUserChangePasswordRequestBody contains the data for a user password change request.
|
||||
type VirtualEnvironmentUserChangePasswordRequestBody struct {
|
||||
// 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"`
|
||||
}
|
||||
|
||||
// VirtualEnvironmentUserCreateRequestBody contains the data for an user create request.
|
||||
type VirtualEnvironmentUserCreateRequestBody struct {
|
||||
// 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"`
|
||||
@ -26,13 +28,13 @@ type VirtualEnvironmentUserCreateRequestBody struct {
|
||||
Password string `json:"password" url:"password"`
|
||||
}
|
||||
|
||||
// VirtualEnvironmentUserGetResponseBody contains the body from an user get response.
|
||||
type VirtualEnvironmentUserGetResponseBody struct {
|
||||
Data *VirtualEnvironmentUserGetResponseData `json:"data,omitempty"`
|
||||
// UserGetResponseBody contains the body from a user get response.
|
||||
type UserGetResponseBody struct {
|
||||
Data *UserGetResponseData `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
// VirtualEnvironmentUserGetResponseData contains the data from an user get response.
|
||||
type VirtualEnvironmentUserGetResponseData struct {
|
||||
// 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"`
|
||||
@ -43,13 +45,13 @@ type VirtualEnvironmentUserGetResponseData struct {
|
||||
LastName *string `json:"lastname,omitempty"`
|
||||
}
|
||||
|
||||
// VirtualEnvironmentUserListResponseBody contains the body from an user list response.
|
||||
type VirtualEnvironmentUserListResponseBody struct {
|
||||
Data []*VirtualEnvironmentUserListResponseData `json:"data,omitempty"`
|
||||
// UserListResponseBody contains the body from a user list response.
|
||||
type UserListResponseBody struct {
|
||||
Data []*UserListResponseData `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
// VirtualEnvironmentUserListResponseData contains the data from an user list response.
|
||||
type VirtualEnvironmentUserListResponseData struct {
|
||||
// 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"`
|
||||
@ -61,8 +63,8 @@ type VirtualEnvironmentUserListResponseData struct {
|
||||
LastName *string `json:"lastname,omitempty"`
|
||||
}
|
||||
|
||||
// VirtualEnvironmentUserUpdateRequestBody contains the data for an user update request.
|
||||
type VirtualEnvironmentUserUpdateRequestBody struct {
|
||||
// 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"`
|
@ -1,8 +1,10 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
/*
|
||||
* 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/. */
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package proxmox
|
||||
package api
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@ -14,40 +16,37 @@ import (
|
||||
"net/url"
|
||||
|
||||
"github.com/hashicorp/terraform-plugin-log/tflog"
|
||||
)
|
||||
|
||||
const (
|
||||
// DefaultRootAccount contains the default username and realm for the root account.
|
||||
DefaultRootAccount = "root@pam"
|
||||
"github.com/bpg/terraform-provider-proxmox/utils"
|
||||
)
|
||||
|
||||
// Authenticate authenticates against the specified endpoint.
|
||||
func (c *VirtualEnvironmentClient) Authenticate(ctx context.Context, reset bool) error {
|
||||
func (c *client) Authenticate(ctx context.Context, reset bool) error {
|
||||
if c.authenticationData != nil && !reset {
|
||||
return nil
|
||||
}
|
||||
|
||||
var reqBody *bytes.Buffer
|
||||
|
||||
if c.OTP != nil {
|
||||
if c.otp != nil {
|
||||
reqBody = bytes.NewBufferString(fmt.Sprintf(
|
||||
"username=%s&password=%s&otp=%s",
|
||||
url.QueryEscape(c.Username),
|
||||
url.QueryEscape(c.Password),
|
||||
url.QueryEscape(*c.OTP),
|
||||
url.QueryEscape(c.username),
|
||||
url.QueryEscape(c.password),
|
||||
url.QueryEscape(*c.otp),
|
||||
))
|
||||
} else {
|
||||
reqBody = bytes.NewBufferString(fmt.Sprintf(
|
||||
"username=%s&password=%s",
|
||||
url.QueryEscape(c.Username),
|
||||
url.QueryEscape(c.Password),
|
||||
url.QueryEscape(c.username),
|
||||
url.QueryEscape(c.password),
|
||||
))
|
||||
}
|
||||
|
||||
req, err := http.NewRequestWithContext(
|
||||
ctx,
|
||||
http.MethodPost,
|
||||
fmt.Sprintf("%s/%s/access/ticket", c.Endpoint, basePathJSONAPI),
|
||||
fmt.Sprintf("%s/%s/access/ticket", c.endpoint, basePathJSONAPI),
|
||||
reqBody,
|
||||
)
|
||||
if err != nil {
|
||||
@ -64,12 +63,14 @@ func (c *VirtualEnvironmentClient) Authenticate(ctx context.Context, reset bool)
|
||||
return fmt.Errorf("failed to retrieve authentication response: %w", err)
|
||||
}
|
||||
|
||||
err = c.ValidateResponseCode(res)
|
||||
defer utils.CloseOrLogError(ctx)(res.Body)
|
||||
|
||||
err = c.validateResponseCode(res)
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("failed to authenticate: %w", err)
|
||||
}
|
||||
|
||||
resBody := VirtualEnvironmentAuthenticationResponseBody{}
|
||||
resBody := AuthenticationResponseBody{}
|
||||
err = json.NewDecoder(res.Body).Decode(&resBody)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to decode authentication response, %w", err)
|
||||
@ -99,10 +100,10 @@ func (c *VirtualEnvironmentClient) Authenticate(ctx context.Context, reset bool)
|
||||
}
|
||||
|
||||
// AuthenticateRequest adds authentication data to a new request.
|
||||
func (c *VirtualEnvironmentClient) AuthenticateRequest(ctx context.Context, req *http.Request) error {
|
||||
func (c *client) AuthenticateRequest(ctx context.Context, req *http.Request) error {
|
||||
err := c.Authenticate(ctx, false)
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("failed to authenticate: %w", err)
|
||||
}
|
||||
|
||||
req.AddCookie(&http.Cookie{
|
||||
@ -110,7 +111,7 @@ func (c *VirtualEnvironmentClient) AuthenticateRequest(ctx context.Context, req
|
||||
Value: *c.authenticationData.Ticket,
|
||||
})
|
||||
|
||||
if req.Method != "GET" {
|
||||
if req.Method != http.MethodGet {
|
||||
req.Header.Add("CSRFPreventionToken", *c.authenticationData.CSRFPreventionToken)
|
||||
}
|
||||
|
32
proxmox/api/authentication_types.go
Normal file
32
proxmox/api/authentication_types.go
Normal file
@ -0,0 +1,32 @@
|
||||
/*
|
||||
* 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/proxmox/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"`
|
||||
}
|
@ -4,7 +4,7 @@
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package proxmox
|
||||
package api
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@ -16,19 +16,35 @@ import (
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"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"
|
||||
)
|
||||
|
||||
// NewVirtualEnvironmentClient creates and initializes a VirtualEnvironmentClient instance.
|
||||
func NewVirtualEnvironmentClient(
|
||||
endpoint, username, password, otp string,
|
||||
insecure bool, sshUsername string, sshPassword string, sshAgent bool, sshAgentSocket string,
|
||||
) (*VirtualEnvironmentClient, error) {
|
||||
const (
|
||||
basePathJSONAPI = "api2/json"
|
||||
)
|
||||
|
||||
// VirtualEnvironmentClient implements an API client for the Proxmox Virtual Environment API.
|
||||
type client struct {
|
||||
endpoint string
|
||||
insecure bool
|
||||
otp *string
|
||||
password string
|
||||
username string
|
||||
|
||||
authenticationData *AuthenticationResponseData
|
||||
httpClient *http.Client
|
||||
}
|
||||
|
||||
// NewClient creates and initializes a VirtualEnvironmentClient instance.
|
||||
func NewClient(
|
||||
endpoint, username, password, otp string, insecure bool,
|
||||
) (Client, error) {
|
||||
u, err := url.ParseRequestURI(endpoint)
|
||||
if err != nil {
|
||||
return nil, errors.New(
|
||||
@ -71,43 +87,25 @@ func NewVirtualEnvironmentClient(
|
||||
InsecureSkipVerify: insecure, //nolint:gosec
|
||||
},
|
||||
}
|
||||
|
||||
if logging.IsDebugOrHigher() {
|
||||
transport = logging.NewLoggingHTTPTransport(transport)
|
||||
}
|
||||
|
||||
httpClient := &http.Client{Transport: transport}
|
||||
|
||||
if sshUsername == "" {
|
||||
sshUsername = strings.Split(username, "@")[0]
|
||||
}
|
||||
|
||||
if sshPassword == "" {
|
||||
sshPassword = password
|
||||
}
|
||||
|
||||
if sshAgent && 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",
|
||||
)
|
||||
}
|
||||
|
||||
return &VirtualEnvironmentClient{
|
||||
Endpoint: strings.TrimRight(u.String(), "/"),
|
||||
Insecure: insecure,
|
||||
OTP: pOTP,
|
||||
Password: password,
|
||||
Username: username,
|
||||
SSHUsername: sshUsername,
|
||||
SSHPassword: sshPassword,
|
||||
SSHAgent: sshAgent,
|
||||
SSHAgentSocket: sshAgentSocket,
|
||||
httpClient: httpClient,
|
||||
return &client{
|
||||
endpoint: strings.TrimRight(u.String(), "/"),
|
||||
insecure: insecure,
|
||||
otp: pOTP,
|
||||
password: password,
|
||||
username: username,
|
||||
httpClient: httpClient,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// DoRequest performs a HTTP request against a JSON API endpoint.
|
||||
func (c *VirtualEnvironmentClient) DoRequest(
|
||||
func (c *client) DoRequest(
|
||||
ctx context.Context,
|
||||
method, path string,
|
||||
requestBody, responseBody interface{},
|
||||
@ -119,7 +117,7 @@ func (c *VirtualEnvironmentClient) DoRequest(
|
||||
reqBodyType := ""
|
||||
|
||||
if requestBody != nil {
|
||||
multipartData, multipart := requestBody.(*VirtualEnvironmentMultiPartData)
|
||||
multipartData, multipart := requestBody.(*MultiPartData)
|
||||
pipedBodyReader, pipedBody := requestBody.(*io.PipeReader)
|
||||
|
||||
if multipart {
|
||||
@ -158,7 +156,7 @@ func (c *VirtualEnvironmentClient) DoRequest(
|
||||
req, err := http.NewRequestWithContext(
|
||||
ctx,
|
||||
method,
|
||||
fmt.Sprintf("%s/%s/%s", c.Endpoint, basePathJSONAPI, modifiedPath),
|
||||
fmt.Sprintf("%s/%s/%s", c.endpoint, basePathJSONAPI, modifiedPath),
|
||||
reqBodyReader,
|
||||
)
|
||||
if err != nil {
|
||||
@ -201,9 +199,9 @@ func (c *VirtualEnvironmentClient) DoRequest(
|
||||
return fErr
|
||||
}
|
||||
|
||||
defer CloseOrLogError(ctx)(res.Body)
|
||||
defer utils.CloseOrLogError(ctx)(res.Body)
|
||||
|
||||
err = c.ValidateResponseCode(res)
|
||||
err = c.validateResponseCode(res)
|
||||
if err != nil {
|
||||
tflog.Warn(ctx, err.Error())
|
||||
return err
|
||||
@ -232,12 +230,21 @@ func (c *VirtualEnvironmentClient) DoRequest(
|
||||
return nil
|
||||
}
|
||||
|
||||
// ValidateResponseCode ensures that a response is valid.
|
||||
func (c *VirtualEnvironmentClient) ValidateResponseCode(res *http.Response) error {
|
||||
// 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.username == "root@pam"
|
||||
}
|
||||
|
||||
// validateResponseCode ensures that a response is valid.
|
||||
func (c *client) validateResponseCode(res *http.Response) error {
|
||||
if res.StatusCode < 200 || res.StatusCode >= 300 {
|
||||
status := strings.TrimPrefix(res.Status, fmt.Sprintf("%d ", res.StatusCode))
|
||||
|
||||
errRes := &VirtualEnvironmentErrorResponseBody{}
|
||||
errRes := &ErrorResponseBody{}
|
||||
err := json.NewDecoder(res.Body).Decode(errRes)
|
||||
|
||||
if err == nil && errRes.Errors != nil {
|
56
proxmox/api/client_types.go
Normal file
56
proxmox/api/client_types.go
Normal file
@ -0,0 +1,56 @@
|
||||
/*
|
||||
* 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"
|
||||
"errors"
|
||||
"io"
|
||||
"os"
|
||||
)
|
||||
|
||||
// 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")
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
95
proxmox/client.go
Normal file
95
proxmox/client.go
Normal file
@ -0,0 +1,95 @@
|
||||
/*
|
||||
* 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
|
||||
}
|
@ -9,19 +9,22 @@ 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"
|
||||
"github.com/bpg/terraform-provider-proxmox/proxmox/types"
|
||||
)
|
||||
|
||||
// Client is an interface for accessing the Proxmox cluster API.
|
||||
type Client struct {
|
||||
types.Client
|
||||
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},
|
||||
|
@ -11,6 +11,22 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"sync"
|
||||
|
||||
"github.com/hashicorp/terraform-plugin-log/tflog"
|
||||
|
||||
"github.com/bpg/terraform-provider-proxmox/proxmox/api"
|
||||
)
|
||||
|
||||
const (
|
||||
getVMIDStep = 1
|
||||
)
|
||||
|
||||
var (
|
||||
//nolint:gochecknoglobals
|
||||
getVMIDCounter = -1
|
||||
//nolint:gochecknoglobals
|
||||
getVMIDCounterMutex = &sync.Mutex{}
|
||||
)
|
||||
|
||||
// GetNextID retrieves the next free VM identifier for the cluster.
|
||||
@ -20,14 +36,61 @@ func (c *Client) GetNextID(ctx context.Context, vmID *int) (*int, error) {
|
||||
}
|
||||
|
||||
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, errors.New("the server did not include a data object in the response")
|
||||
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")
|
||||
}
|
||||
|
@ -12,6 +12,7 @@ import (
|
||||
"github.com/bpg/terraform-provider-proxmox/proxmox/firewall"
|
||||
)
|
||||
|
||||
// API is an interface for managing cluster firewall.
|
||||
type API interface {
|
||||
firewall.API
|
||||
SecurityGroup
|
||||
@ -19,6 +20,7 @@ type API interface {
|
||||
SecurityGroup(group string) firewall.Rule
|
||||
}
|
||||
|
||||
// Client is an interface for accessing the Proxmox cluster firewall API.
|
||||
type Client struct {
|
||||
firewall.Client
|
||||
}
|
||||
@ -28,6 +30,7 @@ type groupClient struct {
|
||||
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
|
||||
|
@ -8,125 +8,39 @@ package firewall
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/bpg/terraform-provider-proxmox/proxmox/types"
|
||||
"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)
|
||||
}
|
||||
|
||||
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"`
|
||||
}
|
||||
|
||||
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"`
|
||||
}
|
||||
|
||||
type OptionsGetResponseBody struct {
|
||||
Data *OptionsGetResponseData `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// 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, fmt.Errorf("the server did not include a data object in the response")
|
||||
return nil, api.ErrNoDataObjectInResponse
|
||||
}
|
||||
|
||||
return resBody.Data, nil
|
||||
|
109
proxmox/cluster/firewall/options_types.go
Normal file
109
proxmox/cluster/firewall/options_types.go
Normal file
@ -0,0 +1,109 @@
|
||||
/*
|
||||
* 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/proxmox/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
|
||||
}
|
@ -8,13 +8,15 @@ package firewall
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"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)
|
||||
@ -22,34 +24,6 @@ type SecurityGroup interface {
|
||||
DeleteGroup(ctx context.Context, group string) error
|
||||
}
|
||||
|
||||
// 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"`
|
||||
}
|
||||
|
||||
func (c *Client) securityGroupsPath() string {
|
||||
return "cluster/firewall/groups"
|
||||
}
|
||||
@ -60,19 +34,21 @@ func (c *Client) CreateGroup(ctx context.Context, d *GroupCreateRequestBody) err
|
||||
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, errors.New("the server did not include a data object in the response")
|
||||
return nil, api.ErrNoDataObjectInResponse
|
||||
}
|
||||
|
||||
sort.Slice(resBody.Data, func(i, j int) bool {
|
||||
@ -94,6 +70,7 @@ func (c *Client) UpdateGroup(ctx context.Context, d *GroupUpdateRequestBody) err
|
||||
if err != nil {
|
||||
return fmt.Errorf("error updating security group: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -109,5 +86,6 @@ func (c *Client) DeleteGroup(ctx context.Context, group string) error {
|
||||
if err != nil {
|
||||
return fmt.Errorf("error deleting security group '%s': %w", group, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
35
proxmox/cluster/firewall/security_groups_types.go
Normal file
35
proxmox/cluster/firewall/security_groups_types.go
Normal file
@ -0,0 +1,35 @@
|
||||
/*
|
||||
* 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,410 +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 (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/terraform-plugin-log/tflog"
|
||||
"golang.org/x/crypto/ssh"
|
||||
|
||||
"github.com/pkg/sftp"
|
||||
)
|
||||
|
||||
// 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 *VirtualEnvironmentClient) 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, err
|
||||
}
|
||||
|
||||
return resBody.Data, nil
|
||||
}
|
||||
|
||||
// DeleteDatastoreFile deletes a file in a datastore.
|
||||
func (c *VirtualEnvironmentClient) DeleteDatastoreFile(
|
||||
ctx context.Context,
|
||||
nodeName, datastoreID, volumeID string,
|
||||
) error {
|
||||
err := c.DoRequest(
|
||||
ctx,
|
||||
http.MethodDelete,
|
||||
fmt.Sprintf(
|
||||
"nodes/%s/storage/%s/content/%s",
|
||||
url.PathEscape(nodeName),
|
||||
url.PathEscape(datastoreID),
|
||||
url.PathEscape(volumeID),
|
||||
),
|
||||
nil,
|
||||
nil,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetDatastoreStatus gets status information for a given datastore.
|
||||
func (c *VirtualEnvironmentClient) GetDatastoreStatus(
|
||||
ctx context.Context,
|
||||
nodeName, datastoreID string,
|
||||
) (*DatastoreGetStatusResponseData, error) {
|
||||
resBody := &DatastoreGetStatusResponseBody{}
|
||||
err := c.DoRequest(
|
||||
ctx,
|
||||
http.MethodGet,
|
||||
fmt.Sprintf(
|
||||
"nodes/%s/storage/%s/status",
|
||||
url.PathEscape(nodeName),
|
||||
url.PathEscape(datastoreID),
|
||||
),
|
||||
nil,
|
||||
resBody,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if resBody.Data == nil {
|
||||
return nil, errors.New("the server did not include a data object in the response")
|
||||
}
|
||||
|
||||
return resBody.Data, nil
|
||||
}
|
||||
|
||||
// ListDatastoreFiles retrieves a list of the files in a datastore.
|
||||
func (c *VirtualEnvironmentClient) ListDatastoreFiles(
|
||||
ctx context.Context,
|
||||
nodeName, datastoreID string,
|
||||
) ([]*DatastoreFileListResponseData, error) {
|
||||
resBody := &DatastoreFileListResponseBody{}
|
||||
err := c.DoRequest(
|
||||
ctx,
|
||||
http.MethodGet,
|
||||
fmt.Sprintf(
|
||||
"nodes/%s/storage/%s/content",
|
||||
url.PathEscape(nodeName),
|
||||
url.PathEscape(datastoreID),
|
||||
),
|
||||
nil,
|
||||
resBody,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if resBody.Data == nil {
|
||||
return nil, errors.New("the server did not include a data object in the response")
|
||||
}
|
||||
|
||||
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 *VirtualEnvironmentClient) ListDatastores(
|
||||
ctx context.Context,
|
||||
nodeName string,
|
||||
d *DatastoreListRequestBody,
|
||||
) ([]*DatastoreListResponseData, error) {
|
||||
resBody := &DatastoreListResponseBody{}
|
||||
err := c.DoRequest(
|
||||
ctx,
|
||||
http.MethodGet,
|
||||
fmt.Sprintf("nodes/%s/storage", url.PathEscape(nodeName)),
|
||||
d,
|
||||
resBody,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if resBody.Data == nil {
|
||||
return nil, errors.New("the server did not include a data object in the response")
|
||||
}
|
||||
|
||||
sort.Slice(resBody.Data, func(i, j int) bool {
|
||||
return resBody.Data[i].ID < resBody.Data[j].ID
|
||||
})
|
||||
|
||||
return resBody.Data, nil
|
||||
}
|
||||
|
||||
// UploadFileToDatastore uploads a file to a datastore.
|
||||
func (c *VirtualEnvironmentClient) UploadFileToDatastore(
|
||||
ctx context.Context,
|
||||
d *DatastoreUploadRequestBody,
|
||||
) (*DatastoreUploadResponseBody, error) {
|
||||
switch d.ContentType {
|
||||
case "iso", "vztmpl":
|
||||
tflog.Debug(ctx, "uploading file to datastore using PVE API", map[string]interface{}{
|
||||
"node_name": d.NodeName,
|
||||
"datastore_id": d.DatastoreID,
|
||||
"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) {
|
||||
err := os.Remove(name)
|
||||
if err != nil {
|
||||
tflog.Error(ctx, "failed to remove temporary file", map[string]interface{}{
|
||||
"error": err,
|
||||
})
|
||||
}
|
||||
}(tempMultipartFileName)
|
||||
|
||||
// Now that the multipart data is stored in a file, we can go ahead and do a 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) {
|
||||
err := fileReader.Close()
|
||||
if err != nil {
|
||||
tflog.Error(ctx, "failed to close file reader", map[string]interface{}{
|
||||
"error": err,
|
||||
})
|
||||
}
|
||||
}(fileReader)
|
||||
|
||||
fileInfo, err := fileReader.Stat()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get file info: %w", err)
|
||||
}
|
||||
|
||||
fileSize := fileInfo.Size()
|
||||
|
||||
reqBody := &VirtualEnvironmentMultiPartData{
|
||||
Boundary: m.Boundary(),
|
||||
Reader: fileReader,
|
||||
Size: &fileSize,
|
||||
}
|
||||
|
||||
resBody := &DatastoreUploadResponseBody{}
|
||||
err = c.DoRequest(
|
||||
ctx,
|
||||
http.MethodPost,
|
||||
fmt.Sprintf(
|
||||
"nodes/%s/storage/%s/upload",
|
||||
url.PathEscape(d.NodeName),
|
||||
url.PathEscape(d.DatastoreID),
|
||||
),
|
||||
reqBody,
|
||||
resBody,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return resBody, nil
|
||||
default:
|
||||
// We need to upload all other files using SFTP due to API limitations.
|
||||
// Hopefully, this will not be required in future releases of Proxmox VE.
|
||||
tflog.Debug(ctx, "uploading file to datastore using SFTP", map[string]interface{}{
|
||||
"node_name": d.NodeName,
|
||||
"datastore_id": d.DatastoreID,
|
||||
"file_name": d.FileName,
|
||||
"content_type": d.ContentType,
|
||||
})
|
||||
|
||||
fileInfo, err := d.File.Stat()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get file info: %w", err)
|
||||
}
|
||||
fileSize := fileInfo.Size()
|
||||
|
||||
sshClient, err := c.OpenNodeShell(ctx, d.NodeName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer func(sshClient *ssh.Client) {
|
||||
err := sshClient.Close()
|
||||
if err != nil {
|
||||
tflog.Error(ctx, "failed to close SSH client", map[string]interface{}{
|
||||
"error": err,
|
||||
})
|
||||
}
|
||||
}(sshClient)
|
||||
|
||||
datastore, err := c.GetDatastore(ctx, d.DatastoreID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get datastore: %w", err)
|
||||
}
|
||||
if datastore.Path == nil || *datastore.Path == "" {
|
||||
return nil, errors.New("failed to determine the datastore path")
|
||||
}
|
||||
|
||||
remoteFileDir := *datastore.Path
|
||||
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 nil, fmt.Errorf("failed to create SFTP client: %w", err)
|
||||
}
|
||||
|
||||
defer func(sftpClient *sftp.Client) {
|
||||
err := sftpClient.Close()
|
||||
if err != nil {
|
||||
tflog.Error(ctx, "failed to close SFTP client", map[string]interface{}{
|
||||
"error": err,
|
||||
})
|
||||
}
|
||||
}(sftpClient)
|
||||
|
||||
err = sftpClient.MkdirAll(remoteFileDir)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create directory %s: %w", remoteFileDir, err)
|
||||
}
|
||||
|
||||
remoteFile, err := sftpClient.Create(remoteFilePath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create file %s: %w", remoteFilePath, err)
|
||||
}
|
||||
|
||||
defer func(remoteFile *sftp.File) {
|
||||
err := remoteFile.Close()
|
||||
if err != nil {
|
||||
tflog.Error(ctx, "failed to close remote file", map[string]interface{}{
|
||||
"error": err,
|
||||
})
|
||||
}
|
||||
}(remoteFile)
|
||||
|
||||
bytesUploaded, err := remoteFile.ReadFrom(d.File)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to upload file %s: %w", remoteFilePath, err)
|
||||
}
|
||||
if bytesUploaded != fileSize {
|
||||
return nil, 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 &DatastoreUploadResponseBody{}, nil
|
||||
}
|
||||
}
|
@ -8,13 +8,15 @@ package firewall
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"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
|
||||
@ -23,83 +25,45 @@ type Alias interface {
|
||||
UpdateAlias(ctx context.Context, name string, d *AliasUpdateRequestBody) error
|
||||
}
|
||||
|
||||
// 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"`
|
||||
}
|
||||
|
||||
func (c *Client) aliasesPath() string {
|
||||
return c.ExpandPath("firewall/aliases")
|
||||
}
|
||||
|
||||
// CreateAlias create an alias
|
||||
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
|
||||
// DeleteAlias delete an alias.
|
||||
func (c *Client) DeleteAlias(ctx context.Context, name string) error {
|
||||
err := c.DoRequest(
|
||||
ctx,
|
||||
http.MethodDelete,
|
||||
fmt.Sprintf("%s/%s", c.aliasesPath(), url.PathEscape(name)),
|
||||
nil,
|
||||
nil,
|
||||
)
|
||||
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
|
||||
// GetAlias retrieves an alias.
|
||||
func (c *Client) GetAlias(ctx context.Context, name string) (*AliasGetResponseData, error) {
|
||||
resBody := &AliasGetResponseBody{}
|
||||
err := c.DoRequest(
|
||||
ctx,
|
||||
http.MethodGet,
|
||||
fmt.Sprintf("%s/%s", c.aliasesPath(), url.PathEscape(name)),
|
||||
nil,
|
||||
resBody,
|
||||
)
|
||||
|
||||
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, errors.New("the server did not include a data object in the response")
|
||||
return nil, api.ErrNoDataObjectInResponse
|
||||
}
|
||||
|
||||
return resBody.Data, nil
|
||||
@ -108,13 +72,14 @@ func (c *Client) GetAlias(ctx context.Context, name string) (*AliasGetResponseDa
|
||||
// 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, errors.New("the server did not include a data object in the response")
|
||||
return nil, api.ErrNoDataObjectInResponse
|
||||
}
|
||||
|
||||
sort.Slice(resBody.Data, func(i, j int) bool {
|
||||
@ -126,15 +91,10 @@ func (c *Client) ListAliases(ctx context.Context) ([]*AliasGetResponseData, erro
|
||||
|
||||
// UpdateAlias updates an alias.
|
||||
func (c *Client) UpdateAlias(ctx context.Context, name string, d *AliasUpdateRequestBody) error {
|
||||
err := c.DoRequest(
|
||||
ctx,
|
||||
http.MethodPut,
|
||||
fmt.Sprintf("%s/%s", c.aliasesPath(), url.PathEscape(name)),
|
||||
d,
|
||||
nil,
|
||||
)
|
||||
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
|
||||
}
|
||||
|
40
proxmox/firewall/aliases_types.go
Normal file
40
proxmox/firewall/aliases_types.go
Normal file
@ -0,0 +1,40 @@
|
||||
/*
|
||||
* 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"`
|
||||
}
|
@ -6,8 +6,11 @@
|
||||
|
||||
package firewall
|
||||
|
||||
import "github.com/bpg/terraform-provider-proxmox/proxmox/types"
|
||||
import (
|
||||
"github.com/bpg/terraform-provider-proxmox/proxmox/api"
|
||||
)
|
||||
|
||||
// API is an interface for the Proxmox firewall API.
|
||||
type API interface {
|
||||
Alias
|
||||
IPSet
|
||||
@ -15,6 +18,7 @@ type API interface {
|
||||
Options
|
||||
}
|
||||
|
||||
// Client is an interface for accessing the Proxmox firewall API.
|
||||
type Client struct {
|
||||
types.Client
|
||||
api.Client
|
||||
}
|
@ -12,15 +12,15 @@ package firewall
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"sort"
|
||||
|
||||
"github.com/bpg/terraform-provider-proxmox/proxmox/types"
|
||||
"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
|
||||
@ -31,59 +31,21 @@ type IPSet interface {
|
||||
ListIPSets(ctx context.Context) ([]*IPSetListResponseData, error)
|
||||
}
|
||||
|
||||
// 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
|
||||
|
||||
func (c *Client) ipsetPath() string {
|
||||
return c.ExpandPath("firewall/ipset")
|
||||
}
|
||||
|
||||
// CreateIPSet create an 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
|
||||
// AddCIDRToIPSet adds IP or Network to IPSet.
|
||||
func (c *Client) AddCIDRToIPSet(ctx context.Context, id string, d IPSetGetResponseData) error {
|
||||
err := c.DoRequest(
|
||||
ctx,
|
||||
@ -95,6 +57,7 @@ func (c *Client) AddCIDRToIPSet(ctx context.Context, id string, d IPSetGetRespon
|
||||
if err != nil {
|
||||
return fmt.Errorf("error adding CIDR to IPSet: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -104,10 +67,11 @@ func (c *Client) UpdateIPSet(ctx context.Context, d *IPSetUpdateRequestBody) err
|
||||
if err != nil {
|
||||
return fmt.Errorf("error updating IPSet: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteIPSet delete an IPSet
|
||||
// DeleteIPSet delete an IPSet.
|
||||
func (c *Client) DeleteIPSet(ctx context.Context, id string) error {
|
||||
err := c.DoRequest(
|
||||
ctx,
|
||||
@ -119,6 +83,7 @@ func (c *Client) DeleteIPSet(ctx context.Context, id string) error {
|
||||
if err != nil {
|
||||
return fmt.Errorf("error deleting IPSet %s: %w", id, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -134,12 +99,14 @@ func (c *Client) DeleteIPSetContent(ctx context.Context, id string, cidr string)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error deleting IPSet content %s: %w", id, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetIPSetContent retrieve a list of IPSet content
|
||||
// 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,
|
||||
@ -152,7 +119,7 @@ func (c *Client) GetIPSetContent(ctx context.Context, id string) ([]*IPSetGetRes
|
||||
}
|
||||
|
||||
if resBody.Data == nil {
|
||||
return nil, errors.New("the server did not include a data object in the response")
|
||||
return nil, api.ErrNoDataObjectInResponse
|
||||
}
|
||||
|
||||
return resBody.Data, nil
|
||||
@ -161,13 +128,14 @@ func (c *Client) GetIPSetContent(ctx context.Context, id string) ([]*IPSetGetRes
|
||||
// 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, errors.New("the server did not include a data object in the response")
|
||||
return nil, api.ErrNoDataObjectInResponse
|
||||
}
|
||||
|
||||
sort.Slice(resBody.Data, func(i, j int) bool {
|
||||
|
48
proxmox/firewall/ipset_types.go
Normal file
48
proxmox/firewall/ipset_types.go
Normal file
@ -0,0 +1,48 @@
|
||||
/*
|
||||
* 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/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
|
@ -14,70 +14,46 @@ import (
|
||||
|
||||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
|
||||
|
||||
"github.com/bpg/terraform-provider-proxmox/proxmox/types"
|
||||
"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)
|
||||
}
|
||||
|
||||
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"`
|
||||
}
|
||||
|
||||
type OptionsGetResponseBody struct {
|
||||
Data *OptionsGetResponseData `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
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"`
|
||||
}
|
||||
|
||||
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, fmt.Errorf("the server did not include a data object in the response")
|
||||
return nil, api.ErrNoDataObjectInResponse
|
||||
}
|
||||
|
||||
return resBody.Data, nil
|
||||
|
42
proxmox/firewall/options_types.go
Normal file
42
proxmox/firewall/options_types.go
Normal file
@ -0,0 +1,42 @@
|
||||
/*
|
||||
* 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/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"`
|
||||
}
|
@ -8,16 +8,17 @@ package firewall
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
|
||||
|
||||
"github.com/bpg/terraform-provider-proxmox/proxmox/api"
|
||||
"github.com/bpg/terraform-provider-proxmox/proxmox/types"
|
||||
)
|
||||
|
||||
// Rule is an interface for the Proxmox firewall rule API.
|
||||
type Rule interface {
|
||||
GetRulesID() string
|
||||
CreateRule(ctx context.Context, d *RuleCreateRequestBody) error
|
||||
@ -27,52 +28,7 @@ type Rule interface {
|
||||
DeleteRule(ctx context.Context, pos int) error
|
||||
}
|
||||
|
||||
// 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"`
|
||||
}
|
||||
|
||||
// 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"`
|
||||
@ -92,6 +48,11 @@ 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()))
|
||||
}
|
||||
@ -102,25 +63,21 @@ func (c *Client) CreateRule(ctx context.Context, d *RuleCreateRequestBody) error
|
||||
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,
|
||||
fmt.Sprintf("%s/%d", c.rulesPath(), pos),
|
||||
nil,
|
||||
resBody,
|
||||
)
|
||||
|
||||
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, errors.New("the server did not include a data object in the response")
|
||||
return nil, api.ErrNoDataObjectInResponse
|
||||
}
|
||||
|
||||
return resBody.Data, nil
|
||||
@ -129,13 +86,14 @@ func (c *Client) GetRule(ctx context.Context, pos int) (*RuleGetResponseData, er
|
||||
// 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, errors.New("the server did not include a data object in the response")
|
||||
return nil, api.ErrNoDataObjectInResponse
|
||||
}
|
||||
|
||||
return resBody.Data, nil
|
||||
@ -143,30 +101,20 @@ func (c *Client) ListRules(ctx context.Context) ([]*RuleListResponseData, error)
|
||||
|
||||
// UpdateRule updates a firewall rule.
|
||||
func (c *Client) UpdateRule(ctx context.Context, pos int, d *RuleUpdateRequestBody) error {
|
||||
err := c.DoRequest(
|
||||
ctx,
|
||||
http.MethodPut,
|
||||
fmt.Sprintf("%s/%d", c.rulesPath(), pos),
|
||||
d,
|
||||
nil,
|
||||
)
|
||||
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,
|
||||
fmt.Sprintf("%s/%d", c.rulesPath(), pos),
|
||||
nil,
|
||||
nil,
|
||||
)
|
||||
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
|
||||
}
|
||||
|
53
proxmox/firewall/rules_types.go
Normal file
53
proxmox/firewall/rules_types.go
Normal file
@ -0,0 +1,53 @@
|
||||
/*
|
||||
* 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"`
|
||||
}
|
51
proxmox/nodes/certificate.go
Normal file
51
proxmox/nodes/certificate.go
Normal file
@ -0,0 +1,51 @@
|
||||
/*
|
||||
* 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,23 +1,25 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
/*
|
||||
* 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/. */
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package proxmox
|
||||
package nodes
|
||||
|
||||
import "github.com/bpg/terraform-provider-proxmox/proxmox/types"
|
||||
|
||||
// VirtualEnvironmentCertificateDeleteRequestBody contains the data for a custom certificate delete request.
|
||||
type VirtualEnvironmentCertificateDeleteRequestBody struct {
|
||||
// CertificateDeleteRequestBody contains the data for a custom certificate delete request.
|
||||
type CertificateDeleteRequestBody struct {
|
||||
Restart *types.CustomBool `json:"restart,omitempty" url:"restart,omitempty,int"`
|
||||
}
|
||||
|
||||
// VirtualEnvironmentCertificateListResponseBody contains the body from a certificate list response.
|
||||
type VirtualEnvironmentCertificateListResponseBody struct {
|
||||
Data *[]VirtualEnvironmentCertificateListResponseData `json:"data,omitempty"`
|
||||
// CertificateListResponseBody contains the body from a certificate list response.
|
||||
type CertificateListResponseBody struct {
|
||||
Data *[]CertificateListResponseData `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
// VirtualEnvironmentCertificateListResponseData contains the data from a certificate list response.
|
||||
type VirtualEnvironmentCertificateListResponseData struct {
|
||||
// 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"`
|
||||
@ -30,8 +32,8 @@ type VirtualEnvironmentCertificateListResponseData struct {
|
||||
SubjectAlternativeNames *[]string `json:"san,omitempty"`
|
||||
}
|
||||
|
||||
// VirtualEnvironmentCertificateUpdateRequestBody contains the body for a custom certificate update request.
|
||||
type VirtualEnvironmentCertificateUpdateRequestBody struct {
|
||||
// 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"`
|
43
proxmox/nodes/client.go
Normal file
43
proxmox/nodes/client.go
Normal file
@ -0,0 +1,43 @@
|
||||
/*
|
||||
* 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/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,
|
||||
}
|
||||
}
|
@ -4,27 +4,37 @@
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package container
|
||||
package containers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
|
||||
containerfirewall "github.com/bpg/terraform-provider-proxmox/proxmox/container/firewall"
|
||||
"github.com/bpg/terraform-provider-proxmox/proxmox/api"
|
||||
"github.com/bpg/terraform-provider-proxmox/proxmox/firewall"
|
||||
"github.com/bpg/terraform-provider-proxmox/proxmox/types"
|
||||
containerfirewall "github.com/bpg/terraform-provider-proxmox/proxmox/nodes/containers/firewall"
|
||||
)
|
||||
|
||||
// Client is an interface for accessing the Proxmox container API.
|
||||
type Client struct {
|
||||
types.Client
|
||||
NodeName string
|
||||
VMID int
|
||||
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 {
|
||||
return fmt.Sprintf("nodes/%s/lxc/%d/%s", url.PathEscape(c.NodeName), c.VMID, path)
|
||||
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},
|
202
proxmox/nodes/containers/container.go
Normal file
202
proxmox/nodes/containers/container.go
Normal file
@ -0,0 +1,202 @@
|
||||
/*
|
||||
* 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)
|
||||
}
|
811
proxmox/nodes/containers/container_types.go
Normal file
811
proxmox/nodes/containers/container_types.go
Normal file
@ -0,0 +1,811 @@
|
||||
/*
|
||||
* 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/proxmox/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
|
||||
}
|
@ -10,6 +10,7 @@ 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
|
||||
}
|
41
proxmox/nodes/dns.go
Normal file
41
proxmox/nodes/dns.go
Normal file
@ -0,0 +1,41 @@
|
||||
/*
|
||||
* 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,24 +1,26 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
/*
|
||||
* 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/. */
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package proxmox
|
||||
package nodes
|
||||
|
||||
// VirtualEnvironmentDNSGetResponseBody contains the body from a DNS get response.
|
||||
type VirtualEnvironmentDNSGetResponseBody struct {
|
||||
Data *VirtualEnvironmentDNSGetResponseData `json:"data,omitempty"`
|
||||
// DNSGetResponseBody contains the body from a DNS get response.
|
||||
type DNSGetResponseBody struct {
|
||||
Data *DNSGetResponseData `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
// VirtualEnvironmentDNSGetResponseData contains the data from a DNS get response.
|
||||
type VirtualEnvironmentDNSGetResponseData struct {
|
||||
// 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"`
|
||||
}
|
||||
|
||||
// VirtualEnvironmentDNSUpdateRequestBody contains the body for a DNS update request.
|
||||
type VirtualEnvironmentDNSUpdateRequestBody struct {
|
||||
// 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"`
|
41
proxmox/nodes/hosts.go
Normal file
41
proxmox/nodes/hosts.go
Normal file
@ -0,0 +1,41 @@
|
||||
/*
|
||||
* 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
|
||||
}
|
24
proxmox/nodes/hosts_types.go
Normal file
24
proxmox/nodes/hosts_types.go
Normal file
@ -0,0 +1,24 @@
|
||||
/*
|
||||
* 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"`
|
||||
}
|
108
proxmox/nodes/nodes.go
Normal file
108
proxmox/nodes/nodes.go
Normal file
@ -0,0 +1,108 @@
|
||||
/*
|
||||
* 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"
|
||||
"strings"
|
||||
|
||||
"github.com/bpg/terraform-provider-proxmox/proxmox/api"
|
||||
)
|
||||
|
||||
// GetIP retrieves the IP address of a node.
|
||||
func (c *Client) GetIP(ctx context.Context) (string, error) {
|
||||
networkDevices, err := c.ListNetworkDevices(ctx)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
nodeAddress := ""
|
||||
|
||||
for _, d := range networkDevices {
|
||||
if d.Address != nil {
|
||||
nodeAddress = *d.Address
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if nodeAddress == "" {
|
||||
return "", fmt.Errorf("failed to determine the IP address of node \"%s\"", c.NodeName)
|
||||
}
|
||||
|
||||
nodeAddressParts := strings.Split(nodeAddress, "/")
|
||||
|
||||
return nodeAddressParts[0], 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
|
||||
}
|
||||
|
||||
// ListNetworkDevices retrieves a list of network devices for a specific nodes.
|
||||
func (c *Client) ListNetworkDevices(ctx context.Context) ([]*NetworkDeviceListResponseData, error) {
|
||||
resBody := &NetworkDeviceListResponseBody{}
|
||||
|
||||
err := c.DoRequest(ctx, http.MethodGet, c.ExpandPath("network"), nil, resBody)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get network devices 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
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
95
proxmox/nodes/nodes_types.go
Normal file
95
proxmox/nodes/nodes_types.go
Normal file
@ -0,0 +1,95 @@
|
||||
/*
|
||||
* 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/proxmox/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"`
|
||||
}
|
||||
|
||||
// NetworkDeviceListResponseBody contains the body from a node network device list response.
|
||||
type NetworkDeviceListResponseBody struct {
|
||||
Data []*NetworkDeviceListResponseData `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
// NetworkDeviceListResponseData contains the data from a node network device list response.
|
||||
type NetworkDeviceListResponseData struct {
|
||||
Active *types.CustomBool `json:"active,omitempty"`
|
||||
Address *string `json:"address,omitempty"`
|
||||
Autostart *types.CustomBool `json:"autostart,omitempty"`
|
||||
BridgeFD *string `json:"bridge_fd,omitempty"`
|
||||
BridgePorts *string `json:"bridge_ports,omitempty"`
|
||||
BridgeSTP *string `json:"bridge_stp,omitempty"`
|
||||
CIDR *string `json:"cidr,omitempty"`
|
||||
Exists *types.CustomBool `json:"exists,omitempty"`
|
||||
Families *[]string `json:"families,omitempty"`
|
||||
Gateway *string `json:"gateway,omitempty"`
|
||||
Iface string `json:"iface"`
|
||||
MethodIPv4 *string `json:"method,omitempty"`
|
||||
MethodIPv6 *string `json:"method6,omitempty"`
|
||||
Netmask *string `json:"netmask,omitempty"`
|
||||
Priority int `json:"priority"`
|
||||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
280
proxmox/nodes/storage.go
Normal file
280
proxmox/nodes/storage.go
Normal file
@ -0,0 +1,280 @@
|
||||
/*
|
||||
* 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,30 +1,15 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
/*
|
||||
* 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/. */
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package proxmox
|
||||
package nodes
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/bpg/terraform-provider-proxmox/proxmox/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"`
|
||||
}
|
||||
|
||||
// DatastoreFileListResponseBody contains the body from a datastore content list response.
|
||||
type DatastoreFileListResponseBody struct {
|
||||
Data []*DatastoreFileListResponseData `json:"data,omitempty"`
|
||||
@ -86,15 +71,6 @@ type DatastoreListResponseData struct {
|
||||
Type string `json:"type,omitempty"`
|
||||
}
|
||||
|
||||
// DatastoreUploadRequestBody contains the body for a datastore upload request.
|
||||
type DatastoreUploadRequestBody struct {
|
||||
ContentType string `json:"content,omitempty"`
|
||||
DatastoreID string `json:"storage,omitempty"`
|
||||
FileName string `json:"filename,omitempty"`
|
||||
NodeName string `json:"node,omitempty"`
|
||||
File *os.File `json:"-"`
|
||||
}
|
||||
|
||||
// DatastoreUploadResponseBody contains the body from a datastore upload response.
|
||||
type DatastoreUploadResponseBody struct {
|
||||
UploadID *string `json:"data,omitempty"`
|
25
proxmox/nodes/tasks/client.go
Normal file
25
proxmox/nodes/tasks/client.go
Normal file
@ -0,0 +1,25 @@
|
||||
/*
|
||||
* 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),
|
||||
)
|
||||
}
|
86
proxmox/nodes/tasks/tasks.go
Normal file
86
proxmox/nodes/tasks/tasks.go
Normal file
@ -0,0 +1,86 @@
|
||||
/*
|
||||
* 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, timeout, 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 {
|
||||
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,
|
||||
)
|
||||
}
|
19
proxmox/nodes/tasks/tasks_types.go
Normal file
19
proxmox/nodes/tasks/tasks_types.go
Normal file
@ -0,0 +1,19 @@
|
||||
/*
|
||||
* 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"`
|
||||
}
|
50
proxmox/nodes/vms/client.go
Normal file
50
proxmox/nodes/vms/client.go
Normal file
@ -0,0 +1,50 @@
|
||||
/*
|
||||
* 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},
|
||||
}
|
||||
}
|
@ -10,6 +10,7 @@ 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
|
||||
}
|
581
proxmox/nodes/vms/vms.go
Normal file
581
proxmox/nodes/vms/vms.go
Normal file
@ -0,0 +1,581 @@
|
||||
/*
|
||||
* 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)
|
||||
}
|
@ -4,7 +4,7 @@
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package proxmox
|
||||
package vms
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
@ -35,6 +35,7 @@ type CustomAudioDevice struct {
|
||||
// CustomAudioDevices handles QEMU audio device parameters.
|
||||
type CustomAudioDevices []CustomAudioDevice
|
||||
|
||||
// CustomBoot handles QEMU boot parameters.
|
||||
type CustomBoot struct {
|
||||
Order *[]string `json:"order,omitempty" url:"order,omitempty,semicolon"`
|
||||
}
|
||||
@ -218,8 +219,8 @@ type CustomWatchdogDevice struct {
|
||||
Model *string `json:"model" url:"model"`
|
||||
}
|
||||
|
||||
// VirtualEnvironmentVMCloneRequestBody contains the data for an virtual machine clone request.
|
||||
type VirtualEnvironmentVMCloneRequestBody struct {
|
||||
// CloneRequestBody contains the data for an virtual machine 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"`
|
||||
@ -232,8 +233,8 @@ type VirtualEnvironmentVMCloneRequestBody struct {
|
||||
VMIDNew int `json:"newid" url:"newid"`
|
||||
}
|
||||
|
||||
// VirtualEnvironmentVMCreateRequestBody contains the data for a virtual machine create request.
|
||||
type VirtualEnvironmentVMCreateRequestBody struct {
|
||||
// CreateRequestBody contains the data for a virtual machine create request.
|
||||
type CreateRequestBody struct {
|
||||
ACPI *types.CustomBool `json:"acpi,omitempty" url:"acpi,omitempty,int"`
|
||||
Agent *CustomAgent `json:"agent,omitempty" url:"agent,omitempty"`
|
||||
AllowReboot *types.CustomBool `json:"reboot,omitempty" url:"reboot,omitempty,int"`
|
||||
@ -305,37 +306,38 @@ type VirtualEnvironmentVMCreateRequestBody struct {
|
||||
WatchdogDevice *CustomWatchdogDevice `json:"watchdog,omitempty" url:"watchdog,omitempty"`
|
||||
}
|
||||
|
||||
type VirtualEnvironmentVMCreateResponseBody struct {
|
||||
// CreateResponseBody contains the body from a create response.
|
||||
type CreateResponseBody struct {
|
||||
Data *string `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
// VirtualEnvironmentVMGetQEMUNetworkInterfacesResponseBody contains the body from a QEMU get network interfaces response.
|
||||
type VirtualEnvironmentVMGetQEMUNetworkInterfacesResponseBody struct {
|
||||
Data *VirtualEnvironmentVMGetQEMUNetworkInterfacesResponseData `json:"data,omitempty"`
|
||||
// GetQEMUNetworkInterfacesResponseBody contains the body from a QEMU get network interfaces response.
|
||||
type GetQEMUNetworkInterfacesResponseBody struct {
|
||||
Data *GetQEMUNetworkInterfacesResponseData `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
// VirtualEnvironmentVMGetQEMUNetworkInterfacesResponseData contains the data from a QEMU get network interfaces response.
|
||||
type VirtualEnvironmentVMGetQEMUNetworkInterfacesResponseData struct {
|
||||
Result *[]VirtualEnvironmentVMGetQEMUNetworkInterfacesResponseResult `json:"result,omitempty"`
|
||||
// GetQEMUNetworkInterfacesResponseData contains the data from a QEMU get network interfaces response.
|
||||
type GetQEMUNetworkInterfacesResponseData struct {
|
||||
Result *[]GetQEMUNetworkInterfacesResponseResult `json:"result,omitempty"`
|
||||
}
|
||||
|
||||
// VirtualEnvironmentVMGetQEMUNetworkInterfacesResponseResult contains the result from a QEMU get network interfaces response.
|
||||
type VirtualEnvironmentVMGetQEMUNetworkInterfacesResponseResult struct {
|
||||
MACAddress string `json:"hardware-address"`
|
||||
Name string `json:"name"`
|
||||
Statistics *VirtualEnvironmentVMGetQEMUNetworkInterfacesResponseResultStatistics `json:"statistics,omitempty"`
|
||||
IPAddresses *[]VirtualEnvironmentVMGetQEMUNetworkInterfacesResponseResultIPAddress `json:"ip-addresses,omitempty"`
|
||||
// GetQEMUNetworkInterfacesResponseResult contains the result from a QEMU get network interfaces response.
|
||||
type GetQEMUNetworkInterfacesResponseResult struct {
|
||||
MACAddress string `json:"hardware-address"`
|
||||
Name string `json:"name"`
|
||||
Statistics *GetQEMUNetworkInterfacesResponseResultStatistics `json:"statistics,omitempty"`
|
||||
IPAddresses *[]GetQEMUNetworkInterfacesResponseResultIPAddress `json:"ip-addresses,omitempty"`
|
||||
}
|
||||
|
||||
// VirtualEnvironmentVMGetQEMUNetworkInterfacesResponseResultIPAddress contains the IP address from a QEMU get network interfaces response.
|
||||
type VirtualEnvironmentVMGetQEMUNetworkInterfacesResponseResultIPAddress struct {
|
||||
// GetQEMUNetworkInterfacesResponseResultIPAddress contains the IP address from a QEMU get network interfaces response.
|
||||
type GetQEMUNetworkInterfacesResponseResultIPAddress struct {
|
||||
Address string `json:"ip-address"`
|
||||
Prefix int `json:"prefix"`
|
||||
Type string `json:"ip-address-type"`
|
||||
}
|
||||
|
||||
// VirtualEnvironmentVMGetQEMUNetworkInterfacesResponseResultStatistics contains the statistics from a QEMU get network interfaces response.
|
||||
type VirtualEnvironmentVMGetQEMUNetworkInterfacesResponseResultStatistics struct {
|
||||
// GetQEMUNetworkInterfacesResponseResultStatistics contains the statistics from a QEMU get network interfaces response.
|
||||
type GetQEMUNetworkInterfacesResponseResultStatistics struct {
|
||||
RXBytes int `json:"rx-bytes"`
|
||||
RXDropped int `json:"rx-dropped"`
|
||||
RXErrors int `json:"rx-errs"`
|
||||
@ -346,13 +348,13 @@ type VirtualEnvironmentVMGetQEMUNetworkInterfacesResponseResultStatistics struct
|
||||
TXPackets int `json:"tx-packets"`
|
||||
}
|
||||
|
||||
// VirtualEnvironmentVMGetResponseBody contains the body from a virtual machine get response.
|
||||
type VirtualEnvironmentVMGetResponseBody struct {
|
||||
Data *VirtualEnvironmentVMGetResponseData `json:"data,omitempty"`
|
||||
// GetResponseBody contains the body from a virtual machine get response.
|
||||
type GetResponseBody struct {
|
||||
Data *GetResponseData `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
// VirtualEnvironmentVMGetResponseData contains the data from an virtual machine get response.
|
||||
type VirtualEnvironmentVMGetResponseData struct {
|
||||
// GetResponseData contains the data from an virtual machine get response.
|
||||
type GetResponseData struct {
|
||||
ACPI *types.CustomBool `json:"acpi,omitempty"`
|
||||
Agent *CustomAgent `json:"agent,omitempty"`
|
||||
AllowReboot *types.CustomBool `json:"reboot,omitempty"`
|
||||
@ -486,13 +488,13 @@ type VirtualEnvironmentVMGetResponseData struct {
|
||||
WatchdogDevice *CustomWatchdogDevice `json:"watchdog,omitempty"`
|
||||
}
|
||||
|
||||
// VirtualEnvironmentVMGetStatusResponseBody contains the body from a VM get status response.
|
||||
type VirtualEnvironmentVMGetStatusResponseBody struct {
|
||||
Data *VirtualEnvironmentVMGetStatusResponseData `json:"data,omitempty"`
|
||||
// GetStatusResponseBody contains the body from a VM get status response.
|
||||
type GetStatusResponseBody struct {
|
||||
Data *GetStatusResponseData `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
// VirtualEnvironmentVMGetStatusResponseData contains the data from a VM get status response.
|
||||
type VirtualEnvironmentVMGetStatusResponseData struct {
|
||||
// GetStatusResponseData contains the data from a VM get status response.
|
||||
type GetStatusResponseData struct {
|
||||
AgentEnabled *types.CustomBool `json:"agent,omitempty"`
|
||||
CPUCount *float64 `json:"cpus,omitempty"`
|
||||
Lock *string `json:"lock,omitempty"`
|
||||
@ -508,33 +510,33 @@ type VirtualEnvironmentVMGetStatusResponseData struct {
|
||||
VMID *int `json:"vmid,omitempty"`
|
||||
}
|
||||
|
||||
// VirtualEnvironmentVMListResponseBody contains the body from an virtual machine list response.
|
||||
type VirtualEnvironmentVMListResponseBody struct {
|
||||
Data []*VirtualEnvironmentVMListResponseData `json:"data,omitempty"`
|
||||
// ListResponseBody contains the body from a virtual machine list response.
|
||||
type ListResponseBody struct {
|
||||
Data []*ListResponseData `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
// VirtualEnvironmentVMListResponseData contains the data from an virtual machine list response.
|
||||
type VirtualEnvironmentVMListResponseData struct {
|
||||
// ListResponseData contains the data from an virtual machine list response.
|
||||
type ListResponseData struct {
|
||||
Name *string `json:"name,omitempty"`
|
||||
Tags *string `json:"tags,omitempty"`
|
||||
VMID int `json:"vmid,omitempty"`
|
||||
}
|
||||
|
||||
// VirtualEnvironmentVMMigrateRequestBody contains the body for a VM migration request.
|
||||
type VirtualEnvironmentVMMigrateRequestBody struct {
|
||||
// MigrateRequestBody contains the body for a VM migration request.
|
||||
type MigrateRequestBody struct {
|
||||
OnlineMigration *types.CustomBool `json:"online,omitempty" url:"online,omitempty"`
|
||||
TargetNode string `json:"target" url:"target"`
|
||||
TargetStorage *string `json:"targetstorage,omitempty" url:"targetstorage,omitempty"`
|
||||
WithLocalDisks *types.CustomBool `json:"with-local-disks,omitempty" url:"with-local-disks,omitempty,int"`
|
||||
}
|
||||
|
||||
// VirtualEnvironmentVMMigrateResponseBody contains the body from a VM migrate response.
|
||||
type VirtualEnvironmentVMMigrateResponseBody struct {
|
||||
// MigrateResponseBody contains the body from a VM migrate response.
|
||||
type MigrateResponseBody struct {
|
||||
Data *string `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
// VirtualEnvironmentVMMoveDiskRequestBody contains the body for a VM move disk request.
|
||||
type VirtualEnvironmentVMMoveDiskRequestBody struct {
|
||||
// MoveDiskRequestBody contains the body for a VM move disk request.
|
||||
type MoveDiskRequestBody struct {
|
||||
BandwidthLimit *int `json:"bwlimit,omitempty" url:"bwlimit,omitempty"`
|
||||
DeleteOriginalDisk *types.CustomBool `json:"delete,omitempty" url:"delete,omitempty,int"`
|
||||
Digest *string `json:"digest,omitempty" url:"digest,omitempty"`
|
||||
@ -543,59 +545,59 @@ type VirtualEnvironmentVMMoveDiskRequestBody struct {
|
||||
TargetStorageFormat *string `json:"format,omitempty" url:"format,omitempty"`
|
||||
}
|
||||
|
||||
// VirtualEnvironmentVMMoveDiskResponseBody contains the body from a VM move disk response.
|
||||
type VirtualEnvironmentVMMoveDiskResponseBody struct {
|
||||
// MoveDiskResponseBody contains the body from a VM move disk response.
|
||||
type MoveDiskResponseBody struct {
|
||||
Data *string `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
// VirtualEnvironmentVMRebootRequestBody contains the body for a VM reboot request.
|
||||
type VirtualEnvironmentVMRebootRequestBody struct {
|
||||
// RebootRequestBody contains the body for a VM reboot request.
|
||||
type RebootRequestBody struct {
|
||||
Timeout *int `json:"timeout,omitempty" url:"timeout,omitempty"`
|
||||
}
|
||||
|
||||
// VirtualEnvironmentVMRebootResponseBody contains the body from a VM reboot response.
|
||||
type VirtualEnvironmentVMRebootResponseBody struct {
|
||||
// RebootResponseBody contains the body from a VM reboot response.
|
||||
type RebootResponseBody struct {
|
||||
Data *string `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
// VirtualEnvironmentVMResizeDiskRequestBody contains the body for a VM resize disk request.
|
||||
type VirtualEnvironmentVMResizeDiskRequestBody struct {
|
||||
// ResizeDiskRequestBody contains the body for a VM resize disk request.
|
||||
type ResizeDiskRequestBody struct {
|
||||
Digest *string `json:"digest,omitempty" url:"digest,omitempty"`
|
||||
Disk string `json:"disk" url:"disk"`
|
||||
Size types.DiskSize `json:"size" url:"size"`
|
||||
SkipLock *types.CustomBool `json:"skiplock,omitempty" url:"skiplock,omitempty,int"`
|
||||
}
|
||||
|
||||
// VirtualEnvironmentVMShutdownRequestBody contains the body for a VM shutdown request.
|
||||
type VirtualEnvironmentVMShutdownRequestBody struct {
|
||||
// ShutdownRequestBody contains the body for a VM shutdown request.
|
||||
type ShutdownRequestBody struct {
|
||||
ForceStop *types.CustomBool `json:"forceStop,omitempty" url:"forceStop,omitempty,int"`
|
||||
KeepActive *types.CustomBool `json:"keepActive,omitempty" url:"keepActive,omitempty,int"`
|
||||
SkipLock *types.CustomBool `json:"skipLock,omitempty" url:"skipLock,omitempty,int"`
|
||||
Timeout *int `json:"timeout,omitempty" url:"timeout,omitempty"`
|
||||
}
|
||||
|
||||
// VirtualEnvironmentVMShutdownResponseBody contains the body from a VM shutdown response.
|
||||
type VirtualEnvironmentVMShutdownResponseBody struct {
|
||||
// ShutdownResponseBody contains the body from a VM shutdown response.
|
||||
type ShutdownResponseBody struct {
|
||||
Data *string `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
// VirtualEnvironmentVMStartResponseBody contains the body from a VM start response.
|
||||
type VirtualEnvironmentVMStartResponseBody struct {
|
||||
// StartResponseBody contains the body from a VM start response.
|
||||
type StartResponseBody struct {
|
||||
Data *string `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
// VirtualEnvironmentVMStopResponseBody contains the body from a VM stop response.
|
||||
type VirtualEnvironmentVMStopResponseBody struct {
|
||||
// StopResponseBody contains the body from a VM stop response.
|
||||
type StopResponseBody struct {
|
||||
Data *string `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
// VirtualEnvironmentVMUpdateAsyncResponseBody contains the body from a VM async update response.
|
||||
type VirtualEnvironmentVMUpdateAsyncResponseBody struct {
|
||||
// UpdateAsyncResponseBody contains the body from a VM async update response.
|
||||
type UpdateAsyncResponseBody struct {
|
||||
Data *string `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
// VirtualEnvironmentVMUpdateRequestBody contains the data for an virtual machine update request.
|
||||
type VirtualEnvironmentVMUpdateRequestBody VirtualEnvironmentVMCreateRequestBody
|
||||
// UpdateRequestBody contains the data for an virtual machine update request.
|
||||
type UpdateRequestBody CreateRequestBody
|
||||
|
||||
// EncodeValues converts a CustomAgent struct to a URL vlaue.
|
||||
func (r CustomAgent) EncodeValues(key string, v *url.Values) error {
|
||||
@ -645,9 +647,8 @@ func (r CustomAudioDevice) EncodeValues(key string, v *url.Values) error {
|
||||
func (r CustomAudioDevices) EncodeValues(key string, v *url.Values) error {
|
||||
for i, d := range r {
|
||||
if d.Enabled {
|
||||
err := d.EncodeValues(fmt.Sprintf("%s%d", key, i), v)
|
||||
if err != nil {
|
||||
return err
|
||||
if err := d.EncodeValues(fmt.Sprintf("%s%d", key, i), v); err != nil {
|
||||
return fmt.Errorf("unable to encode audio device %d: %w", i, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -655,6 +656,7 @@ func (r CustomAudioDevices) EncodeValues(key string, v *url.Values) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// EncodeValues converts a CustomBoot struct to multiple URL values.
|
||||
func (r CustomBoot) EncodeValues(key string, v *url.Values) error {
|
||||
if r.Order != nil && len(*r.Order) > 0 {
|
||||
v.Add(key, fmt.Sprintf("order=%s", strings.Join(*r.Order, ";")))
|
||||
@ -665,6 +667,7 @@ func (r CustomBoot) EncodeValues(key string, v *url.Values) error {
|
||||
|
||||
// EncodeValues converts a CustomCloudInitConfig struct to multiple URL values.
|
||||
func (r CustomCloudInitConfig) EncodeValues(_ string, v *url.Values) error {
|
||||
//nolint:nestif
|
||||
if r.Files != nil {
|
||||
var volumes []string
|
||||
|
||||
@ -830,6 +833,7 @@ func (r CustomNetworkDevice) EncodeValues(key string, v *url.Values) error {
|
||||
if r.Tag != nil {
|
||||
values = append(values, fmt.Sprintf("tag=%d", *r.Tag))
|
||||
}
|
||||
|
||||
if r.MTU != nil {
|
||||
values = append(values, fmt.Sprintf("mtu=%d", *r.MTU))
|
||||
}
|
||||
@ -853,9 +857,8 @@ func (r CustomNetworkDevice) EncodeValues(key string, v *url.Values) error {
|
||||
func (r CustomNetworkDevices) EncodeValues(key string, v *url.Values) error {
|
||||
for i, d := range r {
|
||||
if d.Enabled {
|
||||
err := d.EncodeValues(fmt.Sprintf("%s%d", key, i), v)
|
||||
if err != nil {
|
||||
return err
|
||||
if err := d.EncodeValues(fmt.Sprintf("%s%d", key, i), v); err != nil {
|
||||
return fmt.Errorf("failed to encode network device %d: %w", i, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -889,9 +892,8 @@ func (r CustomNUMADevice) EncodeValues(key string, v *url.Values) error {
|
||||
// EncodeValues converts a CustomNUMADevices array to multiple URL values.
|
||||
func (r CustomNUMADevices) EncodeValues(key string, v *url.Values) error {
|
||||
for i, d := range r {
|
||||
err := d.EncodeValues(fmt.Sprintf("%s%d", key, i), v)
|
||||
if err != nil {
|
||||
return err
|
||||
if err := d.EncodeValues(fmt.Sprintf("%s%d", key, i), v); err != nil {
|
||||
return fmt.Errorf("failed to encode NUMA device %d: %w", i, err)
|
||||
}
|
||||
}
|
||||
|
||||
@ -944,9 +946,8 @@ func (r CustomPCIDevice) EncodeValues(key string, v *url.Values) error {
|
||||
// EncodeValues converts a CustomPCIDevices array to multiple URL values.
|
||||
func (r CustomPCIDevices) EncodeValues(key string, v *url.Values) error {
|
||||
for i, d := range r {
|
||||
err := d.EncodeValues(fmt.Sprintf("%s%d", key, i), v)
|
||||
if err != nil {
|
||||
return err
|
||||
if err := d.EncodeValues(fmt.Sprintf("%s%d", key, i), v); err != nil {
|
||||
return fmt.Errorf("failed to encode PCI device %d: %w", i, err)
|
||||
}
|
||||
}
|
||||
|
||||
@ -1143,11 +1144,10 @@ func (r CustomStorageDevice) EncodeValues(key string, v *url.Values) error {
|
||||
|
||||
// EncodeValues converts a CustomStorageDevices array to multiple URL values.
|
||||
func (r CustomStorageDevices) EncodeValues(_ string, v *url.Values) error {
|
||||
for i, d := range r {
|
||||
for s, d := range r {
|
||||
if d.Enabled {
|
||||
err := d.EncodeValues(i, v)
|
||||
if err != nil {
|
||||
return err
|
||||
if err := d.EncodeValues(s, v); err != nil {
|
||||
return fmt.Errorf("error encoding storage device %s: %w", s, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1177,9 +1177,8 @@ func (r CustomUSBDevice) EncodeValues(key string, v *url.Values) error {
|
||||
// EncodeValues converts a CustomUSBDevices array to multiple URL values.
|
||||
func (r CustomUSBDevices) EncodeValues(key string, v *url.Values) error {
|
||||
for i, d := range r {
|
||||
err := d.EncodeValues(fmt.Sprintf("%s%d", key, i), v)
|
||||
if err != nil {
|
||||
return err
|
||||
if err := d.EncodeValues(fmt.Sprintf("%s%d", key, i), v); err != nil {
|
||||
return fmt.Errorf("error encoding USB device %d: %w", i, err)
|
||||
}
|
||||
}
|
||||
|
||||
@ -1230,9 +1229,8 @@ func (r CustomVirtualIODevice) EncodeValues(key string, v *url.Values) error {
|
||||
func (r CustomVirtualIODevices) EncodeValues(key string, v *url.Values) error {
|
||||
for i, d := range r {
|
||||
if d.Enabled {
|
||||
err := d.EncodeValues(fmt.Sprintf("%s%d", key, i), v)
|
||||
if err != nil {
|
||||
return err
|
||||
if err := d.EncodeValues(fmt.Sprintf("%s%d", key, i), v); err != nil {
|
||||
return fmt.Errorf("error encoding virtual IO device %d: %w", i, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1259,9 +1257,8 @@ func (r CustomWatchdogDevice) EncodeValues(key string, v *url.Values) error {
|
||||
func (r *CustomAgent) UnmarshalJSON(b []byte) error {
|
||||
var s string
|
||||
|
||||
err := json.Unmarshal(b, &s)
|
||||
if err != nil {
|
||||
return err
|
||||
if err := json.Unmarshal(b, &s); err != nil {
|
||||
return fmt.Errorf("error unmarshalling CustomAgent: %w", err)
|
||||
}
|
||||
|
||||
pairs := strings.Split(s, ",")
|
||||
@ -1293,9 +1290,8 @@ func (r *CustomAgent) UnmarshalJSON(b []byte) error {
|
||||
func (r *CustomAudioDevice) UnmarshalJSON(b []byte) error {
|
||||
var s string
|
||||
|
||||
err := json.Unmarshal(b, &s)
|
||||
if err != nil {
|
||||
return err
|
||||
if err := json.Unmarshal(b, &s); err != nil {
|
||||
return fmt.Errorf("error unmarshalling CustomAudioDevice: %w", err)
|
||||
}
|
||||
|
||||
pairs := strings.Split(s, ",")
|
||||
@ -1320,8 +1316,7 @@ func (r *CustomAudioDevice) UnmarshalJSON(b []byte) error {
|
||||
func (r *CustomBoot) UnmarshalJSON(b []byte) error {
|
||||
var s string
|
||||
|
||||
err := json.Unmarshal(b, &s)
|
||||
if err != nil {
|
||||
if err := json.Unmarshal(b, &s); err != nil {
|
||||
return fmt.Errorf("error unmarshalling CustomBoot: %w", err)
|
||||
}
|
||||
|
||||
@ -1332,8 +1327,8 @@ func (r *CustomBoot) UnmarshalJSON(b []byte) error {
|
||||
|
||||
if len(v) == 2 {
|
||||
if v[0] == "order" {
|
||||
v := strings.Split(strings.TrimSpace(v[1]), ";")
|
||||
r.Order = &v
|
||||
o := strings.Split(strings.TrimSpace(v[1]), ";")
|
||||
r.Order = &o
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1345,9 +1340,8 @@ func (r *CustomBoot) UnmarshalJSON(b []byte) error {
|
||||
func (r *CustomCloudInitFiles) UnmarshalJSON(b []byte) error {
|
||||
var s string
|
||||
|
||||
err := json.Unmarshal(b, &s)
|
||||
if err != nil {
|
||||
return err
|
||||
if err := json.Unmarshal(b, &s); err != nil {
|
||||
return fmt.Errorf("error unmarshalling CustomCloudInitFiles: %w", err)
|
||||
}
|
||||
|
||||
pairs := strings.Split(s, ",")
|
||||
@ -1376,9 +1370,8 @@ func (r *CustomCloudInitFiles) UnmarshalJSON(b []byte) error {
|
||||
func (r *CustomCloudInitIPConfig) UnmarshalJSON(b []byte) error {
|
||||
var s string
|
||||
|
||||
err := json.Unmarshal(b, &s)
|
||||
if err != nil {
|
||||
return err
|
||||
if err := json.Unmarshal(b, &s); err != nil {
|
||||
return fmt.Errorf("error unmarshalling CustomCloudInitIPConfig: %w", err)
|
||||
}
|
||||
|
||||
pairs := strings.Split(s, ",")
|
||||
@ -1407,15 +1400,13 @@ func (r *CustomCloudInitIPConfig) UnmarshalJSON(b []byte) error {
|
||||
func (r *CustomCloudInitSSHKeys) UnmarshalJSON(b []byte) error {
|
||||
var s string
|
||||
|
||||
err := json.Unmarshal(b, &s)
|
||||
if err != nil {
|
||||
return err
|
||||
if err := json.Unmarshal(b, &s); err != nil {
|
||||
return fmt.Errorf("error unmarshalling CustomCloudInitSSHKeys: %w", err)
|
||||
}
|
||||
|
||||
s, err = url.QueryUnescape(s)
|
||||
|
||||
s, err := url.QueryUnescape(s)
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("error unescaping CustomCloudInitSSHKeys: %w", err)
|
||||
}
|
||||
|
||||
if s != "" {
|
||||
@ -1431,9 +1422,8 @@ func (r *CustomCloudInitSSHKeys) UnmarshalJSON(b []byte) error {
|
||||
func (r *CustomCPUEmulation) UnmarshalJSON(b []byte) error {
|
||||
var s string
|
||||
|
||||
err := json.Unmarshal(b, &s)
|
||||
if err != nil {
|
||||
return err
|
||||
if err := json.Unmarshal(b, &s); err != nil {
|
||||
return fmt.Errorf("error unmarshalling CustomCPUEmulation: %w", err)
|
||||
}
|
||||
|
||||
if s == "" {
|
||||
@ -1475,8 +1465,7 @@ func (r *CustomCPUEmulation) UnmarshalJSON(b []byte) error {
|
||||
func (r *CustomEFIDisk) UnmarshalJSON(b []byte) error {
|
||||
var s string
|
||||
|
||||
err := json.Unmarshal(b, &s)
|
||||
if err != nil {
|
||||
if err := json.Unmarshal(b, &s); err != nil {
|
||||
return fmt.Errorf("failed to unmarshal CustomEFIDisk: %w", err)
|
||||
}
|
||||
|
||||
@ -1493,6 +1482,7 @@ func (r *CustomEFIDisk) UnmarshalJSON(b []byte) error {
|
||||
r.FileVolume = v[1]
|
||||
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)
|
||||
@ -1508,9 +1498,8 @@ func (r *CustomEFIDisk) UnmarshalJSON(b []byte) error {
|
||||
func (r *CustomNetworkDevice) UnmarshalJSON(b []byte) error {
|
||||
var s string
|
||||
|
||||
err := json.Unmarshal(b, &s)
|
||||
if err != nil {
|
||||
return err
|
||||
if err := json.Unmarshal(b, &s); err != nil {
|
||||
return fmt.Errorf("failed to unmarshal CustomNetworkDevice: %w", err)
|
||||
}
|
||||
|
||||
pairs := strings.Split(s, ",")
|
||||
@ -1518,6 +1507,7 @@ func (r *CustomNetworkDevice) UnmarshalJSON(b []byte) error {
|
||||
for _, p := range pairs {
|
||||
v := strings.Split(strings.TrimSpace(p), "=")
|
||||
|
||||
//nolint:nestif
|
||||
if len(v) == 2 {
|
||||
switch v[0] {
|
||||
case "bridge":
|
||||
@ -1535,28 +1525,30 @@ func (r *CustomNetworkDevice) UnmarshalJSON(b []byte) error {
|
||||
case "queues":
|
||||
iv, err := strconv.Atoi(v[1])
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("failed to parse queues: %w", err)
|
||||
}
|
||||
|
||||
r.Queues = &iv
|
||||
case "rate":
|
||||
fv, err := strconv.ParseFloat(v[1], 64)
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("failed to parse rate: %w", err)
|
||||
}
|
||||
|
||||
r.RateLimit = &fv
|
||||
|
||||
case "mtu":
|
||||
iv, err := strconv.Atoi(v[1])
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("failed to parse mtu: %w", err)
|
||||
}
|
||||
|
||||
r.MTU = &iv
|
||||
|
||||
case "tag":
|
||||
iv, err := strconv.Atoi(v[1])
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("failed to parse tag: %w", err)
|
||||
}
|
||||
|
||||
r.Tag = &iv
|
||||
@ -1567,7 +1559,7 @@ func (r *CustomNetworkDevice) UnmarshalJSON(b []byte) error {
|
||||
for i, trunk := range trunks {
|
||||
iv, err := strconv.Atoi(trunk)
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("failed to parse trunk %d: %w", i, err)
|
||||
}
|
||||
|
||||
r.Trunks[i] = iv
|
||||
@ -1588,9 +1580,8 @@ func (r *CustomNetworkDevice) UnmarshalJSON(b []byte) error {
|
||||
func (r *CustomPCIDevice) UnmarshalJSON(b []byte) error {
|
||||
var s string
|
||||
|
||||
err := json.Unmarshal(b, &s)
|
||||
if err != nil {
|
||||
return err
|
||||
if err := json.Unmarshal(b, &s); err != nil {
|
||||
return fmt.Errorf("failed to unmarshal CustomPCIDevice: %w", err)
|
||||
}
|
||||
|
||||
pairs := strings.Split(s, ",")
|
||||
@ -1627,9 +1618,8 @@ func (r *CustomPCIDevice) UnmarshalJSON(b []byte) error {
|
||||
func (r *CustomSharedMemory) UnmarshalJSON(b []byte) error {
|
||||
var s string
|
||||
|
||||
err := json.Unmarshal(b, &s)
|
||||
if err != nil {
|
||||
return err
|
||||
if err := json.Unmarshal(b, &s); err != nil {
|
||||
return fmt.Errorf("failed to unmarshal CustomSharedMemory: %w", err)
|
||||
}
|
||||
|
||||
pairs := strings.Split(s, ",")
|
||||
@ -1642,10 +1632,11 @@ func (r *CustomSharedMemory) UnmarshalJSON(b []byte) error {
|
||||
case "name":
|
||||
r.Name = &v[1]
|
||||
case "size":
|
||||
r.Size, err = strconv.Atoi(v[1])
|
||||
var err error
|
||||
|
||||
r.Size, err = strconv.Atoi(v[1])
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("failed to parse shared memory size: %w", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1658,9 +1649,8 @@ func (r *CustomSharedMemory) UnmarshalJSON(b []byte) error {
|
||||
func (r *CustomSMBIOS) UnmarshalJSON(b []byte) error {
|
||||
var s string
|
||||
|
||||
err := json.Unmarshal(b, &s)
|
||||
if err != nil {
|
||||
return err
|
||||
if err := json.Unmarshal(b, &s); err != nil {
|
||||
return fmt.Errorf("failed to unmarshal CustomSMBIOS: %w", err)
|
||||
}
|
||||
|
||||
pairs := strings.Split(s, ",")
|
||||
@ -1698,9 +1688,8 @@ func (r *CustomSMBIOS) UnmarshalJSON(b []byte) error {
|
||||
func (r *CustomStorageDevice) UnmarshalJSON(b []byte) error {
|
||||
var s string
|
||||
|
||||
err := json.Unmarshal(b, &s)
|
||||
if err != nil {
|
||||
return err
|
||||
if err := json.Unmarshal(b, &s); err != nil {
|
||||
return fmt.Errorf("failed to unmarshal CustomStorageDevice: %w", err)
|
||||
}
|
||||
|
||||
pairs := strings.Split(s, ",")
|
||||
@ -1708,8 +1697,10 @@ func (r *CustomStorageDevice) UnmarshalJSON(b []byte) error {
|
||||
for _, p := range pairs {
|
||||
v := strings.Split(strings.TrimSpace(p), "=")
|
||||
|
||||
//nolint:nestif
|
||||
if len(v) == 1 {
|
||||
r.FileVolume = v[0]
|
||||
|
||||
ext := filepath.Ext(v[0])
|
||||
if ext != "" {
|
||||
format := string([]byte(ext)[1:])
|
||||
@ -1727,28 +1718,28 @@ func (r *CustomStorageDevice) UnmarshalJSON(b []byte) error {
|
||||
case "mbps_rd":
|
||||
iv, err := strconv.Atoi(v[1])
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("failed to convert mbps_rd to int: %w", err)
|
||||
}
|
||||
|
||||
r.MaxReadSpeedMbps = &iv
|
||||
case "mbps_rd_max":
|
||||
iv, err := strconv.Atoi(v[1])
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("failed to convert mbps_rd_max to int: %w", err)
|
||||
}
|
||||
|
||||
r.BurstableReadSpeedMbps = &iv
|
||||
case "mbps_wr":
|
||||
iv, err := strconv.Atoi(v[1])
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("failed to convert mbps_wr to int: %w", err)
|
||||
}
|
||||
|
||||
r.MaxWriteSpeedMbps = &iv
|
||||
case "mbps_wr_max":
|
||||
iv, err := strconv.Atoi(v[1])
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("failed to convert mbps_wr_max to int: %w", err)
|
||||
}
|
||||
|
||||
r.BurstableWriteSpeedMbps = &iv
|
||||
@ -1783,9 +1774,8 @@ func (r *CustomStorageDevice) UnmarshalJSON(b []byte) error {
|
||||
func (r *CustomVGADevice) UnmarshalJSON(b []byte) error {
|
||||
var s string
|
||||
|
||||
err := json.Unmarshal(b, &s)
|
||||
if err != nil {
|
||||
return err
|
||||
if err := json.Unmarshal(b, &s); err != nil {
|
||||
return fmt.Errorf("failed to unmarshal CustomVGADevice: %w", err)
|
||||
}
|
||||
|
||||
if s == "" {
|
||||
@ -1804,7 +1794,7 @@ func (r *CustomVGADevice) UnmarshalJSON(b []byte) error {
|
||||
case "memory":
|
||||
m, err := strconv.Atoi(v[1])
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("failed to convert memory to int: %w", err)
|
||||
}
|
||||
|
||||
r.Memory = &m
|
||||
@ -1821,9 +1811,8 @@ func (r *CustomVGADevice) UnmarshalJSON(b []byte) error {
|
||||
func (r *CustomWatchdogDevice) UnmarshalJSON(b []byte) error {
|
||||
var s string
|
||||
|
||||
err := json.Unmarshal(b, &s)
|
||||
if err != nil {
|
||||
return err
|
||||
if err := json.Unmarshal(b, &s); err != nil {
|
||||
return fmt.Errorf("failed to unmarshal CustomWatchdogDevice: %w", err)
|
||||
}
|
||||
|
||||
if s == "" {
|
@ -4,7 +4,7 @@
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package proxmox
|
||||
package vms
|
||||
|
||||
import (
|
||||
"testing"
|
16
proxmox/pools/client.go
Normal file
16
proxmox/pools/client.go
Normal file
@ -0,0 +1,16 @@
|
||||
/*
|
||||
* 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
|
||||
}
|
87
proxmox/pools/pool.go
Normal file
87
proxmox/pools/pool.go
Normal file
@ -0,0 +1,87 @@
|
||||
/*
|
||||
* 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
|
||||
}
|
49
proxmox/pools/pool_types.go
Normal file
49
proxmox/pools/pool_types.go
Normal file
@ -0,0 +1,49 @@
|
||||
/*
|
||||
* 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"`
|
||||
}
|
306
proxmox/ssh/client.go
Normal file
306
proxmox/ssh/client.go
Normal file
@ -0,0 +1,306 @@
|
||||
/*
|
||||
* 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"
|
||||
)
|
||||
|
||||
type client struct {
|
||||
username string
|
||||
password string
|
||||
agent bool
|
||||
agentSocket string
|
||||
}
|
||||
|
||||
// NewClient creates a new SSH client.
|
||||
func NewClient(username string, password string, agent bool, agentSocket string) (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",
|
||||
)
|
||||
}
|
||||
|
||||
return &client{
|
||||
username: username,
|
||||
password: password,
|
||||
agent: agent,
|
||||
agentSocket: agentSocket,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ExecuteNodeCommands executes commands on a given node.
|
||||
func (c *client) ExecuteNodeCommands(ctx context.Context, nodeAddress string, commands []string) error {
|
||||
closeOrLogError := utils.CloseOrLogError(ctx)
|
||||
|
||||
sshClient, err := c.openNodeShell(ctx, nodeAddress)
|
||||
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, nodeAddress string, remoteFileDir string,
|
||||
d *api.FileUploadRequest,
|
||||
) error {
|
||||
// We need to upload all other files using SFTP due to API limitations.
|
||||
// Hopefully, this will not be required in future releases of Proxmox VE.
|
||||
tflog.Debug(ctx, "uploading file to datastore using SFTP", map[string]interface{}{
|
||||
"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, nodeAddress)
|
||||
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
|
||||
}
|
28
proxmox/ssh/client_types.go
Normal file
28
proxmox/ssh/client_types.go
Normal file
@ -0,0 +1,28 @@
|
||||
/*
|
||||
* 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"
|
||||
|
||||
"github.com/bpg/terraform-provider-proxmox/proxmox/api"
|
||||
)
|
||||
|
||||
// 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, nodeAddress string,
|
||||
commands []string,
|
||||
) error
|
||||
|
||||
// NodeUpload uploads a file to a node.
|
||||
NodeUpload(
|
||||
ctx context.Context, nodeAddress string,
|
||||
remoteFileDir string, fileUploadRequest *api.FileUploadRequest,
|
||||
) error
|
||||
}
|
16
proxmox/storage/client.go
Normal file
16
proxmox/storage/client.go
Normal file
@ -0,0 +1,16 @@
|
||||
/*
|
||||
* 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
|
||||
}
|
54
proxmox/storage/storage.go
Normal file
54
proxmox/storage/storage.go
Normal file
@ -0,0 +1,54 @@
|
||||
/*
|
||||
* 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
|
||||
}
|
26
proxmox/storage/storage_types.go
Normal file
26
proxmox/storage/storage_types.go
Normal file
@ -0,0 +1,26 @@
|
||||
/*
|
||||
* 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/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,18 +0,0 @@
|
||||
package types
|
||||
|
||||
import "context"
|
||||
|
||||
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
|
||||
}
|
@ -43,6 +43,7 @@ func (r DiskSize) MarshalJSON() ([]byte, error) {
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot marshal disk size: %w", err)
|
||||
}
|
||||
|
||||
return bytes, nil
|
||||
}
|
||||
|
||||
@ -54,12 +55,13 @@ func (r *DiskSize) UnmarshalJSON(b []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*r = DiskSize(size)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// parseDiskSize parses a disk size string into a number of bytes
|
||||
// parseDiskSize parses a disk size string into a number of bytes.
|
||||
func parseDiskSize(size *string) (int64, error) {
|
||||
if size == nil {
|
||||
return 0, nil
|
||||
@ -71,6 +73,7 @@ func parseDiskSize(size *string) (int64, error) {
|
||||
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
|
||||
|
16
proxmox/version/client.go
Normal file
16
proxmox/version/client.go
Normal file
@ -0,0 +1,16 @@
|
||||
/*
|
||||
* 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
|
||||
}
|
31
proxmox/version/version.go
Normal file
31
proxmox/version/version.go
Normal file
@ -0,0 +1,31 @@
|
||||
/*
|
||||
* 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
|
||||
}
|
20
proxmox/version/version_types.go
Normal file
20
proxmox/version/version_types.go
Normal file
@ -0,0 +1,20 @@
|
||||
/*
|
||||
* 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"`
|
||||
}
|
@ -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 proxmox
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net/http"
|
||||
"sort"
|
||||
)
|
||||
|
||||
// GetACL retrieves the access control list.
|
||||
func (c *VirtualEnvironmentClient) GetACL(
|
||||
ctx context.Context,
|
||||
) ([]*VirtualEnvironmentACLGetResponseData, error) {
|
||||
resBody := &VirtualEnvironmentACLGetResponseBody{}
|
||||
err := c.DoRequest(ctx, http.MethodGet, "access/acl", nil, resBody)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if resBody.Data == nil {
|
||||
return nil, errors.New("the server did not include a data object in the response")
|
||||
}
|
||||
|
||||
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 *VirtualEnvironmentClient) UpdateACL(
|
||||
ctx context.Context,
|
||||
d *VirtualEnvironmentACLUpdateRequestBody,
|
||||
) error {
|
||||
return c.DoRequest(ctx, http.MethodPut, "access/acl", d, nil)
|
||||
}
|
@ -1,30 +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/types"
|
||||
|
||||
// VirtualEnvironmentAuthenticationResponseBody contains the body from an authentication response.
|
||||
type VirtualEnvironmentAuthenticationResponseBody struct {
|
||||
Data *VirtualEnvironmentAuthenticationResponseData `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
// VirtualEnvironmentAuthenticationResponseCapabilities contains the supported capabilities for a session.
|
||||
type VirtualEnvironmentAuthenticationResponseCapabilities 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"`
|
||||
}
|
||||
|
||||
// VirtualEnvironmentAuthenticationResponseData contains the data from an authentication response.
|
||||
type VirtualEnvironmentAuthenticationResponseData struct {
|
||||
ClusterName *string `json:"clustername,omitempty"`
|
||||
CSRFPreventionToken *string `json:"CSRFPreventionToken,omitempty"`
|
||||
Capabilities *VirtualEnvironmentAuthenticationResponseCapabilities `json:"cap,omitempty"`
|
||||
Ticket *string `json:"ticket,omitempty"`
|
||||
Username string `json:"username"`
|
||||
}
|
@ -1,67 +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 (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
// DeleteCertificate deletes the custom certificate for a node.
|
||||
func (c *VirtualEnvironmentClient) DeleteCertificate(
|
||||
ctx context.Context,
|
||||
nodeName string,
|
||||
d *VirtualEnvironmentCertificateDeleteRequestBody,
|
||||
) error {
|
||||
return c.DoRequest(
|
||||
ctx,
|
||||
http.MethodDelete,
|
||||
fmt.Sprintf("nodes/%s/certificates/custom", url.PathEscape(nodeName)),
|
||||
d,
|
||||
nil,
|
||||
)
|
||||
}
|
||||
|
||||
// ListCertificates retrieves the list of certificates for a node.
|
||||
func (c *VirtualEnvironmentClient) ListCertificates(
|
||||
ctx context.Context,
|
||||
nodeName string,
|
||||
) (*[]VirtualEnvironmentCertificateListResponseData, error) {
|
||||
resBody := &VirtualEnvironmentCertificateListResponseBody{}
|
||||
err := c.DoRequest(
|
||||
ctx,
|
||||
http.MethodGet,
|
||||
fmt.Sprintf("nodes/%s/certificates/info", url.PathEscape(nodeName)),
|
||||
nil,
|
||||
resBody,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if resBody.Data == nil {
|
||||
return nil, errors.New("the server did not include a data object in the response")
|
||||
}
|
||||
|
||||
return resBody.Data, nil
|
||||
}
|
||||
|
||||
// UpdateCertificate updates the custom certificate for a node.
|
||||
func (c *VirtualEnvironmentClient) UpdateCertificate(
|
||||
ctx context.Context,
|
||||
nodeName string,
|
||||
d *VirtualEnvironmentCertificateUpdateRequestBody,
|
||||
) error {
|
||||
return c.DoRequest(
|
||||
ctx,
|
||||
http.MethodPost,
|
||||
fmt.Sprintf("nodes/%s/certificates/custom", url.PathEscape(nodeName)),
|
||||
d,
|
||||
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 proxmox
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"github.com/bpg/terraform-provider-proxmox/proxmox/cluster"
|
||||
"github.com/bpg/terraform-provider-proxmox/proxmox/container"
|
||||
"github.com/bpg/terraform-provider-proxmox/proxmox/vm"
|
||||
)
|
||||
|
||||
const (
|
||||
basePathJSONAPI = "api2/json"
|
||||
)
|
||||
|
||||
// VirtualEnvironmentClient implements an API client for the Proxmox Virtual Environment API.
|
||||
type VirtualEnvironmentClient struct {
|
||||
Endpoint string
|
||||
Insecure bool
|
||||
OTP *string
|
||||
Password string
|
||||
Username string
|
||||
SSHUsername string
|
||||
SSHPassword string
|
||||
SSHAgent bool
|
||||
SSHAgentSocket string
|
||||
|
||||
authenticationData *VirtualEnvironmentAuthenticationResponseData
|
||||
httpClient *http.Client
|
||||
}
|
||||
|
||||
// VirtualEnvironmentErrorResponseBody contains the body of an error response.
|
||||
type VirtualEnvironmentErrorResponseBody struct {
|
||||
Data *string
|
||||
Errors *map[string]string
|
||||
}
|
||||
|
||||
// VirtualEnvironmentMultiPartData enables multipart uploads in DoRequest.
|
||||
type VirtualEnvironmentMultiPartData struct {
|
||||
Boundary string
|
||||
Reader io.Reader
|
||||
Size *int64
|
||||
}
|
||||
|
||||
type API interface {
|
||||
Cluster() *cluster.Client
|
||||
VM(nodeName string, vmID int) *vm.Client
|
||||
Container(nodeName string, vmID int) *container.Client
|
||||
}
|
||||
|
||||
func (c *VirtualEnvironmentClient) API() API {
|
||||
return &client{c}
|
||||
}
|
||||
|
||||
func (c *VirtualEnvironmentClient) ExpandPath(path string) string {
|
||||
return path
|
||||
}
|
||||
|
||||
type client struct {
|
||||
c *VirtualEnvironmentClient
|
||||
}
|
||||
|
||||
func (c *client) Cluster() *cluster.Client {
|
||||
return &cluster.Client{Client: c.c}
|
||||
}
|
||||
|
||||
func (c *client) VM(nodeName string, vmID int) *vm.Client {
|
||||
return &vm.Client{Client: c.c, NodeName: nodeName, VMID: vmID}
|
||||
}
|
||||
|
||||
func (c *client) Container(nodeName string, vmID int) *container.Client {
|
||||
return &container.Client{Client: c.c, NodeName: nodeName, VMID: vmID}
|
||||
}
|
@ -1,274 +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 (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// CloneContainer clones a container.
|
||||
func (c *VirtualEnvironmentClient) CloneContainer(
|
||||
ctx context.Context,
|
||||
nodeName string,
|
||||
vmID int,
|
||||
d *VirtualEnvironmentContainerCloneRequestBody,
|
||||
) error {
|
||||
return c.DoRequest(
|
||||
ctx,
|
||||
http.MethodPost,
|
||||
fmt.Sprintf("nodes/%s/lxc/%d/clone", url.PathEscape(nodeName), vmID),
|
||||
d,
|
||||
nil,
|
||||
)
|
||||
}
|
||||
|
||||
// CreateContainer creates a container.
|
||||
func (c *VirtualEnvironmentClient) CreateContainer(
|
||||
ctx context.Context,
|
||||
nodeName string,
|
||||
d *VirtualEnvironmentContainerCreateRequestBody,
|
||||
) error {
|
||||
return c.DoRequest(ctx, http.MethodPost, fmt.Sprintf("nodes/%s/lxc", url.PathEscape(nodeName)), d, nil)
|
||||
}
|
||||
|
||||
// DeleteContainer deletes a container.
|
||||
func (c *VirtualEnvironmentClient) DeleteContainer(
|
||||
ctx context.Context,
|
||||
nodeName string,
|
||||
vmID int,
|
||||
) error {
|
||||
return c.DoRequest(
|
||||
ctx,
|
||||
http.MethodDelete,
|
||||
fmt.Sprintf("nodes/%s/lxc/%d", url.PathEscape(nodeName), vmID),
|
||||
nil,
|
||||
nil,
|
||||
)
|
||||
}
|
||||
|
||||
// GetContainer retrieves a container.
|
||||
func (c *VirtualEnvironmentClient) GetContainer(
|
||||
ctx context.Context,
|
||||
nodeName string,
|
||||
vmID int,
|
||||
) (*VirtualEnvironmentContainerGetResponseData, error) {
|
||||
resBody := &VirtualEnvironmentContainerGetResponseBody{}
|
||||
err := c.DoRequest(
|
||||
ctx,
|
||||
http.MethodGet,
|
||||
fmt.Sprintf("nodes/%s/lxc/%d/config", url.PathEscape(nodeName), vmID),
|
||||
nil,
|
||||
resBody,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if resBody.Data == nil {
|
||||
return nil, errors.New("the server did not include a data object in the response")
|
||||
}
|
||||
|
||||
return resBody.Data, nil
|
||||
}
|
||||
|
||||
// GetContainerStatus retrieves the status for a container.
|
||||
func (c *VirtualEnvironmentClient) GetContainerStatus(
|
||||
ctx context.Context,
|
||||
nodeName string,
|
||||
vmID int,
|
||||
) (*VirtualEnvironmentContainerGetStatusResponseData, error) {
|
||||
resBody := &VirtualEnvironmentContainerGetStatusResponseBody{}
|
||||
err := c.DoRequest(
|
||||
ctx,
|
||||
http.MethodGet,
|
||||
fmt.Sprintf("nodes/%s/lxc/%d/status/current", url.PathEscape(nodeName), vmID),
|
||||
nil,
|
||||
resBody,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if resBody.Data == nil {
|
||||
return nil, errors.New("the server did not include a data object in the response")
|
||||
}
|
||||
|
||||
return resBody.Data, nil
|
||||
}
|
||||
|
||||
// RebootContainer reboots a container.
|
||||
func (c *VirtualEnvironmentClient) RebootContainer(
|
||||
ctx context.Context,
|
||||
nodeName string,
|
||||
vmID int,
|
||||
d *VirtualEnvironmentContainerRebootRequestBody,
|
||||
) error {
|
||||
return c.DoRequest(
|
||||
ctx,
|
||||
http.MethodPost,
|
||||
fmt.Sprintf("nodes/%s/lxc/%d/status/reboot", url.PathEscape(nodeName), vmID),
|
||||
d,
|
||||
nil,
|
||||
)
|
||||
}
|
||||
|
||||
// ShutdownContainer shuts down a container.
|
||||
func (c *VirtualEnvironmentClient) ShutdownContainer(
|
||||
ctx context.Context,
|
||||
nodeName string,
|
||||
vmID int,
|
||||
d *VirtualEnvironmentContainerShutdownRequestBody,
|
||||
) error {
|
||||
return c.DoRequest(
|
||||
ctx,
|
||||
http.MethodPost,
|
||||
fmt.Sprintf("nodes/%s/lxc/%d/status/shutdown", url.PathEscape(nodeName), vmID),
|
||||
d,
|
||||
nil,
|
||||
)
|
||||
}
|
||||
|
||||
// StartContainer starts a container.
|
||||
func (c *VirtualEnvironmentClient) StartContainer(
|
||||
ctx context.Context,
|
||||
nodeName string,
|
||||
vmID int,
|
||||
) error {
|
||||
return c.DoRequest(
|
||||
ctx,
|
||||
http.MethodPost,
|
||||
fmt.Sprintf("nodes/%s/lxc/%d/status/start", url.PathEscape(nodeName), vmID),
|
||||
nil,
|
||||
nil,
|
||||
)
|
||||
}
|
||||
|
||||
// StopContainer stops a container immediately.
|
||||
func (c *VirtualEnvironmentClient) StopContainer(
|
||||
ctx context.Context,
|
||||
nodeName string,
|
||||
vmID int,
|
||||
) error {
|
||||
return c.DoRequest(
|
||||
ctx,
|
||||
http.MethodPost,
|
||||
fmt.Sprintf("nodes/%s/lxc/%d/status/stop", url.PathEscape(nodeName), vmID),
|
||||
nil,
|
||||
nil,
|
||||
)
|
||||
}
|
||||
|
||||
// UpdateContainer updates a container.
|
||||
func (c *VirtualEnvironmentClient) UpdateContainer(
|
||||
ctx context.Context,
|
||||
nodeName string,
|
||||
vmID int,
|
||||
d *VirtualEnvironmentContainerUpdateRequestBody,
|
||||
) error {
|
||||
return c.DoRequest(
|
||||
ctx,
|
||||
http.MethodPut,
|
||||
fmt.Sprintf("nodes/%s/lxc/%d/config", url.PathEscape(nodeName), vmID),
|
||||
d,
|
||||
nil,
|
||||
)
|
||||
}
|
||||
|
||||
// WaitForContainerState waits for a container to reach a specific state.
|
||||
//
|
||||
//nolint:dupl
|
||||
func (c *VirtualEnvironmentClient) WaitForContainerState(
|
||||
ctx context.Context,
|
||||
nodeName string,
|
||||
vmID int,
|
||||
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, nodeName, vmID)
|
||||
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 ctx.Err()
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Errorf(
|
||||
"timeout while waiting for container \"%d\" to enter the state \"%s\"",
|
||||
vmID,
|
||||
state,
|
||||
)
|
||||
}
|
||||
|
||||
// WaitForContainerLock waits for a container lock to be released.
|
||||
//
|
||||
//nolint:dupl
|
||||
func (c *VirtualEnvironmentClient) WaitForContainerLock(
|
||||
ctx context.Context,
|
||||
nodeName string,
|
||||
vmID int,
|
||||
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, nodeName, vmID)
|
||||
|
||||
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 ctx.Err()
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Errorf("timeout while waiting for container \"%d\" to become unlocked", vmID)
|
||||
}
|
@ -1,812 +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 (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/bpg/terraform-provider-proxmox/proxmox/types"
|
||||
)
|
||||
|
||||
// VirtualEnvironmentContainerCloneRequestBody contains the data for an container clone request.
|
||||
type VirtualEnvironmentContainerCloneRequestBody 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"`
|
||||
}
|
||||
|
||||
// VirtualEnvironmentContainerCreateRequestBody contains the data for an user create request.
|
||||
type VirtualEnvironmentContainerCreateRequestBody 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 *VirtualEnvironmentContainerCustomFeatures `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 VirtualEnvironmentContainerCustomMountPointArray `json:"mp,omitempty" url:"mp,omitempty,numbered"`
|
||||
NetworkInterfaces VirtualEnvironmentContainerCustomNetworkInterfaceArray `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 *VirtualEnvironmentContainerCustomRootFS `json:"rootfs,omitempty" url:"rootfs,omitempty"`
|
||||
SSHKeys *VirtualEnvironmentContainerCustomSSHKeys `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 *VirtualEnvironmentContainerCustomStartupBehavior `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"`
|
||||
}
|
||||
|
||||
// VirtualEnvironmentContainerCustomFeatures contains the values for the "features" property.
|
||||
type VirtualEnvironmentContainerCustomFeatures 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"`
|
||||
}
|
||||
|
||||
// VirtualEnvironmentContainerCustomMountPoint contains the values for the "mp[n]" properties.
|
||||
type VirtualEnvironmentContainerCustomMountPoint 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"`
|
||||
}
|
||||
|
||||
// VirtualEnvironmentContainerCustomMountPointArray is an array of VirtualEnvironmentContainerCustomMountPoint.
|
||||
type VirtualEnvironmentContainerCustomMountPointArray []VirtualEnvironmentContainerCustomMountPoint
|
||||
|
||||
// VirtualEnvironmentContainerCustomNetworkInterface contains the values for the "net[n]" properties.
|
||||
type VirtualEnvironmentContainerCustomNetworkInterface 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"`
|
||||
}
|
||||
|
||||
// VirtualEnvironmentContainerCustomNetworkInterfaceArray is an array of VirtualEnvironmentContainerCustomNetworkInterface.
|
||||
type VirtualEnvironmentContainerCustomNetworkInterfaceArray []VirtualEnvironmentContainerCustomNetworkInterface
|
||||
|
||||
// VirtualEnvironmentContainerCustomRootFS contains the values for the "rootfs" property.
|
||||
type VirtualEnvironmentContainerCustomRootFS 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"`
|
||||
}
|
||||
|
||||
// VirtualEnvironmentContainerCustomSSHKeys contains the values for the "ssh-public-keys" property.
|
||||
type VirtualEnvironmentContainerCustomSSHKeys []string
|
||||
|
||||
// VirtualEnvironmentContainerCustomStartupBehavior contains the values for the "startup" property.
|
||||
type VirtualEnvironmentContainerCustomStartupBehavior 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"`
|
||||
}
|
||||
|
||||
// VirtualEnvironmentContainerGetResponseBody contains the body from an user get response.
|
||||
type VirtualEnvironmentContainerGetResponseBody struct {
|
||||
Data *VirtualEnvironmentContainerGetResponseData `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
// VirtualEnvironmentContainerGetResponseData contains the data from an user get response.
|
||||
type VirtualEnvironmentContainerGetResponseData 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 *VirtualEnvironmentContainerCustomFeatures `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 VirtualEnvironmentContainerCustomMountPoint `json:"mp0,omitempty"`
|
||||
MountPoint1 VirtualEnvironmentContainerCustomMountPoint `json:"mp1,omitempty"`
|
||||
MountPoint2 VirtualEnvironmentContainerCustomMountPoint `json:"mp2,omitempty"`
|
||||
MountPoint3 VirtualEnvironmentContainerCustomMountPoint `json:"mp3,omitempty"`
|
||||
NetworkInterface0 *VirtualEnvironmentContainerCustomNetworkInterface `json:"net0,omitempty"`
|
||||
NetworkInterface1 *VirtualEnvironmentContainerCustomNetworkInterface `json:"net1,omitempty"`
|
||||
NetworkInterface2 *VirtualEnvironmentContainerCustomNetworkInterface `json:"net2,omitempty"`
|
||||
NetworkInterface3 *VirtualEnvironmentContainerCustomNetworkInterface `json:"net3,omitempty"`
|
||||
NetworkInterface4 *VirtualEnvironmentContainerCustomNetworkInterface `json:"net4,omitempty"`
|
||||
NetworkInterface5 *VirtualEnvironmentContainerCustomNetworkInterface `json:"net5,omitempty"`
|
||||
NetworkInterface6 *VirtualEnvironmentContainerCustomNetworkInterface `json:"net6,omitempty"`
|
||||
NetworkInterface7 *VirtualEnvironmentContainerCustomNetworkInterface `json:"net7,omitempty"`
|
||||
OSType *string `json:"ostype,omitempty"`
|
||||
Protection *types.CustomBool `json:"protection,omitempty"`
|
||||
RootFS *VirtualEnvironmentContainerCustomRootFS `json:"rootfs,omitempty"`
|
||||
StartOnBoot *types.CustomBool `json:"onboot,omitempty"`
|
||||
StartupBehavior *VirtualEnvironmentContainerCustomStartupBehavior `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"`
|
||||
}
|
||||
|
||||
// VirtualEnvironmentContainerGetStatusResponseBody contains the body from a container get status response.
|
||||
type VirtualEnvironmentContainerGetStatusResponseBody struct {
|
||||
Data *VirtualEnvironmentContainerGetStatusResponseData `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
// VirtualEnvironmentContainerGetStatusResponseData contains the data from a container get status response.
|
||||
type VirtualEnvironmentContainerGetStatusResponseData 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"`
|
||||
}
|
||||
|
||||
// VirtualEnvironmentContainerRebootRequestBody contains the body for a container reboot request.
|
||||
type VirtualEnvironmentContainerRebootRequestBody struct {
|
||||
Timeout *int `json:"timeout,omitempty" url:"timeout,omitempty"`
|
||||
}
|
||||
|
||||
// VirtualEnvironmentContainerShutdownRequestBody contains the body for a container shutdown request.
|
||||
type VirtualEnvironmentContainerShutdownRequestBody struct {
|
||||
ForceStop *types.CustomBool `json:"forceStop,omitempty" url:"forceStop,omitempty,int"`
|
||||
Timeout *int `json:"timeout,omitempty" url:"timeout,omitempty"`
|
||||
}
|
||||
|
||||
// VirtualEnvironmentContainerUpdateRequestBody contains the data for an user update request.
|
||||
type VirtualEnvironmentContainerUpdateRequestBody VirtualEnvironmentContainerCreateRequestBody
|
||||
|
||||
// EncodeValues converts a VirtualEnvironmentContainerCustomFeatures struct to a URL vlaue.
|
||||
func (r VirtualEnvironmentContainerCustomFeatures) 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 VirtualEnvironmentContainerCustomMountPoint struct to a URL vlaue.
|
||||
func (r VirtualEnvironmentContainerCustomMountPoint) 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 VirtualEnvironmentContainerCustomMountPointArray array to multiple URL values.
|
||||
func (r VirtualEnvironmentContainerCustomMountPointArray) EncodeValues(
|
||||
key string,
|
||||
v *url.Values,
|
||||
) error {
|
||||
for i, d := range r {
|
||||
err := d.EncodeValues(fmt.Sprintf("%s%d", key, i), v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// EncodeValues converts a VirtualEnvironmentContainerCustomNetworkInterface struct to a URL vlaue.
|
||||
func (r VirtualEnvironmentContainerCustomNetworkInterface) 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 VirtualEnvironmentContainerCustomNetworkInterfaceArray array to multiple URL values.
|
||||
func (r VirtualEnvironmentContainerCustomNetworkInterfaceArray) EncodeValues(
|
||||
key string,
|
||||
v *url.Values,
|
||||
) error {
|
||||
for i, d := range r {
|
||||
err := d.EncodeValues(fmt.Sprintf("%s%d", key, i), v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// EncodeValues converts a VirtualEnvironmentContainerCustomRootFS struct to a URL vlaue.
|
||||
func (r VirtualEnvironmentContainerCustomRootFS) 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 VirtualEnvironmentContainerCustomSSHKeys array to a URL vlaue.
|
||||
func (r VirtualEnvironmentContainerCustomSSHKeys) EncodeValues(key string, v *url.Values) error {
|
||||
v.Add(key, strings.Join(r, "\n"))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// EncodeValues converts a VirtualEnvironmentContainerCustomStartupBehavior struct to a URL vlaue.
|
||||
func (r VirtualEnvironmentContainerCustomStartupBehavior) 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 VirtualEnvironmentContainerCustomFeatures string to an object.
|
||||
func (r *VirtualEnvironmentContainerCustomFeatures) UnmarshalJSON(b []byte) error {
|
||||
var s string
|
||||
|
||||
err := json.Unmarshal(b, &s)
|
||||
if err != nil {
|
||||
return 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 VirtualEnvironmentContainerCustomMountPoint string to an object.
|
||||
func (r *VirtualEnvironmentContainerCustomMountPoint) UnmarshalJSON(b []byte) error {
|
||||
var s string
|
||||
|
||||
err := json.Unmarshal(b, &s)
|
||||
if err != nil {
|
||||
return 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 VirtualEnvironmentContainerCustomNetworkInterface string to an object.
|
||||
func (r *VirtualEnvironmentContainerCustomNetworkInterface) UnmarshalJSON(b []byte) error {
|
||||
var s string
|
||||
|
||||
err := json.Unmarshal(b, &s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
pairs := strings.Split(s, ",")
|
||||
|
||||
for _, p := range pairs {
|
||||
v := strings.Split(strings.TrimSpace(p), "=")
|
||||
|
||||
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 err
|
||||
}
|
||||
|
||||
r.MTU = &iv
|
||||
case "name":
|
||||
r.Name = v[1]
|
||||
case "rate":
|
||||
fv, err := strconv.ParseFloat(v[1], 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
r.RateLimit = &fv
|
||||
case "tag":
|
||||
iv, err := strconv.Atoi(v[1])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
r.Tag = &iv
|
||||
case "trunks":
|
||||
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 err
|
||||
}
|
||||
}
|
||||
|
||||
r.Trunks = &a
|
||||
} else {
|
||||
var a []int
|
||||
r.Trunks = &a
|
||||
}
|
||||
case "type":
|
||||
r.Type = &v[1]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnmarshalJSON converts a VirtualEnvironmentContainerCustomRootFS string to an object.
|
||||
func (r *VirtualEnvironmentContainerCustomRootFS) UnmarshalJSON(b []byte) error {
|
||||
var s string
|
||||
|
||||
err := json.Unmarshal(b, &s)
|
||||
if err != nil {
|
||||
return 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 VirtualEnvironmentContainerCustomStartupBehavior string to an object.
|
||||
func (r *VirtualEnvironmentContainerCustomStartupBehavior) UnmarshalJSON(b []byte) error {
|
||||
var s string
|
||||
|
||||
err := json.Unmarshal(b, &s)
|
||||
if err != nil {
|
||||
return 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 err
|
||||
}
|
||||
|
||||
r.Down = &iv
|
||||
case "order":
|
||||
iv, err := strconv.Atoi(v[1])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
r.Order = &iv
|
||||
case "up":
|
||||
iv, err := strconv.Atoi(v[1])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
r.Up = &iv
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
@ -1,46 +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 (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
// GetDNS retrieves the DNS configuration for a node.
|
||||
func (c *VirtualEnvironmentClient) GetDNS(
|
||||
ctx context.Context,
|
||||
nodeName string,
|
||||
) (*VirtualEnvironmentDNSGetResponseData, error) {
|
||||
resBody := &VirtualEnvironmentDNSGetResponseBody{}
|
||||
err := c.DoRequest(
|
||||
ctx,
|
||||
http.MethodGet,
|
||||
fmt.Sprintf("nodes/%s/dns", url.PathEscape(nodeName)),
|
||||
nil,
|
||||
resBody,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if resBody.Data == nil {
|
||||
return nil, errors.New("the server did not include a data object in the response")
|
||||
}
|
||||
|
||||
return resBody.Data, nil
|
||||
}
|
||||
|
||||
// UpdateDNS updates the DNS configuration for a node.
|
||||
func (c *VirtualEnvironmentClient) UpdateDNS(
|
||||
ctx context.Context,
|
||||
nodeName string,
|
||||
d *VirtualEnvironmentDNSUpdateRequestBody,
|
||||
) error {
|
||||
return c.DoRequest(ctx, http.MethodPut, fmt.Sprintf("nodes/%s/dns", url.PathEscape(nodeName)), d, nil)
|
||||
}
|
@ -1,83 +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 (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"sort"
|
||||
)
|
||||
|
||||
// CreateGroup creates an access group.
|
||||
func (c *VirtualEnvironmentClient) CreateGroup(
|
||||
ctx context.Context,
|
||||
d *VirtualEnvironmentGroupCreateRequestBody,
|
||||
) error {
|
||||
return c.DoRequest(ctx, http.MethodPost, "access/groups", d, nil)
|
||||
}
|
||||
|
||||
// DeleteGroup deletes an access group.
|
||||
func (c *VirtualEnvironmentClient) DeleteGroup(ctx context.Context, id string) error {
|
||||
return c.DoRequest(ctx, http.MethodDelete, fmt.Sprintf("access/groups/%s", url.PathEscape(id)), nil, nil)
|
||||
}
|
||||
|
||||
// GetGroup retrieves an access group.
|
||||
func (c *VirtualEnvironmentClient) GetGroup(
|
||||
ctx context.Context,
|
||||
id string,
|
||||
) (*VirtualEnvironmentGroupGetResponseData, error) {
|
||||
resBody := &VirtualEnvironmentGroupGetResponseBody{}
|
||||
err := c.DoRequest(
|
||||
ctx,
|
||||
http.MethodGet,
|
||||
fmt.Sprintf("access/groups/%s", url.PathEscape(id)),
|
||||
nil,
|
||||
resBody,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if resBody.Data == nil {
|
||||
return nil, errors.New("the server did not include a data object in the response")
|
||||
}
|
||||
|
||||
sort.Strings(resBody.Data.Members)
|
||||
|
||||
return resBody.Data, nil
|
||||
}
|
||||
|
||||
// ListGroups retrieves a list of access groups.
|
||||
func (c *VirtualEnvironmentClient) ListGroups(
|
||||
ctx context.Context,
|
||||
) ([]*VirtualEnvironmentGroupListResponseData, error) {
|
||||
resBody := &VirtualEnvironmentGroupListResponseBody{}
|
||||
err := c.DoRequest(ctx, http.MethodGet, "access/groups", nil, resBody)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if resBody.Data == nil {
|
||||
return nil, errors.New("the server did not include a data object in the response")
|
||||
}
|
||||
|
||||
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 *VirtualEnvironmentClient) UpdateGroup(
|
||||
ctx context.Context,
|
||||
id string,
|
||||
d *VirtualEnvironmentGroupUpdateRequestBody,
|
||||
) error {
|
||||
return c.DoRequest(ctx, http.MethodPut, fmt.Sprintf("access/groups/%s", url.PathEscape(id)), d, nil)
|
||||
}
|
@ -1,38 +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
|
||||
|
||||
// VirtualEnvironmentGroupCreateRequestBody contains the data for an access group create request.
|
||||
type VirtualEnvironmentGroupCreateRequestBody struct {
|
||||
Comment *string `json:"comment,omitempty" url:"comment,omitempty"`
|
||||
ID string `json:"groupid" url:"groupid"`
|
||||
}
|
||||
|
||||
// VirtualEnvironmentGroupGetResponseBody contains the body from an access group get response.
|
||||
type VirtualEnvironmentGroupGetResponseBody struct {
|
||||
Data *VirtualEnvironmentGroupGetResponseData `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
// VirtualEnvironmentGroupGetResponseData contains the data from an access group get response.
|
||||
type VirtualEnvironmentGroupGetResponseData struct {
|
||||
Comment *string `json:"comment,omitempty"`
|
||||
Members []string `json:"members"`
|
||||
}
|
||||
|
||||
// VirtualEnvironmentGroupListResponseBody contains the body from an access group list response.
|
||||
type VirtualEnvironmentGroupListResponseBody struct {
|
||||
Data []*VirtualEnvironmentGroupListResponseData `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
// VirtualEnvironmentGroupListResponseData contains the data from an access group list response.
|
||||
type VirtualEnvironmentGroupListResponseData struct {
|
||||
Comment *string `json:"comment,omitempty"`
|
||||
ID string `json:"groupid"`
|
||||
}
|
||||
|
||||
// VirtualEnvironmentGroupUpdateRequestBody contains the data for an access group update request.
|
||||
type VirtualEnvironmentGroupUpdateRequestBody struct {
|
||||
Comment *string `json:"comment,omitempty" url:"comment,omitempty"`
|
||||
}
|
@ -1,46 +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 (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
// GetHosts retrieves the Hosts configuration for a node.
|
||||
func (c *VirtualEnvironmentClient) GetHosts(
|
||||
ctx context.Context,
|
||||
nodeName string,
|
||||
) (*VirtualEnvironmentHostsGetResponseData, error) {
|
||||
resBody := &VirtualEnvironmentHostsGetResponseBody{}
|
||||
err := c.DoRequest(
|
||||
ctx,
|
||||
http.MethodGet,
|
||||
fmt.Sprintf("nodes/%s/hosts", url.PathEscape(nodeName)),
|
||||
nil,
|
||||
resBody,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if resBody.Data == nil {
|
||||
return nil, errors.New("the server did not include a data object in the response")
|
||||
}
|
||||
|
||||
return resBody.Data, nil
|
||||
}
|
||||
|
||||
// UpdateHosts updates the Hosts configuration for a node.
|
||||
func (c *VirtualEnvironmentClient) UpdateHosts(
|
||||
ctx context.Context,
|
||||
nodeName string,
|
||||
d *VirtualEnvironmentHostsUpdateRequestBody,
|
||||
) error {
|
||||
return c.DoRequest(ctx, http.MethodPost, fmt.Sprintf("nodes/%s/hosts", url.PathEscape(nodeName)), d, nil)
|
||||
}
|
@ -1,22 +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
|
||||
|
||||
// VirtualEnvironmentHostsGetResponseBody contains the body from a hosts get response.
|
||||
type VirtualEnvironmentHostsGetResponseBody struct {
|
||||
Data *VirtualEnvironmentHostsGetResponseData `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
// VirtualEnvironmentHostsGetResponseData contains the data from a hosts get response.
|
||||
type VirtualEnvironmentHostsGetResponseData struct {
|
||||
Data string `json:"data"`
|
||||
Digest *string `json:"digest,omitempty"`
|
||||
}
|
||||
|
||||
// VirtualEnvironmentHostsUpdateRequestBody contains the body for a hosts update request.
|
||||
type VirtualEnvironmentHostsUpdateRequestBody struct {
|
||||
Data string `json:"data" url:"data"`
|
||||
Digest *string `json:"digest,omitempty" url:"digest,omitempty"`
|
||||
}
|
@ -1,379 +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 (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/terraform-plugin-log/tflog"
|
||||
"github.com/skeema/knownhosts"
|
||||
|
||||
"golang.org/x/crypto/ssh"
|
||||
"golang.org/x/crypto/ssh/agent"
|
||||
)
|
||||
|
||||
// ExecuteNodeCommands executes commands on a given node.
|
||||
func (c *VirtualEnvironmentClient) ExecuteNodeCommands(
|
||||
ctx context.Context,
|
||||
nodeName string,
|
||||
commands []string,
|
||||
) error {
|
||||
closeOrLogError := CloseOrLogError(ctx)
|
||||
|
||||
sshClient, err := c.OpenNodeShell(ctx, nodeName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer closeOrLogError(sshClient)
|
||||
|
||||
sshSession, err := sshClient.NewSession()
|
||||
if err != nil {
|
||||
return 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
|
||||
}
|
||||
|
||||
// GetNodeIP retrieves the IP address of a node.
|
||||
func (c *VirtualEnvironmentClient) GetNodeIP(
|
||||
ctx context.Context,
|
||||
nodeName string,
|
||||
) (*string, error) {
|
||||
networkDevices, err := c.ListNodeNetworkDevices(ctx, nodeName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
nodeAddress := ""
|
||||
|
||||
for _, d := range networkDevices {
|
||||
if d.Address != nil {
|
||||
nodeAddress = *d.Address
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if nodeAddress == "" {
|
||||
return nil, fmt.Errorf("failed to determine the IP address of node \"%s\"", nodeName)
|
||||
}
|
||||
|
||||
nodeAddressParts := strings.Split(nodeAddress, "/")
|
||||
|
||||
return &nodeAddressParts[0], nil
|
||||
}
|
||||
|
||||
// GetNodeTime retrieves the time information for a node.
|
||||
func (c *VirtualEnvironmentClient) GetNodeTime(
|
||||
ctx context.Context,
|
||||
nodeName string,
|
||||
) (*VirtualEnvironmentNodeGetTimeResponseData, error) {
|
||||
resBody := &VirtualEnvironmentNodeGetTimeResponseBody{}
|
||||
err := c.DoRequest(
|
||||
ctx,
|
||||
http.MethodGet,
|
||||
fmt.Sprintf("nodes/%s/time", url.PathEscape(nodeName)),
|
||||
nil,
|
||||
resBody,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if resBody.Data == nil {
|
||||
return nil, errors.New("the server did not include a data object in the response")
|
||||
}
|
||||
|
||||
return resBody.Data, nil
|
||||
}
|
||||
|
||||
// GetNodeTaskStatus retrieves the status of a node task.
|
||||
func (c *VirtualEnvironmentClient) GetNodeTaskStatus(
|
||||
ctx context.Context,
|
||||
nodeName string,
|
||||
upid string,
|
||||
) (*VirtualEnvironmentNodeGetTaskStatusResponseData, error) {
|
||||
resBody := &VirtualEnvironmentNodeGetTaskStatusResponseBody{}
|
||||
err := c.DoRequest(
|
||||
ctx,
|
||||
http.MethodGet,
|
||||
fmt.Sprintf("nodes/%s/tasks/%s/status", url.PathEscape(nodeName), url.PathEscape(upid)),
|
||||
nil,
|
||||
resBody,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if resBody.Data == nil {
|
||||
return nil, errors.New("the server did not include a data object in the response")
|
||||
}
|
||||
|
||||
return resBody.Data, nil
|
||||
}
|
||||
|
||||
// ListNodeNetworkDevices retrieves a list of network devices for a specific nodes.
|
||||
func (c *VirtualEnvironmentClient) ListNodeNetworkDevices(
|
||||
ctx context.Context,
|
||||
nodeName string,
|
||||
) ([]*VirtualEnvironmentNodeNetworkDeviceListResponseData, error) {
|
||||
resBody := &VirtualEnvironmentNodeNetworkDeviceListResponseBody{}
|
||||
err := c.DoRequest(
|
||||
ctx,
|
||||
http.MethodGet,
|
||||
fmt.Sprintf("nodes/%s/network", url.PathEscape(nodeName)),
|
||||
nil,
|
||||
resBody,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if resBody.Data == nil {
|
||||
return nil, errors.New("the server did not include a data object in the response")
|
||||
}
|
||||
|
||||
sort.Slice(resBody.Data, func(i, j int) bool {
|
||||
return resBody.Data[i].Priority < resBody.Data[j].Priority
|
||||
})
|
||||
|
||||
return resBody.Data, nil
|
||||
}
|
||||
|
||||
// ListNodes retrieves a list of nodes.
|
||||
func (c *VirtualEnvironmentClient) ListNodes(
|
||||
ctx context.Context,
|
||||
) ([]*VirtualEnvironmentNodeListResponseData, error) {
|
||||
resBody := &VirtualEnvironmentNodeListResponseBody{}
|
||||
err := c.DoRequest(ctx, http.MethodGet, "nodes", nil, resBody)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if resBody.Data == nil {
|
||||
return nil, errors.New("the server did not include a data object in the response")
|
||||
}
|
||||
|
||||
sort.Slice(resBody.Data, func(i, j int) bool {
|
||||
return resBody.Data[i].Name < resBody.Data[j].Name
|
||||
})
|
||||
|
||||
return resBody.Data, nil
|
||||
}
|
||||
|
||||
// OpenNodeShell establishes a new SSH connection to a node.
|
||||
func (c *VirtualEnvironmentClient) OpenNodeShell(
|
||||
ctx context.Context,
|
||||
nodeName string,
|
||||
) (*ssh.Client, error) {
|
||||
nodeAddress, err := c.GetNodeIP(ctx, nodeName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
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 {
|
||||
err := kh(hostname, remote, key)
|
||||
if knownhosts.IsHostKeyChanged(err) {
|
||||
return fmt.Errorf("REMOTE HOST IDENTIFICATION HAS CHANGED for host %s! This may indicate a MitM attack", hostname)
|
||||
}
|
||||
|
||||
if knownhosts.IsHostUnknown(err) {
|
||||
f, ferr := os.OpenFile(khPath, os.O_APPEND|os.O_WRONLY, 0o600)
|
||||
if ferr == nil {
|
||||
defer 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": err,
|
||||
})
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
})
|
||||
|
||||
sshConfig := &ssh.ClientConfig{
|
||||
User: c.SSHUsername,
|
||||
Auth: []ssh.AuthMethod{ssh.Password(c.SSHPassword)},
|
||||
HostKeyCallback: cb,
|
||||
HostKeyAlgorithms: kh.HostKeyAlgorithms(sshHost),
|
||||
}
|
||||
|
||||
tflog.Info(ctx, fmt.Sprintf("Agent is set to %t", c.SSHAgent))
|
||||
|
||||
if c.SSHAgent {
|
||||
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.SSHUsername,
|
||||
})
|
||||
return sshClient, nil
|
||||
}
|
||||
|
||||
// CreateSSHClientAgent establishes an ssh connection through the agent authentication mechanism
|
||||
func (c *VirtualEnvironmentClient) CreateSSHClientAgent(
|
||||
ctx context.Context,
|
||||
cb ssh.HostKeyCallback,
|
||||
kh knownhosts.HostKeyCallback,
|
||||
sshHost string,
|
||||
) (*ssh.Client, error) {
|
||||
if c.SSHAgentSocket == "" {
|
||||
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.SSHAgentSocket)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed connecting to SSH auth socket '%s': %w", c.SSHAgentSocket, err)
|
||||
}
|
||||
|
||||
ag := agent.NewClient(conn)
|
||||
|
||||
sshConfig := &ssh.ClientConfig{
|
||||
User: c.SSHUsername,
|
||||
Auth: []ssh.AuthMethod{ssh.PublicKeysCallback(ag.Signers), ssh.Password(c.SSHPassword)},
|
||||
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.SSHUsername,
|
||||
})
|
||||
return sshClient, nil
|
||||
}
|
||||
|
||||
// UpdateNodeTime updates the time on a node.
|
||||
func (c *VirtualEnvironmentClient) UpdateNodeTime(
|
||||
ctx context.Context,
|
||||
nodeName string,
|
||||
d *VirtualEnvironmentNodeUpdateTimeRequestBody,
|
||||
) error {
|
||||
return c.DoRequest(ctx, http.MethodPut, fmt.Sprintf("nodes/%s/time", url.PathEscape(nodeName)), d, nil)
|
||||
}
|
||||
|
||||
// WaitForNodeTask waits for a specific node task to complete.
|
||||
func (c *VirtualEnvironmentClient) WaitForNodeTask(
|
||||
ctx context.Context,
|
||||
nodeName string,
|
||||
upid string,
|
||||
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 {
|
||||
status, err := c.GetNodeTaskStatus(ctx, nodeName, upid)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if status.Status != "running" {
|
||||
if status.ExitCode != "OK" {
|
||||
return fmt.Errorf(
|
||||
"task \"%s\" on node \"%s\" failed to complete with error: %s",
|
||||
upid,
|
||||
nodeName,
|
||||
status.ExitCode,
|
||||
)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
|
||||
timeElapsed = time.Since(timeStart)
|
||||
|
||||
if ctx.Err() != nil {
|
||||
return ctx.Err()
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Errorf(
|
||||
"timeout while waiting for task \"%s\" on node \"%s\" to complete",
|
||||
upid,
|
||||
nodeName,
|
||||
)
|
||||
}
|
@ -1,104 +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 (
|
||||
"encoding/json"
|
||||
"net/url"
|
||||
|
||||
"github.com/bpg/terraform-provider-proxmox/proxmox/types"
|
||||
)
|
||||
|
||||
// CustomNodeCommands contains an array of commands to execute.
|
||||
type CustomNodeCommands []string
|
||||
|
||||
// VirtualEnvironmentNodeExecuteRequestBody contains the data for a node execute request.
|
||||
type VirtualEnvironmentNodeExecuteRequestBody struct {
|
||||
Commands CustomNodeCommands `json:"commands" url:"commands"`
|
||||
}
|
||||
|
||||
// VirtualEnvironmentNodeGetTimeResponseBody contains the body from a node time zone get response.
|
||||
type VirtualEnvironmentNodeGetTimeResponseBody struct {
|
||||
Data *VirtualEnvironmentNodeGetTimeResponseData `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
// VirtualEnvironmentNodeGetTimeResponseData contains the data from a node list response.
|
||||
type VirtualEnvironmentNodeGetTimeResponseData struct {
|
||||
LocalTime types.CustomTimestamp `json:"localtime"`
|
||||
TimeZone string `json:"timezone"`
|
||||
UTCTime types.CustomTimestamp `json:"time"`
|
||||
}
|
||||
|
||||
// VirtualEnvironmentNodeGetTaskStatusResponseBody contains the body from a node get task status response.
|
||||
type VirtualEnvironmentNodeGetTaskStatusResponseBody struct {
|
||||
Data *VirtualEnvironmentNodeGetTaskStatusResponseData `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
// VirtualEnvironmentNodeGetTaskStatusResponseData contains the data from a node get task status response.
|
||||
type VirtualEnvironmentNodeGetTaskStatusResponseData struct {
|
||||
PID int `json:"pid,omitempty"`
|
||||
Status string `json:"status,omitempty"`
|
||||
ExitCode string `json:"exitstatus,omitempty"`
|
||||
}
|
||||
|
||||
// VirtualEnvironmentNodeListResponseBody contains the body from a node list response.
|
||||
type VirtualEnvironmentNodeListResponseBody struct {
|
||||
Data []*VirtualEnvironmentNodeListResponseData `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
// VirtualEnvironmentNodeListResponseData contains the data from a node list response.
|
||||
type VirtualEnvironmentNodeListResponseData 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"`
|
||||
}
|
||||
|
||||
// VirtualEnvironmentNodeNetworkDeviceListResponseBody contains the body from a node network device list response.
|
||||
type VirtualEnvironmentNodeNetworkDeviceListResponseBody struct {
|
||||
Data []*VirtualEnvironmentNodeNetworkDeviceListResponseData `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
// VirtualEnvironmentNodeNetworkDeviceListResponseData contains the data from a node network device list response.
|
||||
type VirtualEnvironmentNodeNetworkDeviceListResponseData struct {
|
||||
Active *types.CustomBool `json:"active,omitempty"`
|
||||
Address *string `json:"address,omitempty"`
|
||||
Autostart *types.CustomBool `json:"autostart,omitempty"`
|
||||
BridgeFD *string `json:"bridge_fd,omitempty"`
|
||||
BridgePorts *string `json:"bridge_ports,omitempty"`
|
||||
BridgeSTP *string `json:"bridge_stp,omitempty"`
|
||||
CIDR *string `json:"cidr,omitempty"`
|
||||
Exists *types.CustomBool `json:"exists,omitempty"`
|
||||
Families *[]string `json:"families,omitempty"`
|
||||
Gateway *string `json:"gateway,omitempty"`
|
||||
Iface string `json:"iface"`
|
||||
MethodIPv4 *string `json:"method,omitempty"`
|
||||
MethodIPv6 *string `json:"method6,omitempty"`
|
||||
Netmask *string `json:"netmask,omitempty"`
|
||||
Priority int `json:"priority"`
|
||||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
// VirtualEnvironmentNodeUpdateTimeRequestBody contains the body for a node time update request.
|
||||
type VirtualEnvironmentNodeUpdateTimeRequestBody struct {
|
||||
TimeZone string `json:"timezone" url:"timezone"`
|
||||
}
|
||||
|
||||
// EncodeValues converts a CustomNodeCommands array to a JSON encoded URL vlaue.
|
||||
func (r CustomNodeCommands) EncodeValues(key string, v *url.Values) error {
|
||||
jsonArrayBytes, err := json.Marshal(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
v.Add(key, string(jsonArrayBytes))
|
||||
|
||||
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 proxmox
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"sort"
|
||||
)
|
||||
|
||||
// CreatePool creates a pool.
|
||||
func (c *VirtualEnvironmentClient) CreatePool(
|
||||
ctx context.Context,
|
||||
d *VirtualEnvironmentPoolCreateRequestBody,
|
||||
) error {
|
||||
return c.DoRequest(ctx, http.MethodPost, "pools", d, nil)
|
||||
}
|
||||
|
||||
// DeletePool deletes a pool.
|
||||
func (c *VirtualEnvironmentClient) DeletePool(ctx context.Context, id string) error {
|
||||
return c.DoRequest(ctx, http.MethodDelete, fmt.Sprintf("pools/%s", url.PathEscape(id)), nil, nil)
|
||||
}
|
||||
|
||||
// GetPool retrieves a pool.
|
||||
func (c *VirtualEnvironmentClient) GetPool(
|
||||
ctx context.Context,
|
||||
id string,
|
||||
) (*VirtualEnvironmentPoolGetResponseData, error) {
|
||||
resBody := &VirtualEnvironmentPoolGetResponseBody{}
|
||||
err := c.DoRequest(ctx, http.MethodGet, fmt.Sprintf("pools/%s", url.PathEscape(id)), nil, resBody)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if resBody.Data == nil {
|
||||
return nil, errors.New("the server did not include a data object in the response")
|
||||
}
|
||||
|
||||
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 *VirtualEnvironmentClient) ListPools(
|
||||
ctx context.Context,
|
||||
) ([]*VirtualEnvironmentPoolListResponseData, error) {
|
||||
resBody := &VirtualEnvironmentPoolListResponseBody{}
|
||||
err := c.DoRequest(ctx, http.MethodGet, "pools", nil, resBody)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if resBody.Data == nil {
|
||||
return nil, errors.New("the server did not include a data object in the response")
|
||||
}
|
||||
|
||||
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 *VirtualEnvironmentClient) UpdatePool(
|
||||
ctx context.Context,
|
||||
id string,
|
||||
d *VirtualEnvironmentPoolUpdateRequestBody,
|
||||
) error {
|
||||
return c.DoRequest(ctx, http.MethodPut, fmt.Sprintf("pools/%s", url.PathEscape(id)), d, nil)
|
||||
}
|
@ -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 proxmox
|
||||
|
||||
// VirtualEnvironmentPoolCreateRequestBody contains the data for an pool create request.
|
||||
type VirtualEnvironmentPoolCreateRequestBody struct {
|
||||
Comment *string `json:"comment,omitempty" url:"comment,omitempty"`
|
||||
ID string `json:"groupid" url:"poolid"`
|
||||
}
|
||||
|
||||
// VirtualEnvironmentPoolGetResponseBody contains the body from an pool get response.
|
||||
type VirtualEnvironmentPoolGetResponseBody struct {
|
||||
Data *VirtualEnvironmentPoolGetResponseData `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
// VirtualEnvironmentPoolGetResponseData contains the data from an pool get response.
|
||||
type VirtualEnvironmentPoolGetResponseData struct {
|
||||
Comment *string `json:"comment,omitempty"`
|
||||
Members []VirtualEnvironmentPoolGetResponseMembers `json:"members,omitempty"`
|
||||
}
|
||||
|
||||
// VirtualEnvironmentPoolGetResponseMembers contains the members data from an 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"`
|
||||
}
|
||||
|
||||
// VirtualEnvironmentPoolListResponseBody contains the body from an pool list response.
|
||||
type VirtualEnvironmentPoolListResponseBody struct {
|
||||
Data []*VirtualEnvironmentPoolListResponseData `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
// VirtualEnvironmentPoolListResponseData contains the data from an pool list response.
|
||||
type VirtualEnvironmentPoolListResponseData struct {
|
||||
Comment *string `json:"comment,omitempty"`
|
||||
ID string `json:"poolid"`
|
||||
}
|
||||
|
||||
// VirtualEnvironmentPoolUpdateRequestBody contains the data for an pool update request.
|
||||
type VirtualEnvironmentPoolUpdateRequestBody struct {
|
||||
Comment *string `json:"comment,omitempty" url:"comment,omitempty"`
|
||||
}
|
@ -1,85 +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 (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"sort"
|
||||
|
||||
"github.com/bpg/terraform-provider-proxmox/proxmox/types"
|
||||
)
|
||||
|
||||
// CreateRole creates an access role.
|
||||
func (c *VirtualEnvironmentClient) CreateRole(
|
||||
ctx context.Context,
|
||||
d *VirtualEnvironmentRoleCreateRequestBody,
|
||||
) error {
|
||||
return c.DoRequest(ctx, http.MethodPost, "access/roles", d, nil)
|
||||
}
|
||||
|
||||
// DeleteRole deletes an access role.
|
||||
func (c *VirtualEnvironmentClient) DeleteRole(ctx context.Context, id string) error {
|
||||
return c.DoRequest(ctx, http.MethodDelete, fmt.Sprintf("access/roles/%s", url.PathEscape(id)), nil, nil)
|
||||
}
|
||||
|
||||
// GetRole retrieves an access role.
|
||||
func (c *VirtualEnvironmentClient) GetRole(
|
||||
ctx context.Context,
|
||||
id string,
|
||||
) (*types.CustomPrivileges, error) {
|
||||
resBody := &VirtualEnvironmentRoleGetResponseBody{}
|
||||
err := c.DoRequest(ctx, http.MethodGet, fmt.Sprintf("access/roles/%s", url.PathEscape(id)), nil, resBody)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if resBody.Data == nil {
|
||||
return nil, errors.New("the server did not include a data object in the response")
|
||||
}
|
||||
|
||||
sort.Strings(*resBody.Data)
|
||||
|
||||
return resBody.Data, nil
|
||||
}
|
||||
|
||||
// ListRoles retrieves a list of access roles.
|
||||
func (c *VirtualEnvironmentClient) ListRoles(
|
||||
ctx context.Context,
|
||||
) ([]*VirtualEnvironmentRoleListResponseData, error) {
|
||||
resBody := &VirtualEnvironmentRoleListResponseBody{}
|
||||
err := c.DoRequest(ctx, http.MethodGet, "access/roles", nil, resBody)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if resBody.Data == nil {
|
||||
return nil, errors.New("the server did not include a data object in the response")
|
||||
}
|
||||
|
||||
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 *VirtualEnvironmentClient) UpdateRole(
|
||||
ctx context.Context,
|
||||
id string,
|
||||
d *VirtualEnvironmentRoleUpdateRequestBody,
|
||||
) error {
|
||||
return c.DoRequest(ctx, http.MethodPut, fmt.Sprintf("access/roles/%s", url.PathEscape(id)), d, 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 proxmox
|
||||
|
||||
import "github.com/bpg/terraform-provider-proxmox/proxmox/types"
|
||||
|
||||
// VirtualEnvironmentRoleCreateRequestBody contains the data for an access group create request.
|
||||
type VirtualEnvironmentRoleCreateRequestBody struct {
|
||||
ID string `json:"roleid" url:"roleid"`
|
||||
Privileges types.CustomPrivileges `json:"privs" url:"privs,comma"`
|
||||
}
|
||||
|
||||
// VirtualEnvironmentRoleGetResponseBody contains the body from an access group get response.
|
||||
type VirtualEnvironmentRoleGetResponseBody struct {
|
||||
Data *types.CustomPrivileges `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
// VirtualEnvironmentRoleListResponseBody contains the body from an access group list response.
|
||||
type VirtualEnvironmentRoleListResponseBody struct {
|
||||
Data []*VirtualEnvironmentRoleListResponseData `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
// VirtualEnvironmentRoleListResponseData contains the data from an access group list response.
|
||||
type VirtualEnvironmentRoleListResponseData struct {
|
||||
ID string `json:"roleid"`
|
||||
Privileges *types.CustomPrivileges `json:"privs,omitempty"`
|
||||
Special *types.CustomBool `json:"special,omitempty"`
|
||||
}
|
||||
|
||||
// VirtualEnvironmentRoleUpdateRequestBody contains the data for an access group update request.
|
||||
type VirtualEnvironmentRoleUpdateRequestBody struct {
|
||||
Privileges types.CustomPrivileges `json:"privs" url:"privs,comma"`
|
||||
}
|
@ -1,111 +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 (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"github.com/bpg/terraform-provider-proxmox/proxmox/types"
|
||||
)
|
||||
|
||||
// ChangeUserPassword changes a user's password.
|
||||
func (c *VirtualEnvironmentClient) ChangeUserPassword(
|
||||
ctx context.Context,
|
||||
id, password string,
|
||||
) error {
|
||||
d := VirtualEnvironmentUserChangePasswordRequestBody{
|
||||
ID: id,
|
||||
Password: password,
|
||||
}
|
||||
|
||||
return c.DoRequest(ctx, http.MethodPut, "access/password", d, nil)
|
||||
}
|
||||
|
||||
// CreateUser creates a user.
|
||||
func (c *VirtualEnvironmentClient) CreateUser(
|
||||
ctx context.Context,
|
||||
d *VirtualEnvironmentUserCreateRequestBody,
|
||||
) error {
|
||||
return c.DoRequest(ctx, http.MethodPost, "access/users", d, nil)
|
||||
}
|
||||
|
||||
// DeleteUser deletes an user.
|
||||
func (c *VirtualEnvironmentClient) DeleteUser(ctx context.Context, id string) error {
|
||||
return c.DoRequest(ctx, http.MethodDelete, fmt.Sprintf("access/users/%s", url.PathEscape(id)), nil, nil)
|
||||
}
|
||||
|
||||
// GetUser retrieves a user.
|
||||
func (c *VirtualEnvironmentClient) GetUser(
|
||||
ctx context.Context,
|
||||
id string,
|
||||
) (*VirtualEnvironmentUserGetResponseData, error) {
|
||||
resBody := &VirtualEnvironmentUserGetResponseBody{}
|
||||
err := c.DoRequest(ctx, http.MethodGet, fmt.Sprintf("access/users/%s", url.PathEscape(id)), nil, resBody)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if resBody.Data == nil {
|
||||
return nil, errors.New("the server did not include a data object in the response")
|
||||
}
|
||||
|
||||
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 *VirtualEnvironmentClient) ListUsers(
|
||||
ctx context.Context,
|
||||
) ([]*VirtualEnvironmentUserListResponseData, error) {
|
||||
resBody := &VirtualEnvironmentUserListResponseBody{}
|
||||
err := c.DoRequest(ctx, http.MethodGet, "access/users", nil, resBody)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if resBody.Data == nil {
|
||||
return nil, errors.New("the server did not include a data object in the response")
|
||||
}
|
||||
|
||||
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 *VirtualEnvironmentClient) UpdateUser(
|
||||
ctx context.Context,
|
||||
id string,
|
||||
d *VirtualEnvironmentUserUpdateRequestBody,
|
||||
) error {
|
||||
return c.DoRequest(ctx, http.MethodPut, fmt.Sprintf("access/users/%s", url.PathEscape(id)), d, 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 proxmox
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// Version retrieves the version information.
|
||||
func (c *VirtualEnvironmentClient) Version(
|
||||
ctx context.Context,
|
||||
) (*VirtualEnvironmentVersionResponseData, error) {
|
||||
resBody := &VirtualEnvironmentVersionResponseBody{}
|
||||
err := c.DoRequest(ctx, http.MethodGet, "version", nil, resBody)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if resBody.Data == nil {
|
||||
return nil, errors.New("the server did not include a data object in the response")
|
||||
}
|
||||
|
||||
return resBody.Data, nil
|
||||
}
|
@ -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 proxmox
|
||||
|
||||
// VirtualEnvironmentVersionResponseBody contains the body from a version response.
|
||||
type VirtualEnvironmentVersionResponseBody struct {
|
||||
Data *VirtualEnvironmentVersionResponseData `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
// VirtualEnvironmentVersionResponseData contains the data from a version response.
|
||||
type VirtualEnvironmentVersionResponseData struct {
|
||||
Keyboard string `json:"keyboard"`
|
||||
Release string `json:"release"`
|
||||
RepositoryID string `json:"repoid"`
|
||||
Version string `json:"version"`
|
||||
}
|
@ -1,837 +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 (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/terraform-plugin-log/tflog"
|
||||
)
|
||||
|
||||
const (
|
||||
getVMIDStep = 1
|
||||
)
|
||||
|
||||
var (
|
||||
getVMIDCounter = -1
|
||||
getVMIDCounterMutex = &sync.Mutex{}
|
||||
)
|
||||
|
||||
// CloneVM clones a virtual machine.
|
||||
func (c *VirtualEnvironmentClient) CloneVM(
|
||||
ctx context.Context,
|
||||
nodeName string,
|
||||
vmID int,
|
||||
retries int,
|
||||
d *VirtualEnvironmentVMCloneRequestBody,
|
||||
timeout int,
|
||||
) error {
|
||||
resBody := &VirtualEnvironmentVMMoveDiskResponseBody{}
|
||||
var err error
|
||||
|
||||
// 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,
|
||||
fmt.Sprintf("nodes/%s/qemu/%d/clone", url.PathEscape(nodeName), vmID),
|
||||
d,
|
||||
resBody,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if resBody.Data == nil {
|
||||
return errors.New("the server did not include a data object in the response")
|
||||
}
|
||||
|
||||
err = c.WaitForNodeTask(ctx, nodeName, *resBody.Data, timeout, 5)
|
||||
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
time.Sleep(10 * time.Second)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// CreateVM creates a virtual machine.
|
||||
func (c *VirtualEnvironmentClient) CreateVM(
|
||||
ctx context.Context,
|
||||
nodeName string,
|
||||
d *VirtualEnvironmentVMCreateRequestBody,
|
||||
timeout int,
|
||||
) error {
|
||||
taskID, err := c.CreateVMAsync(ctx, nodeName, d)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = c.WaitForNodeTask(ctx, nodeName, *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 *VirtualEnvironmentClient) CreateVMAsync(
|
||||
ctx context.Context,
|
||||
nodeName string,
|
||||
d *VirtualEnvironmentVMCreateRequestBody,
|
||||
) (*string, error) {
|
||||
resBody := &VirtualEnvironmentVMCreateResponseBody{}
|
||||
err := c.DoRequest(
|
||||
ctx,
|
||||
http.MethodPost,
|
||||
fmt.Sprintf("nodes/%s/qemu", url.PathEscape(nodeName)),
|
||||
d,
|
||||
resBody,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if resBody.Data == nil {
|
||||
return nil, errors.New("the server did not include a data object in the response")
|
||||
}
|
||||
|
||||
return resBody.Data, nil
|
||||
}
|
||||
|
||||
// DeleteVM deletes a virtual machine.
|
||||
func (c *VirtualEnvironmentClient) DeleteVM(ctx context.Context, nodeName string, vmID int) error {
|
||||
return c.DoRequest(
|
||||
ctx,
|
||||
http.MethodDelete,
|
||||
fmt.Sprintf(
|
||||
"nodes/%s/qemu/%d?destroy-unreferenced-disks=1&purge=1",
|
||||
url.PathEscape(nodeName),
|
||||
vmID,
|
||||
),
|
||||
nil,
|
||||
nil,
|
||||
)
|
||||
}
|
||||
|
||||
// GetVM retrieves a virtual machine.
|
||||
func (c *VirtualEnvironmentClient) GetVM(
|
||||
ctx context.Context,
|
||||
nodeName string,
|
||||
vmID int,
|
||||
) (*VirtualEnvironmentVMGetResponseData, error) {
|
||||
resBody := &VirtualEnvironmentVMGetResponseBody{}
|
||||
err := c.DoRequest(
|
||||
ctx,
|
||||
http.MethodGet,
|
||||
fmt.Sprintf("nodes/%s/qemu/%d/config", url.PathEscape(nodeName), vmID),
|
||||
nil,
|
||||
resBody,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if resBody.Data == nil {
|
||||
return nil, errors.New("the server did not include a data object in the response")
|
||||
}
|
||||
|
||||
return resBody.Data, nil
|
||||
}
|
||||
|
||||
// GetVMID retrieves the next available VM identifier.
|
||||
func (c *VirtualEnvironmentClient) GetVMID(ctx context.Context) (*int, error) {
|
||||
getVMIDCounterMutex.Lock()
|
||||
defer getVMIDCounterMutex.Unlock()
|
||||
|
||||
if getVMIDCounter < 0 {
|
||||
nextVMID, err := c.API().Cluster().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.API().Cluster().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")
|
||||
}
|
||||
|
||||
// GetVMNetworkInterfacesFromAgent retrieves the network interfaces reported by the QEMU agent.
|
||||
func (c *VirtualEnvironmentClient) GetVMNetworkInterfacesFromAgent(
|
||||
ctx context.Context,
|
||||
nodeName string,
|
||||
vmID int,
|
||||
) (*VirtualEnvironmentVMGetQEMUNetworkInterfacesResponseData, error) {
|
||||
resBody := &VirtualEnvironmentVMGetQEMUNetworkInterfacesResponseBody{}
|
||||
err := c.DoRequest(
|
||||
ctx,
|
||||
http.MethodGet,
|
||||
fmt.Sprintf(
|
||||
"nodes/%s/qemu/%d/agent/network-get-interfaces",
|
||||
url.PathEscape(nodeName),
|
||||
vmID,
|
||||
),
|
||||
nil,
|
||||
resBody,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if resBody.Data == nil {
|
||||
return nil, errors.New("the server did not include a data object in the response")
|
||||
}
|
||||
|
||||
return resBody.Data, nil
|
||||
}
|
||||
|
||||
// GetVMStatus retrieves the status for a virtual machine.
|
||||
func (c *VirtualEnvironmentClient) GetVMStatus(
|
||||
ctx context.Context,
|
||||
nodeName string,
|
||||
vmID int,
|
||||
) (*VirtualEnvironmentVMGetStatusResponseData, error) {
|
||||
resBody := &VirtualEnvironmentVMGetStatusResponseBody{}
|
||||
err := c.DoRequest(
|
||||
ctx,
|
||||
http.MethodGet,
|
||||
fmt.Sprintf("nodes/%s/qemu/%d/status/current", url.PathEscape(nodeName), vmID),
|
||||
nil,
|
||||
resBody,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if resBody.Data == nil {
|
||||
return nil, errors.New("the server did not include a data object in the response")
|
||||
}
|
||||
|
||||
return resBody.Data, nil
|
||||
}
|
||||
|
||||
// MigrateVM migrates a virtual machine.
|
||||
func (c *VirtualEnvironmentClient) MigrateVM(
|
||||
ctx context.Context,
|
||||
nodeName string,
|
||||
vmID int,
|
||||
d *VirtualEnvironmentVMMigrateRequestBody,
|
||||
timeout int,
|
||||
) error {
|
||||
taskID, err := c.MigrateVMAsync(ctx, nodeName, vmID, d)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = c.WaitForNodeTask(ctx, nodeName, *taskID, timeout, 5)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// MigrateVMAsync migrates a virtual machine asynchronously.
|
||||
func (c *VirtualEnvironmentClient) MigrateVMAsync(
|
||||
ctx context.Context,
|
||||
nodeName string,
|
||||
vmID int,
|
||||
d *VirtualEnvironmentVMMigrateRequestBody,
|
||||
) (*string, error) {
|
||||
resBody := &VirtualEnvironmentVMMigrateResponseBody{}
|
||||
err := c.DoRequest(
|
||||
ctx,
|
||||
http.MethodPost,
|
||||
fmt.Sprintf("nodes/%s/qemu/%d/migrate", url.PathEscape(nodeName), vmID),
|
||||
d,
|
||||
resBody,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if resBody.Data == nil {
|
||||
return nil, errors.New("the server did not include a data object in the response")
|
||||
}
|
||||
|
||||
return resBody.Data, nil
|
||||
}
|
||||
|
||||
// MoveVMDisk moves a virtual machine disk.
|
||||
func (c *VirtualEnvironmentClient) MoveVMDisk(
|
||||
ctx context.Context,
|
||||
nodeName string,
|
||||
vmID int,
|
||||
d *VirtualEnvironmentVMMoveDiskRequestBody,
|
||||
timeout int,
|
||||
) error {
|
||||
taskID, err := c.MoveVMDiskAsync(ctx, nodeName, vmID, 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.WaitForNodeTask(ctx, nodeName, *taskID, timeout, 5)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// MoveVMDiskAsync moves a virtual machine disk asynchronously.
|
||||
func (c *VirtualEnvironmentClient) MoveVMDiskAsync(
|
||||
ctx context.Context,
|
||||
nodeName string,
|
||||
vmID int,
|
||||
d *VirtualEnvironmentVMMoveDiskRequestBody,
|
||||
) (*string, error) {
|
||||
resBody := &VirtualEnvironmentVMMoveDiskResponseBody{}
|
||||
err := c.DoRequest(
|
||||
ctx,
|
||||
http.MethodPost,
|
||||
fmt.Sprintf("nodes/%s/qemu/%d/move_disk", url.PathEscape(nodeName), vmID),
|
||||
d,
|
||||
resBody,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if resBody.Data == nil {
|
||||
return nil, errors.New("the server did not include a data object in the response")
|
||||
}
|
||||
|
||||
return resBody.Data, nil
|
||||
}
|
||||
|
||||
// ListVMs retrieves a list of virtual machines.
|
||||
func (c *VirtualEnvironmentClient) ListVMs(
|
||||
ctx context.Context,
|
||||
nodeName string,
|
||||
) ([]*VirtualEnvironmentVMListResponseData, error) {
|
||||
resBody := &VirtualEnvironmentVMListResponseBody{}
|
||||
err := c.DoRequest(
|
||||
ctx,
|
||||
http.MethodGet,
|
||||
fmt.Sprintf("nodes/%s/qemu", url.PathEscape(nodeName)),
|
||||
nil,
|
||||
resBody,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if resBody.Data == nil {
|
||||
return nil, errors.New("the server did not include a data object in the response")
|
||||
}
|
||||
|
||||
return resBody.Data, nil
|
||||
}
|
||||
|
||||
// RebootVM reboots a virtual machine.
|
||||
func (c *VirtualEnvironmentClient) RebootVM(
|
||||
ctx context.Context,
|
||||
nodeName string,
|
||||
vmID int,
|
||||
d *VirtualEnvironmentVMRebootRequestBody,
|
||||
timeout int,
|
||||
) error {
|
||||
taskID, err := c.RebootVMAsync(ctx, nodeName, vmID, d)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = c.WaitForNodeTask(ctx, nodeName, *taskID, timeout, 5)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// RebootVMAsync reboots a virtual machine asynchronously.
|
||||
func (c *VirtualEnvironmentClient) RebootVMAsync(
|
||||
ctx context.Context,
|
||||
nodeName string,
|
||||
vmID int,
|
||||
d *VirtualEnvironmentVMRebootRequestBody,
|
||||
) (*string, error) {
|
||||
resBody := &VirtualEnvironmentVMRebootResponseBody{}
|
||||
err := c.DoRequest(
|
||||
ctx,
|
||||
http.MethodPost,
|
||||
fmt.Sprintf("nodes/%s/qemu/%d/status/reboot", url.PathEscape(nodeName), vmID),
|
||||
d,
|
||||
resBody,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if resBody.Data == nil {
|
||||
return nil, errors.New("the server did not include a data object in the response")
|
||||
}
|
||||
|
||||
return resBody.Data, nil
|
||||
}
|
||||
|
||||
// ResizeVMDisk resizes a virtual machine disk.
|
||||
func (c *VirtualEnvironmentClient) ResizeVMDisk(
|
||||
ctx context.Context,
|
||||
nodeName string,
|
||||
vmID int,
|
||||
d *VirtualEnvironmentVMResizeDiskRequestBody,
|
||||
) 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,
|
||||
fmt.Sprintf("nodes/%s/qemu/%d/resize", url.PathEscape(nodeName), vmID),
|
||||
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 ctx.Err()
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// ShutdownVM shuts down a virtual machine.
|
||||
func (c *VirtualEnvironmentClient) ShutdownVM(
|
||||
ctx context.Context,
|
||||
nodeName string,
|
||||
vmID int,
|
||||
d *VirtualEnvironmentVMShutdownRequestBody,
|
||||
timeout int,
|
||||
) error {
|
||||
taskID, err := c.ShutdownVMAsync(ctx, nodeName, vmID, d)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = c.WaitForNodeTask(ctx, nodeName, *taskID, timeout, 5)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ShutdownVMAsync shuts down a virtual machine asynchronously.
|
||||
func (c *VirtualEnvironmentClient) ShutdownVMAsync(
|
||||
ctx context.Context,
|
||||
nodeName string,
|
||||
vmID int,
|
||||
d *VirtualEnvironmentVMShutdownRequestBody,
|
||||
) (*string, error) {
|
||||
resBody := &VirtualEnvironmentVMShutdownResponseBody{}
|
||||
err := c.DoRequest(
|
||||
ctx,
|
||||
http.MethodPost,
|
||||
fmt.Sprintf("nodes/%s/qemu/%d/status/shutdown", url.PathEscape(nodeName), vmID),
|
||||
d,
|
||||
resBody,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if resBody.Data == nil {
|
||||
return nil, errors.New("the server did not include a data object in the response")
|
||||
}
|
||||
|
||||
return resBody.Data, nil
|
||||
}
|
||||
|
||||
// StartVM starts a virtual machine.
|
||||
func (c *VirtualEnvironmentClient) StartVM(
|
||||
ctx context.Context,
|
||||
nodeName string,
|
||||
vmID int,
|
||||
timeout int,
|
||||
) error {
|
||||
taskID, err := c.StartVMAsync(ctx, nodeName, vmID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = c.WaitForNodeTask(ctx, nodeName, *taskID, timeout, 5)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// StartVMAsync starts a virtual machine asynchronously.
|
||||
func (c *VirtualEnvironmentClient) StartVMAsync(
|
||||
ctx context.Context,
|
||||
nodeName string,
|
||||
vmID int,
|
||||
) (*string, error) {
|
||||
resBody := &VirtualEnvironmentVMStartResponseBody{}
|
||||
err := c.DoRequest(
|
||||
ctx,
|
||||
http.MethodPost,
|
||||
fmt.Sprintf("nodes/%s/qemu/%d/status/start", url.PathEscape(nodeName), vmID),
|
||||
nil,
|
||||
resBody,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if resBody.Data == nil {
|
||||
return nil, errors.New("the server did not include a data object in the response")
|
||||
}
|
||||
|
||||
return resBody.Data, nil
|
||||
}
|
||||
|
||||
// StopVM stops a virtual machine.
|
||||
func (c *VirtualEnvironmentClient) StopVM(
|
||||
ctx context.Context,
|
||||
nodeName string,
|
||||
vmID int,
|
||||
timeout int,
|
||||
) error {
|
||||
taskID, err := c.StopVMAsync(ctx, nodeName, vmID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = c.WaitForNodeTask(ctx, nodeName, *taskID, timeout, 5)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// StopVMAsync stops a virtual machine asynchronously.
|
||||
func (c *VirtualEnvironmentClient) StopVMAsync(
|
||||
ctx context.Context,
|
||||
nodeName string,
|
||||
vmID int,
|
||||
) (*string, error) {
|
||||
resBody := &VirtualEnvironmentVMStopResponseBody{}
|
||||
err := c.DoRequest(
|
||||
ctx,
|
||||
http.MethodPost,
|
||||
fmt.Sprintf("nodes/%s/qemu/%d/status/stop", url.PathEscape(nodeName), vmID),
|
||||
nil,
|
||||
resBody,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if resBody.Data == nil {
|
||||
return nil, errors.New("the server did not include a data object in the response")
|
||||
}
|
||||
|
||||
return resBody.Data, nil
|
||||
}
|
||||
|
||||
// UpdateVM updates a virtual machine.
|
||||
func (c *VirtualEnvironmentClient) UpdateVM(
|
||||
ctx context.Context,
|
||||
nodeName string,
|
||||
vmID int,
|
||||
d *VirtualEnvironmentVMUpdateRequestBody,
|
||||
) error {
|
||||
return c.DoRequest(
|
||||
ctx,
|
||||
http.MethodPut,
|
||||
fmt.Sprintf("nodes/%s/qemu/%d/config", url.PathEscape(nodeName), vmID),
|
||||
d,
|
||||
nil,
|
||||
)
|
||||
}
|
||||
|
||||
// UpdateVMAsync updates a virtual machine asynchronously.
|
||||
func (c *VirtualEnvironmentClient) UpdateVMAsync(
|
||||
ctx context.Context,
|
||||
nodeName string,
|
||||
vmID int,
|
||||
d *VirtualEnvironmentVMUpdateRequestBody,
|
||||
) (*string, error) {
|
||||
resBody := &VirtualEnvironmentVMUpdateAsyncResponseBody{}
|
||||
err := c.DoRequest(
|
||||
ctx,
|
||||
http.MethodPost,
|
||||
fmt.Sprintf("nodes/%s/qemu/%d/config", url.PathEscape(nodeName), vmID),
|
||||
d,
|
||||
resBody,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if resBody.Data == nil {
|
||||
return nil, errors.New("the server did not include a data object in the response")
|
||||
}
|
||||
|
||||
return resBody.Data, nil
|
||||
}
|
||||
|
||||
// WaitForNetworkInterfacesFromVMAgent waits for a virtual machine's QEMU agent to publish the network interfaces.
|
||||
func (c *VirtualEnvironmentClient) WaitForNetworkInterfacesFromVMAgent(
|
||||
ctx context.Context,
|
||||
nodeName string,
|
||||
vmID int,
|
||||
timeout int,
|
||||
delay int,
|
||||
waitForIP bool,
|
||||
) (*VirtualEnvironmentVMGetQEMUNetworkInterfacesResponseData, 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.GetVMNetworkInterfacesFromAgent(ctx, nodeName, vmID)
|
||||
|
||||
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, ctx.Err()
|
||||
}
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf(
|
||||
"timeout while waiting for the QEMU agent on VM \"%d\" to publish the network interfaces",
|
||||
vmID,
|
||||
)
|
||||
}
|
||||
|
||||
// WaitForNoNetworkInterfacesFromVMAgent waits for a virtual machine's QEMU agent to unpublish the network interfaces.
|
||||
func (c *VirtualEnvironmentClient) WaitForNoNetworkInterfacesFromVMAgent(
|
||||
ctx context.Context,
|
||||
nodeName string,
|
||||
vmID int,
|
||||
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, nodeName, vmID)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
|
||||
timeElapsed = time.Since(timeStart)
|
||||
|
||||
if ctx.Err() != nil {
|
||||
return ctx.Err()
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Errorf(
|
||||
"timeout while waiting for the QEMU agent on VM \"%d\" to unpublish the network interfaces",
|
||||
vmID,
|
||||
)
|
||||
}
|
||||
|
||||
// WaitForVMConfigUnlock waits for a virtual machine configuration to become unlocked.
|
||||
//
|
||||
//nolint:dupl
|
||||
func (c *VirtualEnvironmentClient) WaitForVMConfigUnlock(
|
||||
ctx context.Context,
|
||||
nodeName string,
|
||||
vmID int,
|
||||
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, nodeName, vmID)
|
||||
|
||||
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 ctx.Err()
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Errorf("timeout while waiting for VM \"%d\" configuration to become unlocked", vmID)
|
||||
}
|
||||
|
||||
// WaitForVMState waits for a virtual machine to reach a specific state.
|
||||
//
|
||||
//nolint:dupl
|
||||
func (c *VirtualEnvironmentClient) WaitForVMState(
|
||||
ctx context.Context,
|
||||
nodeName string,
|
||||
vmID int,
|
||||
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, nodeName, vmID)
|
||||
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 ctx.Err()
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Errorf("timeout while waiting for VM \"%d\" to enter the state \"%s\"", vmID, state)
|
||||
}
|
@ -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 vm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
|
||||
"github.com/bpg/terraform-provider-proxmox/proxmox/firewall"
|
||||
"github.com/bpg/terraform-provider-proxmox/proxmox/types"
|
||||
vmfirewall "github.com/bpg/terraform-provider-proxmox/proxmox/vm/firewall"
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
types.Client
|
||||
NodeName string
|
||||
VMID int
|
||||
}
|
||||
|
||||
func (c *Client) ExpandPath(path string) string {
|
||||
return fmt.Sprintf("nodes/%s/qemu/%d/%s", url.PathEscape(c.NodeName), c.VMID, path)
|
||||
}
|
||||
|
||||
func (c *Client) Firewall() firewall.API {
|
||||
return &vmfirewall.Client{
|
||||
Client: firewall.Client{Client: c},
|
||||
}
|
||||
}
|
@ -10,24 +10,40 @@ 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"
|
||||
)
|
||||
|
||||
// ProviderConfiguration is the configuration for the provider.
|
||||
type ProviderConfiguration struct {
|
||||
veClient *proxmox.VirtualEnvironmentClient
|
||||
apiClient api.Client
|
||||
sshClient ssh.Client
|
||||
}
|
||||
|
||||
func NewProviderConfiguration(veClient *proxmox.VirtualEnvironmentClient) ProviderConfiguration {
|
||||
// NewProviderConfiguration creates a new provider configuration.
|
||||
func NewProviderConfiguration(
|
||||
apiClient api.Client,
|
||||
sshClient ssh.Client,
|
||||
) ProviderConfiguration {
|
||||
return ProviderConfiguration{
|
||||
veClient: veClient,
|
||||
apiClient: apiClient,
|
||||
sshClient: sshClient,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *ProviderConfiguration) GetVEClient() (*proxmox.VirtualEnvironmentClient, error) {
|
||||
if c.veClient == nil {
|
||||
// GetClient returns the Proxmox API client.
|
||||
func (c *ProviderConfiguration) GetClient() (proxmox.Client, error) {
|
||||
if c.apiClient == nil {
|
||||
return nil, errors.New(
|
||||
"you must specify the virtual environment details in the provider configuration",
|
||||
"you must specify the API access details in the provider configuration",
|
||||
)
|
||||
}
|
||||
|
||||
return c.veClient, nil
|
||||
if c.sshClient == nil {
|
||||
return nil, errors.New(
|
||||
"you must specify the SSH access details in the provider configuration",
|
||||
)
|
||||
}
|
||||
|
||||
return proxmox.NewClient(c.apiClient, c.sshClient), nil
|
||||
}
|
||||
|
@ -17,6 +17,7 @@ import (
|
||||
"github.com/bpg/terraform-provider-proxmox/proxmoxtf/datasource/firewall"
|
||||
)
|
||||
|
||||
// FirewallAlias returns a resource that represents a single firewall alias.
|
||||
func FirewallAlias() *schema.Resource {
|
||||
return &schema.Resource{
|
||||
Schema: firewall.AliasSchema(),
|
||||
@ -24,6 +25,7 @@ func FirewallAlias() *schema.Resource {
|
||||
}
|
||||
}
|
||||
|
||||
// FirewallAliases returns a resource that represents firewall aliases.
|
||||
func FirewallAliases() *schema.Resource {
|
||||
return &schema.Resource{
|
||||
Schema: firewall.AliasesSchema(),
|
||||
@ -31,6 +33,7 @@ func FirewallAliases() *schema.Resource {
|
||||
}
|
||||
}
|
||||
|
||||
// FirewallIPSet returns a resource that represents a single firewall IP set.
|
||||
func FirewallIPSet() *schema.Resource {
|
||||
return &schema.Resource{
|
||||
Schema: firewall.IPSetSchema(),
|
||||
@ -38,6 +41,7 @@ func FirewallIPSet() *schema.Resource {
|
||||
}
|
||||
}
|
||||
|
||||
// FirewallIPSets returns a resource that represents firewall IP sets.
|
||||
func FirewallIPSets() *schema.Resource {
|
||||
return &schema.Resource{
|
||||
Schema: firewall.IPSetsSchema(),
|
||||
@ -45,30 +49,17 @@ func FirewallIPSets() *schema.Resource {
|
||||
}
|
||||
}
|
||||
|
||||
// func FirewallSecurityGroup() *schema.Resource {
|
||||
// return &schema.Resource{
|
||||
// Schema: firewall.SecurityGroupSchema(),
|
||||
// ReadContext: invokeFirewallAPI(firewall.SecurityGroupRead),
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// func FirewallSecurityGroups() *schema.Resource {
|
||||
// return &schema.Resource{
|
||||
// Schema: firewall.SecurityGroupsSchema(),
|
||||
// ReadContext: invokeFirewallAPI(firewall.SecurityGroupsRead),
|
||||
// }
|
||||
// }
|
||||
|
||||
func invokeFirewallAPI(
|
||||
f func(context.Context, fw.API, *schema.ResourceData) diag.Diagnostics,
|
||||
) func(context.Context, *schema.ResourceData, interface{}) diag.Diagnostics {
|
||||
return func(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
|
||||
config := m.(proxmoxtf.ProviderConfiguration)
|
||||
veClient, err := config.GetVEClient()
|
||||
|
||||
api, err := config.GetClient()
|
||||
if err != nil {
|
||||
return diag.FromErr(err)
|
||||
}
|
||||
|
||||
return f(ctx, veClient.API().Cluster().Firewall(), d)
|
||||
return f(ctx, api.Cluster().Firewall(), d)
|
||||
}
|
||||
}
|
||||
|
@ -30,6 +30,7 @@ const (
|
||||
mkDataSourceVirtualEnvironmentDatastoresTypes = "types"
|
||||
)
|
||||
|
||||
// Datastores returns a resource for the Proxmox data store.
|
||||
func Datastores() *schema.Resource {
|
||||
return &schema.Resource{
|
||||
Schema: map[string]*schema.Schema{
|
||||
@ -104,13 +105,13 @@ func datastoresRead(ctx context.Context, d *schema.ResourceData, m interface{})
|
||||
var diags diag.Diagnostics
|
||||
|
||||
config := m.(proxmoxtf.ProviderConfiguration)
|
||||
veClient, err := config.GetVEClient()
|
||||
api, err := config.GetClient()
|
||||
if err != nil {
|
||||
return diag.FromErr(err)
|
||||
}
|
||||
|
||||
nodeName := d.Get(mkDataSourceVirtualEnvironmentDatastoresNodeName).(string)
|
||||
list, err := veClient.ListDatastores(ctx, nodeName, nil)
|
||||
list, err := api.Node(nodeName).ListDatastores(ctx, nil)
|
||||
if err != nil {
|
||||
return diag.FromErr(err)
|
||||
}
|
||||
|
@ -17,8 +17,8 @@ import (
|
||||
// TestDatastoresInstantiation tests whether the Datastores instance can be instantiated.
|
||||
func TestDatastoresInstantiation(t *testing.T) {
|
||||
t.Parallel()
|
||||
s := Datastores()
|
||||
|
||||
s := Datastores()
|
||||
if s == nil {
|
||||
t.Fatalf("Cannot instantiate Datastores")
|
||||
}
|
||||
@ -27,6 +27,7 @@ func TestDatastoresInstantiation(t *testing.T) {
|
||||
// TestDatastoresSchema tests the Datastores schema.
|
||||
func TestDatastoresSchema(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
s := Datastores()
|
||||
|
||||
test.AssertRequiredArguments(t, s, []string{
|
||||
|
@ -22,6 +22,7 @@ const (
|
||||
mkDataSourceVirtualEnvironmentDNSServers = "servers"
|
||||
)
|
||||
|
||||
// DNS returns a resource for DNS settings on a node.
|
||||
func DNS() *schema.Resource {
|
||||
return &schema.Resource{
|
||||
Schema: map[string]*schema.Schema{
|
||||
@ -50,13 +51,14 @@ func dnsRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Di
|
||||
var diags diag.Diagnostics
|
||||
|
||||
config := m.(proxmoxtf.ProviderConfiguration)
|
||||
veClient, err := config.GetVEClient()
|
||||
api, err := config.GetClient()
|
||||
if err != nil {
|
||||
return diag.FromErr(err)
|
||||
}
|
||||
|
||||
nodeName := d.Get(mkDataSourceVirtualEnvironmentDNSNodeName).(string)
|
||||
dns, err := veClient.GetDNS(ctx, nodeName)
|
||||
|
||||
dns, err := api.Node(nodeName).GetDNS(ctx)
|
||||
if err != nil {
|
||||
return diag.FromErr(err)
|
||||
}
|
||||
|
@ -17,8 +17,8 @@ import (
|
||||
// TestDNSInstantiation tests whether the DNS instance can be instantiated.
|
||||
func TestDNSInstantiation(t *testing.T) {
|
||||
t.Parallel()
|
||||
s := DNS()
|
||||
|
||||
s := DNS()
|
||||
if s == nil {
|
||||
t.Fatalf("Cannot instantiate DNS")
|
||||
}
|
||||
@ -27,6 +27,7 @@ func TestDNSInstantiation(t *testing.T) {
|
||||
// TestDNSSchema tests the DNS schema.
|
||||
func TestDNSSchema(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
s := DNS()
|
||||
|
||||
test.AssertRequiredArguments(t, s, []string{
|
||||
|
@ -23,6 +23,7 @@ const (
|
||||
mkAliasComment = "comment"
|
||||
)
|
||||
|
||||
// AliasSchema defines the schema for the alias.
|
||||
func AliasSchema() map[string]*schema.Schema {
|
||||
return map[string]*schema.Schema{
|
||||
mkAliasName: {
|
||||
@ -43,10 +44,12 @@ func AliasSchema() map[string]*schema.Schema {
|
||||
}
|
||||
}
|
||||
|
||||
// AliasRead reads the alias.
|
||||
func AliasRead(ctx context.Context, fw firewall.API, d *schema.ResourceData) diag.Diagnostics {
|
||||
var diags diag.Diagnostics
|
||||
|
||||
aliasName := d.Get(mkAliasName).(string)
|
||||
|
||||
alias, err := fw.GetAlias(ctx, aliasName)
|
||||
if err != nil {
|
||||
return diag.FromErr(err)
|
||||
@ -62,6 +65,7 @@ func AliasRead(ctx context.Context, fw firewall.API, d *schema.ResourceData) dia
|
||||
} else {
|
||||
err = d.Set(mkAliasComment, dvAliasComment)
|
||||
}
|
||||
|
||||
diags = append(diags, diag.FromErr(err)...)
|
||||
|
||||
return diags
|
||||
|
@ -24,6 +24,7 @@ func TestAliasSchemaInstantiation(t *testing.T) {
|
||||
// TestAliasSchema tests the AliasSchema.
|
||||
func TestAliasSchema(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
s := AliasSchema()
|
||||
|
||||
structure.AssertRequiredArguments(t, s, []string{
|
||||
|
@ -20,6 +20,7 @@ const (
|
||||
mkAliasesAliasNames = "alias_names"
|
||||
)
|
||||
|
||||
// AliasesSchema defines the schema for the Aliases data source.
|
||||
func AliasesSchema() map[string]*schema.Schema {
|
||||
return map[string]*schema.Schema{
|
||||
mkAliasesAliasNames: {
|
||||
@ -31,6 +32,7 @@ func AliasesSchema() map[string]*schema.Schema {
|
||||
}
|
||||
}
|
||||
|
||||
// AliasesRead reads the aliases.
|
||||
func AliasesRead(ctx context.Context, fw firewall.API, d *schema.ResourceData) diag.Diagnostics {
|
||||
list, err := fw.ListAliases(ctx)
|
||||
if err != nil {
|
||||
|
@ -24,6 +24,7 @@ func TestAliasesSchemaInstantiation(t *testing.T) {
|
||||
// TestAliasesSchema tests the AliasesSchema.
|
||||
func TestAliasesSchema(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
s := AliasesSchema()
|
||||
|
||||
structure.AssertComputedAttributes(t, s, []string{
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user