0
0
mirror of https://github.com/bpg/terraform-provider-proxmox.git synced 2025-07-03 20:12:59 +00:00

feat(provider): add optional SSH port param to node in provider ssh block (#520)

* feat: Add optional SSH port param to node in provider ssh block

* fix: minor cleanups

---------

Co-authored-by: Pavel Boldyrev <627562+bpg@users.noreply.github.com>
This commit is contained in:
Rafał Safin 2023-09-03 01:43:08 +02:00 committed by GitHub
parent 4cccebaa62
commit 124cac247c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 63 additions and 26 deletions

View File

@ -208,3 +208,4 @@ Proxmox `provider` block:
specified multiple times to provide configuration fo multiple nodes. specified multiple times to provide configuration fo multiple nodes.
- `name` - (Required) The name of the node. - `name` - (Required) The name of the node.
- `address` - (Required) The IP address of the node. - `address` - (Required) The IP address of the node.
- `port` - (Optional) SSH port of the node. Defaults to 22.

View File

@ -12,6 +12,7 @@ import (
"regexp" "regexp"
"strings" "strings"
"github.com/hashicorp/terraform-plugin-framework-validators/int64validator"
"github.com/hashicorp/terraform-plugin-framework-validators/listvalidator" "github.com/hashicorp/terraform-plugin-framework-validators/listvalidator"
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
"github.com/hashicorp/terraform-plugin-framework/datasource" "github.com/hashicorp/terraform-plugin-framework/datasource"
@ -68,6 +69,7 @@ type proxmoxProviderModel struct {
Nodes []struct { Nodes []struct {
Name types.String `tfsdk:"name"` Name types.String `tfsdk:"name"`
Address types.String `tfsdk:"address"` Address types.String `tfsdk:"address"`
Port types.Int64 `tfsdk:"port"`
} `tfsdk:"node"` } `tfsdk:"node"`
} `tfsdk:"ssh"` } `tfsdk:"ssh"`
} }
@ -167,6 +169,11 @@ func (p *proxmoxProvider) Schema(_ context.Context, _ provider.SchemaRequest, re
Description: "The address of the Proxmox VE node.", Description: "The address of the Proxmox VE node.",
Required: true, Required: true,
}, },
"port": schema.Int64Attribute{
Description: "The port of the Proxmox VE node.",
Optional: true,
Validators: []validator.Int64{int64validator.Between(1, 65535)},
},
}, },
}, },
}, },
@ -291,8 +298,9 @@ func (p *proxmoxProvider) Configure(
sshPassword := utils.GetAnyStringEnv("PROXMOX_VE_SSH_PASSWORD") sshPassword := utils.GetAnyStringEnv("PROXMOX_VE_SSH_PASSWORD")
sshAgent := utils.GetAnyBoolEnv("PROXMOX_VE_SSH_AGENT") sshAgent := utils.GetAnyBoolEnv("PROXMOX_VE_SSH_AGENT")
sshAgentSocket := utils.GetAnyStringEnv("SSH_AUTH_SOCK", "PROXMOX_VE_SSH_AUTH_SOCK") sshAgentSocket := utils.GetAnyStringEnv("SSH_AUTH_SOCK", "PROXMOX_VE_SSH_AUTH_SOCK")
nodeOverrides := map[string]string{} nodeOverrides := map[string]ssh.ProxmoxNode{}
//nolint: nestif
if len(config.SSH) > 0 { if len(config.SSH) > 0 {
if !config.SSH[0].Username.IsNull() { if !config.SSH[0].Username.IsNull() {
sshUsername = config.SSH[0].Username.ValueString() sshUsername = config.SSH[0].Username.ValueString()
@ -311,7 +319,15 @@ func (p *proxmoxProvider) Configure(
} }
for _, n := range config.SSH[0].Nodes { for _, n := range config.SSH[0].Nodes {
nodeOverrides[n.Name.ValueString()] = n.Address.ValueString() nodePort := int32(n.Port.ValueInt64())
if nodePort == 0 {
nodePort = 22
}
nodeOverrides[n.Name.ValueString()] = ssh.ProxmoxNode{
Address: n.Address.ValueString(),
Port: nodePort,
}
} }
} }
@ -370,12 +386,12 @@ type apiResolver struct {
c api.Client c api.Client
} }
func (r *apiResolver) Resolve(ctx context.Context, nodeName string) (string, error) { func (r *apiResolver) Resolve(ctx context.Context, nodeName string) (ssh.ProxmoxNode, error) {
nc := &nodes.Client{Client: r.c, NodeName: nodeName} nc := &nodes.Client{Client: r.c, NodeName: nodeName}
networkDevices, err := nc.ListNetworkInterfaces(ctx) networkDevices, err := nc.ListNetworkInterfaces(ctx)
if err != nil { if err != nil {
return "", fmt.Errorf("failed to list network devices of node \"%s\": %w", nc.NodeName, err) return ssh.ProxmoxNode{}, fmt.Errorf("failed to list network devices of node \"%s\": %w", nc.NodeName, err)
} }
nodeAddress := "" nodeAddress := ""
@ -388,22 +404,23 @@ func (r *apiResolver) Resolve(ctx context.Context, nodeName string) (string, err
} }
if nodeAddress == "" { if nodeAddress == "" {
return "", fmt.Errorf("failed to determine the IP address of node \"%s\"", nc.NodeName) return ssh.ProxmoxNode{}, fmt.Errorf("failed to determine the IP address of node \"%s\"", nc.NodeName)
} }
nodeAddressParts := strings.Split(nodeAddress, "/") nodeAddressParts := strings.Split(nodeAddress, "/")
node := ssh.ProxmoxNode{Address: nodeAddressParts[0], Port: 22}
return nodeAddressParts[0], nil return node, nil
} }
type apiResolverWithOverrides struct { type apiResolverWithOverrides struct {
ar apiResolver ar apiResolver
overrides map[string]string overrides map[string]ssh.ProxmoxNode
} }
func (r *apiResolverWithOverrides) Resolve(ctx context.Context, nodeName string) (string, error) { func (r *apiResolverWithOverrides) Resolve(ctx context.Context, nodeName string) (ssh.ProxmoxNode, error) {
if ip, ok := r.overrides[nodeName]; ok { if node, ok := r.overrides[nodeName]; ok {
return ip, nil return node, nil
} }
return r.ar.Resolve(ctx, nodeName) return r.ar.Resolve(ctx, nodeName)

View File

@ -74,19 +74,20 @@ func NewClient(
// ExecuteNodeCommands executes commands on a given node. // ExecuteNodeCommands executes commands on a given node.
func (c *client) ExecuteNodeCommands(ctx context.Context, nodeName string, commands []string) error { func (c *client) ExecuteNodeCommands(ctx context.Context, nodeName string, commands []string) error {
ip, err := c.nodeLookup.Resolve(ctx, nodeName) node, err := c.nodeLookup.Resolve(ctx, nodeName)
if err != nil { if err != nil {
return fmt.Errorf("failed to find node endpoint: %w", err) return fmt.Errorf("failed to find node endpoint: %w", err)
} }
tflog.Debug(ctx, "executing commands on the node using SSH", map[string]interface{}{ tflog.Debug(ctx, "executing commands on the node using SSH", map[string]interface{}{
"node_address": ip, "node_address": node.Address,
"node_port": node.Port,
"commands": commands, "commands": commands,
}) })
closeOrLogError := utils.CloseOrLogError(ctx) closeOrLogError := utils.CloseOrLogError(ctx)
sshClient, err := c.openNodeShell(ctx, ip) sshClient, err := c.openNodeShell(ctx, node)
if err != nil { if err != nil {
return err return err
} }
@ -212,13 +213,13 @@ func (c *client) NodeUpload(
} }
// openNodeShell establishes a new SSH connection to a node. // openNodeShell establishes a new SSH connection to a node.
func (c *client) openNodeShell(ctx context.Context, nodeAddress string) (*ssh.Client, error) { func (c *client) openNodeShell(ctx context.Context, node ProxmoxNode) (*ssh.Client, error) {
homeDir, err := os.UserHomeDir() homeDir, err := os.UserHomeDir()
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to determine the home directory: %w", err) return nil, fmt.Errorf("failed to determine the home directory: %w", err)
} }
sshHost := fmt.Sprintf("%s:22", nodeAddress) sshHost := fmt.Sprintf("%s:%d", node.Address, node.Port)
sshPath := path.Join(homeDir, ".ssh") sshPath := path.Join(homeDir, ".ssh")
if _, err = os.Stat(sshPath); os.IsNotExist(err) { if _, err = os.Stat(sshPath); os.IsNotExist(err) {

View File

@ -10,7 +10,13 @@ import (
"context" "context"
) )
// ProxmoxNode represents node address and port for SSH connection.
type ProxmoxNode struct {
Address string
Port int32
}
// NodeResolver is an interface for resolving node names to IP addresses to use for SSH connection. // NodeResolver is an interface for resolving node names to IP addresses to use for SSH connection.
type NodeResolver interface { type NodeResolver interface {
Resolve(ctx context.Context, nodeName string) (string, error) Resolve(ctx context.Context, nodeName string) (ProxmoxNode, error)
} }

View File

@ -129,12 +129,15 @@ func providerConfigure(_ context.Context, d *schema.ResourceData) (interface{},
sshConf[mkProviderSSHAgentSocket] = sshAgentSocket sshConf[mkProviderSSHAgentSocket] = sshAgentSocket
} }
nodeOverrides := map[string]string{} nodeOverrides := map[string]ssh.ProxmoxNode{}
if ns, ok := sshConf[mkProviderSSHNode]; ok { if ns, ok := sshConf[mkProviderSSHNode]; ok {
for _, n := range ns.([]interface{}) { for _, n := range ns.([]interface{}) {
node := n.(map[string]interface{}) node := n.(map[string]interface{})
nodeOverrides[node[mkProviderSSHNodeName].(string)] = node[mkProviderSSHNodeAddress].(string) nodeOverrides[node[mkProviderSSHNodeName].(string)] = ssh.ProxmoxNode{
Address: node[mkProviderSSHNodeAddress].(string),
Port: node[mkProviderSSHNodePort].(int32),
}
} }
} }
@ -161,12 +164,12 @@ type apiResolver struct {
c api.Client c api.Client
} }
func (r *apiResolver) Resolve(ctx context.Context, nodeName string) (string, error) { func (r *apiResolver) Resolve(ctx context.Context, nodeName string) (ssh.ProxmoxNode, error) {
nc := &nodes.Client{Client: r.c, NodeName: nodeName} nc := &nodes.Client{Client: r.c, NodeName: nodeName}
networkDevices, err := nc.ListNetworkInterfaces(ctx) networkDevices, err := nc.ListNetworkInterfaces(ctx)
if err != nil { if err != nil {
return "", fmt.Errorf("failed to list network devices of node \"%s\": %w", nc.NodeName, err) return ssh.ProxmoxNode{}, fmt.Errorf("failed to list network devices of node \"%s\": %w", nc.NodeName, err)
} }
nodeAddress := "" nodeAddress := ""
@ -179,22 +182,23 @@ func (r *apiResolver) Resolve(ctx context.Context, nodeName string) (string, err
} }
if nodeAddress == "" { if nodeAddress == "" {
return "", fmt.Errorf("failed to determine the IP address of node \"%s\"", nc.NodeName) return ssh.ProxmoxNode{}, fmt.Errorf("failed to determine the IP address of node \"%s\"", nc.NodeName)
} }
nodeAddressParts := strings.Split(nodeAddress, "/") nodeAddressParts := strings.Split(nodeAddress, "/")
node := ssh.ProxmoxNode{Address: nodeAddressParts[0], Port: 22}
return nodeAddressParts[0], nil return node, nil
} }
type apiResolverWithOverrides struct { type apiResolverWithOverrides struct {
ar apiResolver ar apiResolver
overrides map[string]string overrides map[string]ssh.ProxmoxNode
} }
func (r *apiResolverWithOverrides) Resolve(ctx context.Context, nodeName string) (string, error) { func (r *apiResolverWithOverrides) Resolve(ctx context.Context, nodeName string) (ssh.ProxmoxNode, error) {
if ip, ok := r.overrides[nodeName]; ok { if node, ok := r.overrides[nodeName]; ok {
return ip, nil return node, nil
} }
return r.ar.Resolve(ctx, nodeName) return r.ar.Resolve(ctx, nodeName)

View File

@ -31,6 +31,7 @@ const (
mkProviderSSHNode = "node" mkProviderSSHNode = "node"
mkProviderSSHNodeName = "name" mkProviderSSHNodeName = "name"
mkProviderSSHNodeAddress = "address" mkProviderSSHNodeAddress = "address"
mkProviderSSHNodePort = "port"
) )
func createSchema() map[string]*schema.Schema { func createSchema() map[string]*schema.Schema {
@ -155,6 +156,13 @@ func createSchema() map[string]*schema.Schema {
Description: "The address of the Proxmox VE node.", Description: "The address of the Proxmox VE node.",
ValidateFunc: validation.IsIPAddress, ValidateFunc: validation.IsIPAddress,
}, },
mkProviderSSHNodePort: {
Type: schema.TypeInt,
Optional: true,
Description: "The port of the Proxmox VE node.",
Default: 22,
ValidateFunc: validation.IsPortNumber,
},
}, },
}, },
}, },