mirror of
https://github.com/yusing/godoxy.git
synced 2026-04-21 07:51:28 +02:00
feat(proxmox): support node-level routes and journalctl access
This change enables Proxmox node-level operations without requiring a specific
LXC container VMID.
**Features added:**
- New `/proxmox/journalctl/{node}` API endpoint for streaming node journalctl
- Route configuration support for Proxmox nodes (VMID = 0)
- `ReverseLookupNode` function for node discovery by hostname/IP/alias
- `NodeJournalctl` method for executing journalctl on nodes
**Behavior changes:**
- VMID parameter in journalctl endpoints is now optional
- Routes targeting nodes (without specific containers) are now valid
**Bug fixes:**
- Fixed error message variable reference in route validation
This commit is contained in:
@@ -146,6 +146,7 @@ func NewHandler(requireAuth bool) *gin.Engine {
|
|||||||
|
|
||||||
proxmox := v1.Group("/proxmox")
|
proxmox := v1.Group("/proxmox")
|
||||||
{
|
{
|
||||||
|
proxmox.GET("/journalctl/:node", proxmoxApi.Journalctl)
|
||||||
proxmox.GET("/journalctl/:node/:vmid", proxmoxApi.Journalctl)
|
proxmox.GET("/journalctl/:node/:vmid", proxmoxApi.Journalctl)
|
||||||
proxmox.GET("/journalctl/:node/:vmid/:service", proxmoxApi.Journalctl)
|
proxmox.GET("/journalctl/:node/:vmid/:service", proxmoxApi.Journalctl)
|
||||||
proxmox.GET("/stats/:node/:vmid", proxmoxApi.Stats)
|
proxmox.GET("/stats/:node/:vmid", proxmoxApi.Stats)
|
||||||
|
|||||||
@@ -2077,9 +2077,9 @@
|
|||||||
"operationId": "uptime"
|
"operationId": "uptime"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/proxmox/journalctl/{node}/{vmid}": {
|
"/proxmox/journalctl/{node}": {
|
||||||
"get": {
|
"get": {
|
||||||
"description": "Get journalctl output",
|
"description": "Get journalctl output for node or LXC container. If vmid is not provided, streams node journalctl.",
|
||||||
"consumes": [
|
"consumes": [
|
||||||
"application/json"
|
"application/json"
|
||||||
],
|
],
|
||||||
@@ -2094,19 +2094,85 @@
|
|||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
"description": "Node name",
|
||||||
"name": "node",
|
"name": "node",
|
||||||
"in": "path",
|
"in": "path",
|
||||||
"required": true
|
"required": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"name": "vmid",
|
"description": "Limit output lines (1-1000)",
|
||||||
|
"name": "limit",
|
||||||
|
"in": "query"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Journalctl output",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Invalid request",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/ErrorResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"403": {
|
||||||
|
"description": "Unauthorized",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/ErrorResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "Node not found",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/ErrorResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal server error",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/ErrorResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"x-id": "journalctl",
|
||||||
|
"operationId": "journalctl"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/proxmox/journalctl/{node}/{vmid}": {
|
||||||
|
"get": {
|
||||||
|
"description": "Get journalctl output for node or LXC container. If vmid is not provided, streams node journalctl.",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"proxmox",
|
||||||
|
"websocket"
|
||||||
|
],
|
||||||
|
"summary": "Get journalctl output",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Node name",
|
||||||
|
"name": "node",
|
||||||
"in": "path",
|
"in": "path",
|
||||||
"required": true
|
"required": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"description": "limit",
|
"description": "Container VMID (optional - if not provided, streams node journalctl)",
|
||||||
|
"name": "vmid",
|
||||||
|
"in": "path"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "Limit output lines (1-1000)",
|
||||||
"name": "limit",
|
"name": "limit",
|
||||||
"in": "query"
|
"in": "query"
|
||||||
}
|
}
|
||||||
@@ -2149,7 +2215,7 @@
|
|||||||
},
|
},
|
||||||
"/proxmox/journalctl/{node}/{vmid}/{service}": {
|
"/proxmox/journalctl/{node}/{vmid}/{service}": {
|
||||||
"get": {
|
"get": {
|
||||||
"description": "Get journalctl output",
|
"description": "Get journalctl output for node or LXC container. If vmid is not provided, streams node journalctl.",
|
||||||
"consumes": [
|
"consumes": [
|
||||||
"application/json"
|
"application/json"
|
||||||
],
|
],
|
||||||
@@ -2164,24 +2230,26 @@
|
|||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
"description": "Node name",
|
||||||
"name": "node",
|
"name": "node",
|
||||||
"in": "path",
|
"in": "path",
|
||||||
"required": true
|
"required": true
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "Container VMID (optional - if not provided, streams node journalctl)",
|
||||||
|
"name": "vmid",
|
||||||
|
"in": "path"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
"description": "Service name (e.g., 'pveproxy' for node, 'container@.service' format for LXC)",
|
||||||
"name": "service",
|
"name": "service",
|
||||||
"in": "path"
|
"in": "path"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"name": "vmid",
|
"description": "Limit output lines (1-1000)",
|
||||||
"in": "path",
|
|
||||||
"required": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "integer",
|
|
||||||
"description": "limit",
|
|
||||||
"name": "limit",
|
"name": "limit",
|
||||||
"in": "query"
|
"in": "query"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3361,21 +3361,67 @@ paths:
|
|||||||
- metrics
|
- metrics
|
||||||
- websocket
|
- websocket
|
||||||
x-id: uptime
|
x-id: uptime
|
||||||
|
/proxmox/journalctl/{node}:
|
||||||
|
get:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: Get journalctl output for node or LXC container. If vmid is not
|
||||||
|
provided, streams node journalctl.
|
||||||
|
parameters:
|
||||||
|
- description: Node name
|
||||||
|
in: path
|
||||||
|
name: node
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
- description: Limit output lines (1-1000)
|
||||||
|
in: query
|
||||||
|
name: limit
|
||||||
|
type: integer
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: Journalctl output
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
"400":
|
||||||
|
description: Invalid request
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/ErrorResponse'
|
||||||
|
"403":
|
||||||
|
description: Unauthorized
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/ErrorResponse'
|
||||||
|
"404":
|
||||||
|
description: Node not found
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/ErrorResponse'
|
||||||
|
"500":
|
||||||
|
description: Internal server error
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/ErrorResponse'
|
||||||
|
summary: Get journalctl output
|
||||||
|
tags:
|
||||||
|
- proxmox
|
||||||
|
- websocket
|
||||||
|
x-id: journalctl
|
||||||
/proxmox/journalctl/{node}/{vmid}:
|
/proxmox/journalctl/{node}/{vmid}:
|
||||||
get:
|
get:
|
||||||
consumes:
|
consumes:
|
||||||
- application/json
|
- application/json
|
||||||
description: Get journalctl output
|
description: Get journalctl output for node or LXC container. If vmid is not
|
||||||
|
provided, streams node journalctl.
|
||||||
parameters:
|
parameters:
|
||||||
- in: path
|
- description: Node name
|
||||||
|
in: path
|
||||||
name: node
|
name: node
|
||||||
required: true
|
required: true
|
||||||
type: string
|
type: string
|
||||||
- in: path
|
- description: Container VMID (optional - if not provided, streams node journalctl)
|
||||||
|
in: path
|
||||||
name: vmid
|
name: vmid
|
||||||
required: true
|
|
||||||
type: integer
|
type: integer
|
||||||
- description: limit
|
- description: Limit output lines (1-1000)
|
||||||
in: query
|
in: query
|
||||||
name: limit
|
name: limit
|
||||||
type: integer
|
type: integer
|
||||||
@@ -3411,20 +3457,24 @@ paths:
|
|||||||
get:
|
get:
|
||||||
consumes:
|
consumes:
|
||||||
- application/json
|
- application/json
|
||||||
description: Get journalctl output
|
description: Get journalctl output for node or LXC container. If vmid is not
|
||||||
|
provided, streams node journalctl.
|
||||||
parameters:
|
parameters:
|
||||||
- in: path
|
- description: Node name
|
||||||
|
in: path
|
||||||
name: node
|
name: node
|
||||||
required: true
|
required: true
|
||||||
type: string
|
type: string
|
||||||
- in: path
|
- description: Container VMID (optional - if not provided, streams node journalctl)
|
||||||
|
in: path
|
||||||
|
name: vmid
|
||||||
|
type: integer
|
||||||
|
- description: Service name (e.g., 'pveproxy' for node, 'container@.service'
|
||||||
|
format for LXC)
|
||||||
|
in: path
|
||||||
name: service
|
name: service
|
||||||
type: string
|
type: string
|
||||||
- in: path
|
- description: Limit output lines (1-1000)
|
||||||
name: vmid
|
|
||||||
required: true
|
|
||||||
type: integer
|
|
||||||
- description: limit
|
|
||||||
in: query
|
in: query
|
||||||
name: limit
|
name: limit
|
||||||
type: integer
|
type: integer
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import (
|
|||||||
|
|
||||||
type JournalctlRequest struct {
|
type JournalctlRequest struct {
|
||||||
Node string `uri:"node" binding:"required"`
|
Node string `uri:"node" binding:"required"`
|
||||||
VMID int `uri:"vmid" binding:"required"`
|
VMID *int `uri:"vmid"` // optional - if not provided, streams node journalctl
|
||||||
Service string `uri:"service"`
|
Service string `uri:"service"`
|
||||||
Limit int `query:"limit" binding:"omitempty,min=1,max=1000"`
|
Limit int `query:"limit" binding:"omitempty,min=1,max=1000"`
|
||||||
}
|
}
|
||||||
@@ -20,17 +20,20 @@ type JournalctlRequest struct {
|
|||||||
// @x-id "journalctl"
|
// @x-id "journalctl"
|
||||||
// @BasePath /api/v1
|
// @BasePath /api/v1
|
||||||
// @Summary Get journalctl output
|
// @Summary Get journalctl output
|
||||||
// @Description Get journalctl output
|
// @Description Get journalctl output for node or LXC container. If vmid is not provided, streams node journalctl.
|
||||||
// @Tags proxmox,websocket
|
// @Tags proxmox,websocket
|
||||||
// @Accept json
|
// @Accept json
|
||||||
// @Produce application/json
|
// @Produce application/json
|
||||||
// @Param path path JournalctlRequest true "Request"
|
// @Param node path string true "Node name"
|
||||||
// @Param limit query int false "limit"
|
// @Param vmid path int false "Container VMID (optional - if not provided, streams node journalctl)"
|
||||||
// @Success 200 string plain "Journalctl output"
|
// @Param service path string false "Service name (e.g., 'pveproxy' for node, 'container@.service' format for LXC)"
|
||||||
|
// @Param limit query int false "Limit output lines (1-1000)"
|
||||||
|
// @Success 200 string plain "Journalctl output"
|
||||||
// @Failure 400 {object} apitypes.ErrorResponse "Invalid request"
|
// @Failure 400 {object} apitypes.ErrorResponse "Invalid request"
|
||||||
// @Failure 403 {object} apitypes.ErrorResponse "Unauthorized"
|
// @Failure 403 {object} apitypes.ErrorResponse "Unauthorized"
|
||||||
// @Failure 404 {object} apitypes.ErrorResponse "Node not found"
|
// @Failure 404 {object} apitypes.ErrorResponse "Node not found"
|
||||||
// @Failure 500 {object} apitypes.ErrorResponse "Internal server error"
|
// @Failure 500 {object} apitypes.ErrorResponse "Internal server error"
|
||||||
|
// @Router /proxmox/journalctl/{node} [get]
|
||||||
// @Router /proxmox/journalctl/{node}/{vmid} [get]
|
// @Router /proxmox/journalctl/{node}/{vmid} [get]
|
||||||
// @Router /proxmox/journalctl/{node}/{vmid}/{service} [get]
|
// @Router /proxmox/journalctl/{node}/{vmid}/{service} [get]
|
||||||
func Journalctl(c *gin.Context) {
|
func Journalctl(c *gin.Context) {
|
||||||
@@ -53,7 +56,12 @@ func Journalctl(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
defer manager.Close()
|
defer manager.Close()
|
||||||
|
|
||||||
reader, err := node.LXCJournalctl(c.Request.Context(), request.VMID, request.Service, request.Limit)
|
var reader io.ReadCloser
|
||||||
|
if request.VMID == nil {
|
||||||
|
reader, err = node.NodeJournalctl(c.Request.Context(), request.Service, request.Limit)
|
||||||
|
} else {
|
||||||
|
reader, err = node.LXCJournalctl(c.Request.Context(), *request.VMID, request.Service, request.Limit)
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Error(apitypes.InternalServerError(err, "failed to get journalctl output"))
|
c.Error(apitypes.InternalServerError(err, "failed to get journalctl output"))
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -147,6 +147,34 @@ func (c *Client) ReverseLookupResource(ip net.IP, hostname string, alias string)
|
|||||||
return nil, ErrResourceNotFound
|
return nil, ErrResourceNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ReverseLookupNode looks up a node by name or IP address.
|
||||||
|
// Returns the node name if found.
|
||||||
|
func (c *Client) ReverseLookupNode(hostname string, ip net.IP, alias string) string {
|
||||||
|
shouldCheckHostname := hostname != ""
|
||||||
|
shouldCheckIP := ip != nil && !ip.IsLoopback() && !ip.IsUnspecified()
|
||||||
|
shouldCheckAlias := alias != ""
|
||||||
|
|
||||||
|
if shouldCheckHostname {
|
||||||
|
hostname, _, _ = strings.Cut(hostname, ".")
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, node := range c.Cluster.Nodes {
|
||||||
|
if shouldCheckHostname && node.Name == hostname {
|
||||||
|
return node.Name
|
||||||
|
}
|
||||||
|
if shouldCheckIP {
|
||||||
|
nodeIP := net.ParseIP(node.IP)
|
||||||
|
if nodeIP != nil && nodeIP.Equal(ip) {
|
||||||
|
return node.Name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if shouldCheckAlias && node.Name == alias {
|
||||||
|
return node.Name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
// Key implements pool.Object
|
// Key implements pool.Object
|
||||||
func (c *Client) Key() string {
|
func (c *Client) Key() string {
|
||||||
return c.Cluster.ID
|
return c.Cluster.ID
|
||||||
|
|||||||
@@ -186,3 +186,14 @@ func (n *Node) NodeCommand(ctx context.Context, command string) (io.ReadCloser,
|
|||||||
|
|
||||||
return pr, nil
|
return pr, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (n *Node) NodeJournalctl(ctx context.Context, service string, limit int) (io.ReadCloser, error) {
|
||||||
|
command := "journalctl -f"
|
||||||
|
if service != "" {
|
||||||
|
command = fmt.Sprintf("journalctl -u %q -f", service)
|
||||||
|
}
|
||||||
|
if limit > 0 {
|
||||||
|
command = fmt.Sprintf("%s -n %d", command, limit)
|
||||||
|
}
|
||||||
|
return n.NodeCommand(ctx, command)
|
||||||
|
}
|
||||||
|
|||||||
@@ -196,68 +196,68 @@ func (r *Route) validate() gperr.Error {
|
|||||||
if nodeName == "" {
|
if nodeName == "" {
|
||||||
return gperr.Errorf("node (proxmox node name) is required")
|
return gperr.Errorf("node (proxmox node name) is required")
|
||||||
}
|
}
|
||||||
if vmid <= 0 {
|
|
||||||
return gperr.Errorf("vmid (lxc id) is required")
|
|
||||||
}
|
|
||||||
|
|
||||||
node, ok := proxmox.Nodes.Get(nodeName)
|
node, ok := proxmox.Nodes.Get(nodeName)
|
||||||
if !ok {
|
if !ok {
|
||||||
return gperr.Errorf("proxmox node %s not found in pool", node)
|
return gperr.Errorf("proxmox node %s not found in pool", nodeName)
|
||||||
}
|
}
|
||||||
|
|
||||||
res, err := node.Client().GetResource("lxc", vmid)
|
// Node-level route (VMID = 0) - no container control needed
|
||||||
if err != nil {
|
if vmid > 0 {
|
||||||
return gperr.Wrap(err) // ErrResourceNotFound
|
res, err := node.Client().GetResource("lxc", vmid)
|
||||||
}
|
|
||||||
|
|
||||||
r.Proxmox.VMName = res.Name
|
|
||||||
|
|
||||||
if r.Host == DefaultHost {
|
|
||||||
containerName := r.Idlewatcher.ContainerName()
|
|
||||||
// get ip addresses of the vmid
|
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
ips := res.IPs
|
|
||||||
if len(ips) == 0 {
|
|
||||||
return gperr.Multiline().
|
|
||||||
Addf("no ip addresses found for %s", containerName).
|
|
||||||
Adds("make sure you have set static ip address for container instead of dhcp").
|
|
||||||
Subject(containerName)
|
|
||||||
}
|
|
||||||
|
|
||||||
l := log.With().Str("container", containerName).Logger()
|
|
||||||
|
|
||||||
l.Info().Msg("checking if container is running")
|
|
||||||
running, err := node.LXCIsRunning(ctx, vmid)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return gperr.New("failed to check container state").With(err)
|
return gperr.Wrap(err) // ErrResourceNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
if !running {
|
r.Proxmox.VMName = res.Name
|
||||||
l.Info().Msg("starting container")
|
|
||||||
if err := node.LXCAction(ctx, vmid, proxmox.LXCStart); err != nil {
|
|
||||||
return gperr.New("failed to start container").With(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
l.Info().Msgf("finding reachable ip addresses")
|
|
||||||
errs := gperr.NewBuilder("failed to find reachable ip addresses")
|
|
||||||
for _, ip := range ips {
|
|
||||||
if err := netutils.PingTCP(ctx, ip, r.Port.Proxy); err != nil {
|
|
||||||
errs.Add(gperr.Unwrap(err).Subjectf("%s:%d", ip, r.Port.Proxy))
|
|
||||||
} else {
|
|
||||||
r.Host = ip.String()
|
|
||||||
l.Info().Msgf("using ip %s", r.Host)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if r.Host == DefaultHost {
|
if r.Host == DefaultHost {
|
||||||
return gperr.Multiline().
|
containerName := r.Idlewatcher.ContainerName()
|
||||||
Addf("no reachable ip addresses found, tried %d IPs", len(ips)).
|
// get ip addresses of the vmid
|
||||||
With(errs.Error()).
|
|
||||||
Subject(containerName)
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
ips := res.IPs
|
||||||
|
if len(ips) == 0 {
|
||||||
|
return gperr.Multiline().
|
||||||
|
Addf("no ip addresses found for %s", containerName).
|
||||||
|
Adds("make sure you have set static ip address for container instead of dhcp").
|
||||||
|
Subject(containerName)
|
||||||
|
}
|
||||||
|
|
||||||
|
l := log.With().Str("container", containerName).Logger()
|
||||||
|
|
||||||
|
l.Info().Msg("checking if container is running")
|
||||||
|
running, err := node.LXCIsRunning(ctx, vmid)
|
||||||
|
if err != nil {
|
||||||
|
return gperr.New("failed to check container state").With(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !running {
|
||||||
|
l.Info().Msg("starting container")
|
||||||
|
if err := node.LXCAction(ctx, vmid, proxmox.LXCStart); err != nil {
|
||||||
|
return gperr.New("failed to start container").With(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
l.Info().Msgf("finding reachable ip addresses")
|
||||||
|
errs := gperr.NewBuilder("failed to find reachable ip addresses")
|
||||||
|
for _, ip := range ips {
|
||||||
|
if err := netutils.PingTCP(ctx, ip, r.Port.Proxy); err != nil {
|
||||||
|
errs.Add(gperr.Unwrap(err).Subjectf("%s:%d", ip, r.Port.Proxy))
|
||||||
|
} else {
|
||||||
|
r.Host = ip.String()
|
||||||
|
l.Info().Msgf("using ip %s", r.Host)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if r.Host == DefaultHost {
|
||||||
|
return gperr.Multiline().
|
||||||
|
Addf("no reachable ip addresses found, tried %d IPs", len(ips)).
|
||||||
|
With(errs.Error()).
|
||||||
|
Subject(containerName)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -337,8 +337,21 @@ func (r *Route) validate() gperr.Error {
|
|||||||
hostname := r.ProxyURL.Hostname()
|
hostname := r.ProxyURL.Hostname()
|
||||||
ip := net.ParseIP(hostname)
|
ip := net.ParseIP(hostname)
|
||||||
for _, p := range config.WorkingState.Load().Value().Providers.Proxmox {
|
for _, p := range config.WorkingState.Load().Value().Providers.Proxmox {
|
||||||
|
// First check if hostname, IP, or alias matches a node (node-level route)
|
||||||
|
if nodeName := p.Client().ReverseLookupNode(hostname, ip, r.Alias); nodeName != "" {
|
||||||
|
r.Proxmox = &proxmox.NodeConfig{
|
||||||
|
Node: nodeName,
|
||||||
|
VMID: 0, // node-level route, no specific VM
|
||||||
|
VMName: "",
|
||||||
|
}
|
||||||
|
log.Info().
|
||||||
|
Str("node", nodeName).
|
||||||
|
Msgf("found proxmox node for route %q", r.Alias)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// Then check if hostname, IP, or alias matches a VM resource
|
||||||
resource, _ := p.Client().ReverseLookupResource(ip, hostname, r.Alias)
|
resource, _ := p.Client().ReverseLookupResource(ip, hostname, r.Alias)
|
||||||
// reverse lookup resource by ip address, hostname or alias
|
|
||||||
if resource != nil {
|
if resource != nil {
|
||||||
r.Proxmox = &proxmox.NodeConfig{
|
r.Proxmox = &proxmox.NodeConfig{
|
||||||
Node: resource.Node,
|
Node: resource.Node,
|
||||||
|
|||||||
Reference in New Issue
Block a user