diff --git a/docs/index.md b/docs/index.md index 6b329bd5..ed06d841 100644 --- a/docs/index.md +++ b/docs/index.md @@ -208,3 +208,4 @@ Proxmox `provider` block: specified multiple times to provide configuration fo multiple nodes. - `name` - (Required) The name of the node. - `address` - (Required) The IP address of the node. + - `port` - (Optional) SSH port of the node. Defaults to 22. diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 168b4355..9ebabfb0 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -12,6 +12,7 @@ import ( "regexp" "strings" + "github.com/hashicorp/terraform-plugin-framework-validators/int64validator" "github.com/hashicorp/terraform-plugin-framework-validators/listvalidator" "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" "github.com/hashicorp/terraform-plugin-framework/datasource" @@ -68,6 +69,7 @@ type proxmoxProviderModel struct { Nodes []struct { Name types.String `tfsdk:"name"` Address types.String `tfsdk:"address"` + Port types.Int64 `tfsdk:"port"` } `tfsdk:"node"` } `tfsdk:"ssh"` } @@ -167,6 +169,11 @@ func (p *proxmoxProvider) Schema(_ context.Context, _ provider.SchemaRequest, re Description: "The address of the Proxmox VE node.", 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") sshAgent := utils.GetAnyBoolEnv("PROXMOX_VE_SSH_AGENT") 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 !config.SSH[0].Username.IsNull() { sshUsername = config.SSH[0].Username.ValueString() @@ -311,7 +319,15 @@ func (p *proxmoxProvider) Configure( } 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 } -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} networkDevices, err := nc.ListNetworkInterfaces(ctx) 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 := "" @@ -388,22 +404,23 @@ func (r *apiResolver) Resolve(ctx context.Context, nodeName string) (string, err } 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, "/") + node := ssh.ProxmoxNode{Address: nodeAddressParts[0], Port: 22} - return nodeAddressParts[0], nil + return node, nil } type apiResolverWithOverrides struct { ar apiResolver - overrides map[string]string + overrides map[string]ssh.ProxmoxNode } -func (r *apiResolverWithOverrides) Resolve(ctx context.Context, nodeName string) (string, error) { - if ip, ok := r.overrides[nodeName]; ok { - return ip, nil +func (r *apiResolverWithOverrides) Resolve(ctx context.Context, nodeName string) (ssh.ProxmoxNode, error) { + if node, ok := r.overrides[nodeName]; ok { + return node, nil } return r.ar.Resolve(ctx, nodeName) diff --git a/proxmox/ssh/client.go b/proxmox/ssh/client.go index 49917e43..00e70754 100644 --- a/proxmox/ssh/client.go +++ b/proxmox/ssh/client.go @@ -74,19 +74,20 @@ func NewClient( // ExecuteNodeCommands executes commands on a given node. func (c *client) ExecuteNodeCommands(ctx context.Context, nodeName string, commands []string) error { - ip, err := c.nodeLookup.Resolve(ctx, nodeName) + node, err := c.nodeLookup.Resolve(ctx, nodeName) if err != nil { return fmt.Errorf("failed to find node endpoint: %w", err) } tflog.Debug(ctx, "executing commands on the node using SSH", map[string]interface{}{ - "node_address": ip, + "node_address": node.Address, + "node_port": node.Port, "commands": commands, }) closeOrLogError := utils.CloseOrLogError(ctx) - sshClient, err := c.openNodeShell(ctx, ip) + sshClient, err := c.openNodeShell(ctx, node) if err != nil { return err } @@ -212,13 +213,13 @@ func (c *client) NodeUpload( } // 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() if err != nil { 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") if _, err = os.Stat(sshPath); os.IsNotExist(err) { diff --git a/proxmox/ssh/resolver.go b/proxmox/ssh/resolver.go index cbe476bc..166b77d4 100644 --- a/proxmox/ssh/resolver.go +++ b/proxmox/ssh/resolver.go @@ -10,7 +10,13 @@ import ( "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. type NodeResolver interface { - Resolve(ctx context.Context, nodeName string) (string, error) + Resolve(ctx context.Context, nodeName string) (ProxmoxNode, error) } diff --git a/proxmoxtf/provider/provider.go b/proxmoxtf/provider/provider.go index 1a7f8794..d3a460c7 100644 --- a/proxmoxtf/provider/provider.go +++ b/proxmoxtf/provider/provider.go @@ -129,12 +129,15 @@ func providerConfigure(_ context.Context, d *schema.ResourceData) (interface{}, sshConf[mkProviderSSHAgentSocket] = sshAgentSocket } - nodeOverrides := map[string]string{} + nodeOverrides := map[string]ssh.ProxmoxNode{} if ns, ok := sshConf[mkProviderSSHNode]; ok { for _, n := range ns.([]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 } -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} networkDevices, err := nc.ListNetworkInterfaces(ctx) 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 := "" @@ -179,22 +182,23 @@ func (r *apiResolver) Resolve(ctx context.Context, nodeName string) (string, err } 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, "/") + node := ssh.ProxmoxNode{Address: nodeAddressParts[0], Port: 22} - return nodeAddressParts[0], nil + return node, nil } type apiResolverWithOverrides struct { ar apiResolver - overrides map[string]string + overrides map[string]ssh.ProxmoxNode } -func (r *apiResolverWithOverrides) Resolve(ctx context.Context, nodeName string) (string, error) { - if ip, ok := r.overrides[nodeName]; ok { - return ip, nil +func (r *apiResolverWithOverrides) Resolve(ctx context.Context, nodeName string) (ssh.ProxmoxNode, error) { + if node, ok := r.overrides[nodeName]; ok { + return node, nil } return r.ar.Resolve(ctx, nodeName) diff --git a/proxmoxtf/provider/schema.go b/proxmoxtf/provider/schema.go index d28ce982..0d8da9bf 100644 --- a/proxmoxtf/provider/schema.go +++ b/proxmoxtf/provider/schema.go @@ -31,6 +31,7 @@ const ( mkProviderSSHNode = "node" mkProviderSSHNodeName = "name" mkProviderSSHNodeAddress = "address" + mkProviderSSHNodePort = "port" ) func createSchema() map[string]*schema.Schema { @@ -155,6 +156,13 @@ func createSchema() map[string]*schema.Schema { Description: "The address of the Proxmox VE node.", ValidateFunc: validation.IsIPAddress, }, + mkProviderSSHNodePort: { + Type: schema.TypeInt, + Optional: true, + Description: "The port of the Proxmox VE node.", + Default: 22, + ValidateFunc: validation.IsPortNumber, + }, }, }, },