package api import ( "bytes" "context" "encoding/json" "io" "net/http" "net/url" "strings" "sync" "time" "github.com/rcourtman/pulse-go-rewrite/internal/config" "github.com/rcourtman/pulse-go-rewrite/internal/mock" "github.com/rcourtman/pulse-go-rewrite/internal/monitoring" "github.com/rcourtman/pulse-go-rewrite/internal/unifiedresources" "github.com/rcourtman/pulse-go-rewrite/internal/vmware" ) const vmwareConnectionsPathPrefix = "/api/vmware/connections/" // VMwareHandlers manages VMware vCenter connection CRUD and connectivity checks. type VMwareHandlers struct { getPersistence func(ctx context.Context) *config.ConfigPersistence getMonitor func(ctx context.Context) *monitoring.Monitor getPoller func(ctx context.Context) *monitoring.VMwarePoller newClient func(vmware.ClientConfig) (vmwareClient, error) previewRecords func(ctx context.Context, instance config.VMwareVCenterInstance) ([]unifiedresources.IngestRecord, error) statusMu sync.RWMutex statuses map[string]vmwareConnectionRuntimeStatus } type vmwareClient interface { TestConnection(ctx context.Context) (*vmware.InventorySummary, error) Close() } type vmwareConnectionResponse struct { config.VMwareVCenterInstance Poll *monitoring.VMwareConnectionPollStatus `json:"poll,omitempty"` Observed *monitoring.VMwareConnectionObservedSummary `json:"observed,omitempty"` } type vmwareConnectionRuntimeStatus struct { Poll *monitoring.VMwareConnectionPollStatus Observed *monitoring.VMwareConnectionObservedSummary } // HandleAdd stores a new VMware vCenter connection. func (h *VMwareHandlers) HandleAdd(w http.ResponseWriter, r *http.Request) { if !h.featureEnabled(w) { return } if mock.IsMockEnabled() { writeErrorResponse(w, http.StatusForbidden, "mock_mode_enabled", "Cannot modify connections in mock mode", nil) return } r.Body = http.MaxBytesReader(w, r.Body, 32*1024) defer r.Body.Close() instance, ok := decodeVMwareInstanceRequest(w, r, config.NewVMwareVCenterInstance()) if !ok { return } instance.ID = strings.TrimSpace(instance.ID) if instance.ID == "" { instance.ID = config.NewVMwareVCenterInstance().ID } normalizeVMwareInstance(&instance) if err := instance.Validate(); err != nil { writeErrorResponse(w, http.StatusBadRequest, "validation_error", err.Error(), nil) return } persistence := h.persistenceForRequest(w, r.Context()) if persistence == nil { return } instances, err := persistence.LoadVMwareConfig() if err != nil { writeErrorResponse(w, http.StatusInternalServerError, "vmware_load_failed", "Failed to load VMware configuration", map[string]string{"error": err.Error()}) return } instances = append(instances, instance) if err := persistence.SaveVMwareConfig(instances); err != nil { writeErrorResponse(w, http.StatusInternalServerError, "vmware_save_failed", "Failed to save VMware configuration", map[string]string{"error": err.Error()}) return } writeJSON(w, http.StatusCreated, instance.Redacted()) } // HandleList returns all configured VMware connections with sensitive fields redacted. func (h *VMwareHandlers) HandleList(w http.ResponseWriter, r *http.Request) { if !h.featureEnabled(w) { return } if mock.IsMockEnabled() { writeJSON(w, http.StatusOK, mockVMwareConnectionResponses()) return } persistence := h.persistenceForRequest(w, r.Context()) if persistence == nil { return } instances, err := persistence.LoadVMwareConfig() if err != nil { writeErrorResponse(w, http.StatusInternalServerError, "vmware_load_failed", "Failed to load VMware configuration", map[string]string{"error": err.Error()}) return } orgID := resolveTenantOrgID(r) var summaries map[string]monitoring.VMwareConnectionSummary if h != nil && h.getPoller != nil { if poller := h.getPoller(r.Context()); poller != nil { summaries = poller.ConnectionSummaries(orgID, instances) } } responses := make([]vmwareConnectionResponse, 0, len(instances)) for i := range instances { item := instances[i] item.ApplyDefaults() response := vmwareConnectionResponse{ VMwareVCenterInstance: item.Redacted(), } connID := strings.TrimSpace(item.ID) if summary, ok := summaries[connID]; ok { response.Poll = summary.Poll response.Observed = summary.Observed } else { status := h.runtimeStatus(connID) response.Poll = status.Poll response.Observed = status.Observed } responses = append(responses, response) } writeJSON(w, http.StatusOK, responses) } // HandleDelete removes a configured VMware vCenter connection by ID. func (h *VMwareHandlers) HandleDelete(w http.ResponseWriter, r *http.Request) { if !h.featureEnabled(w) { return } if mock.IsMockEnabled() { writeErrorResponse(w, http.StatusForbidden, "mock_mode_enabled", "Cannot modify connections in mock mode", nil) return } connectionID, ok := vmwareConnectionIDFromPath(r.URL.Path) if !ok { writeErrorResponse(w, http.StatusBadRequest, "missing_connection_id", "Connection ID is required", nil) return } persistence := h.persistenceForRequest(w, r.Context()) if persistence == nil { return } instances, err := persistence.LoadVMwareConfig() if err != nil { writeErrorResponse(w, http.StatusInternalServerError, "vmware_load_failed", "Failed to load VMware configuration", map[string]string{"error": err.Error()}) return } index := -1 for i := range instances { if strings.TrimSpace(instances[i].ID) == connectionID { index = i break } } if index < 0 { writeErrorResponse(w, http.StatusNotFound, "vmware_not_found", "Connection not found", nil) return } instances = append(instances[:index], instances[index+1:]...) if err := persistence.SaveVMwareConfig(instances); err != nil { writeErrorResponse(w, http.StatusInternalServerError, "vmware_save_failed", "Failed to save VMware configuration", map[string]string{"error": err.Error()}) return } h.clearRuntimeStatus(connectionID) writeJSON(w, http.StatusOK, map[string]any{ "success": true, "id": connectionID, }) } // HandleUpdate replaces a configured VMware vCenter connection by ID while // preserving unchanged masked secrets from the stored record. func (h *VMwareHandlers) HandleUpdate(w http.ResponseWriter, r *http.Request) { if !h.featureEnabled(w) { return } if mock.IsMockEnabled() { writeErrorResponse(w, http.StatusForbidden, "mock_mode_enabled", "Cannot modify connections in mock mode", nil) return } connectionID, ok := vmwareConnectionIDFromPath(r.URL.Path) if !ok { writeErrorResponse(w, http.StatusBadRequest, "missing_connection_id", "Connection ID is required", nil) return } persistence := h.persistenceForRequest(w, r.Context()) if persistence == nil { return } instances, err := persistence.LoadVMwareConfig() if err != nil { writeErrorResponse(w, http.StatusInternalServerError, "vmware_load_failed", "Failed to load VMware configuration", map[string]string{"error": err.Error()}) return } index := -1 for i := range instances { if strings.TrimSpace(instances[i].ID) == connectionID { index = i break } } if index < 0 { writeErrorResponse(w, http.StatusNotFound, "vmware_not_found", "Connection not found", nil) return } instance, ok := decodeVMwareInstanceRequest(w, r, instances[index]) if !ok { return } instance.ID = connectionID normalizeVMwareInstance(&instance) instance.PreserveMaskedSecrets(instances[index]) if err := instance.Validate(); err != nil { writeErrorResponse(w, http.StatusBadRequest, "validation_error", err.Error(), nil) return } instances[index] = instance if err := persistence.SaveVMwareConfig(instances); err != nil { writeErrorResponse(w, http.StatusInternalServerError, "vmware_save_failed", "Failed to save VMware configuration", map[string]string{"error": err.Error()}) return } writeJSON(w, http.StatusOK, instance.Redacted()) } // HandlePreviewConnection projects the monitored-system impact of a proposed // VMware vCenter connection without persisting it. func (h *VMwareHandlers) HandlePreviewConnection(w http.ResponseWriter, r *http.Request) { if !h.featureEnabled(w) { return } instance, ok := decodeVMwareInstanceRequest(w, r, config.NewVMwareVCenterInstance()) if !ok { return } normalizeVMwareInstance(&instance) if err := instance.Validate(); err != nil { writeErrorResponse(w, http.StatusBadRequest, "validation_error", err.Error(), nil) return } monitor := h.monitorForRequest(r.Context()) usage := monitoredSystemUsage(monitor) if !usage.available { writeMonitoredSystemUsageUnavailable(w, usage.unavailableReason) return } candidate := vmwareMonitoredSystemCandidate(instance) if !candidate.CountsTowardMonitoredSystems() { preview := unifiedresources.PreviewMonitoredSystemCandidate(usage.readState, candidate) writeJSON(w, http.StatusOK, monitoredSystemLedgerPreviewResponse(false, preview).NormalizeCollections()) return } records, invalidConfig, err := h.previewMonitoredSystemRecords(r.Context(), instance) if err != nil { h.writeConnectionFailure(w, invalidConfig, err) return } preview := unifiedresources.PreviewMonitoredSystemRecords(usage.readState, map[unifiedresources.DataSource][]unifiedresources.IngestRecord{ unifiedresources.SourceVMware: records, }) if len(preview.ProjectedSystems) == 0 { writeErrorResponse(w, http.StatusBadRequest, "validation_error", "VMware connection did not resolve to a canonical monitored system preview", nil) return } writeJSON(w, http.StatusOK, monitoredSystemLedgerPreviewResponse(false, preview).NormalizeCollections()) } // HandlePreviewSavedConnection projects the monitored-system impact of editing // one saved VMware connection using the same secret-preservation path as // update and saved test flows. func (h *VMwareHandlers) HandlePreviewSavedConnection(w http.ResponseWriter, r *http.Request) { if !h.featureEnabled(w) { return } connectionID, ok := vmwareConnectionIDFromPreviewPath(r.URL.Path) if !ok { writeErrorResponse(w, http.StatusBadRequest, "missing_connection_id", "Connection ID is required", nil) return } persistence := h.persistenceForRequest(w, r.Context()) if persistence == nil { return } instances, err := persistence.LoadVMwareConfig() if err != nil { writeErrorResponse(w, http.StatusInternalServerError, "vmware_load_failed", "Failed to load VMware configuration", map[string]string{"error": err.Error()}) return } for i := range instances { current := instances[i] if strings.TrimSpace(current.ID) != connectionID { continue } payload, hasPayload, ok := decodeOptionalVMwareInstanceRequest(w, r, current) if !ok { return } next := current if hasPayload { payload.ID = connectionID normalizeVMwareInstance(&payload) payload.PreserveMaskedSecrets(current) if err := payload.Validate(); err != nil { writeErrorResponse(w, http.StatusBadRequest, "validation_error", err.Error(), nil) return } next = payload } monitor := h.monitorForRequest(r.Context()) usage := monitoredSystemUsage(monitor) if !usage.available { writeMonitoredSystemUsageUnavailable(w, usage.unavailableReason) return } candidate := vmwareMonitoredSystemCandidate(next) replacement := unifiedresources.MonitoredSystemReplacement{ Source: unifiedresources.SourceVMware, Selector: unifiedresources.MonitoredSystemReplacementSelector{ ResourceID: connectionID, }, } if !candidate.CountsTowardMonitoredSystems() { preview := unifiedresources.PreviewMonitoredSystemCandidateReplacement( usage.readState, replacement, candidate, ) writeJSON(w, http.StatusOK, monitoredSystemLedgerPreviewResponse(true, preview).NormalizeCollections()) return } records, invalidConfig, err := h.previewMonitoredSystemRecords(r.Context(), next) if err != nil { h.writeConnectionFailure(w, invalidConfig, err) return } preview := unifiedresources.PreviewMonitoredSystemRecordsReplacement( usage.readState, replacement, map[unifiedresources.DataSource][]unifiedresources.IngestRecord{ unifiedresources.SourceVMware: records, }, ) if len(preview.ProjectedSystems) == 0 { writeErrorResponse(w, http.StatusBadRequest, "validation_error", "VMware connection did not resolve to a canonical monitored system preview", nil) return } writeJSON(w, http.StatusOK, monitoredSystemLedgerPreviewResponse(true, preview).NormalizeCollections()) return } writeErrorResponse(w, http.StatusNotFound, "vmware_not_found", "Connection not found", nil) } // HandleTestConnection validates connectivity for a proposed VMware vCenter connection. func (h *VMwareHandlers) HandleTestConnection(w http.ResponseWriter, r *http.Request) { if !h.featureEnabled(w) { return } instance, ok := decodeVMwareInstanceRequest(w, r, config.NewVMwareVCenterInstance()) if !ok { return } normalizeVMwareInstance(&instance) if err := instance.Validate(); err != nil { writeErrorResponse(w, http.StatusBadRequest, "validation_error", err.Error(), nil) return } h.writeConnectionTestResult(w, r, instance) } // HandleTestSavedConnection validates connectivity for one saved VMware // connection using the server-owned stored secret material instead of relying // on frontend redaction placeholders. func (h *VMwareHandlers) HandleTestSavedConnection(w http.ResponseWriter, r *http.Request) { if !h.featureEnabled(w) { return } connectionID, ok := vmwareConnectionIDFromTestPath(r.URL.Path) if !ok { writeErrorResponse(w, http.StatusBadRequest, "missing_connection_id", "Connection ID is required", nil) return } persistence := h.persistenceForRequest(w, r.Context()) if persistence == nil { return } instances, err := persistence.LoadVMwareConfig() if err != nil { writeErrorResponse(w, http.StatusInternalServerError, "vmware_load_failed", "Failed to load VMware configuration", map[string]string{"error": err.Error()}) return } for i := range instances { instance := instances[i] if strings.TrimSpace(instance.ID) != connectionID { continue } normalizeVMwareInstance(&instance) payload, hasPayload, ok := decodeOptionalVMwareInstanceRequest(w, r, instance) if !ok { return } if hasPayload { payload.ID = connectionID normalizeVMwareInstance(&payload) payload.PreserveMaskedSecrets(instance) if err := payload.Validate(); err != nil { writeErrorResponse(w, http.StatusBadRequest, "validation_error", err.Error(), nil) return } instance = payload } summary, invalidConfig, err := h.testConnectionInstance(r, instance) if err != nil { if !hasPayload { at := time.Now().UTC() if poller := h.pollerForRequest(r.Context()); poller != nil { poller.RecordConnectionTestFailure(resolveTenantOrgID(r), connectionID, err, at) } else { h.recordTestFailure(connectionID, err, at) } } h.writeConnectionFailure(w, invalidConfig, err) return } if !hasPayload { at := time.Now().UTC() if poller := h.pollerForRequest(r.Context()); poller != nil { poller.RecordConnectionTestSuccess(resolveTenantOrgID(r), connectionID, summary, at) } else { h.recordTestSuccess(connectionID, summary, at) } } writeJSON(w, http.StatusOK, map[string]any{"success": true}) return } writeErrorResponse(w, http.StatusNotFound, "vmware_not_found", "Connection not found", nil) } func (h *VMwareHandlers) writeConnectionTestResult( w http.ResponseWriter, r *http.Request, instance config.VMwareVCenterInstance, ) { _, invalidConfig, err := h.testConnectionInstance(r, instance) if err != nil { h.writeConnectionFailure(w, invalidConfig, err) return } writeJSON(w, http.StatusOK, map[string]any{"success": true}) } func (h *VMwareHandlers) writeConnectionFailure(w http.ResponseWriter, invalidConfig bool, err error) { if invalidConfig { writeErrorResponse(w, http.StatusBadRequest, "vmware_invalid_config", "Invalid VMware vCenter connection configuration", connectionFailureDetails(err)) return } writeErrorResponse(w, http.StatusBadRequest, "vmware_connection_failed", "Failed to connect to VMware vCenter", connectionFailureDetails(err)) } func connectionFailureDetails(err error) map[string]string { if err == nil { return nil } details := map[string]string{"error": err.Error()} if category := connectionErrorCategory(err); category != "" { details["category"] = category } return details } func connectionErrorCategory(err error) string { if err == nil { return "" } if connectionErr, ok := err.(*vmware.ConnectionError); ok { return strings.TrimSpace(connectionErr.Category) } return "" } func (h *VMwareHandlers) testConnectionInstance( r *http.Request, instance config.VMwareVCenterInstance, ) (*vmware.InventorySummary, bool, error) { newClient := h.newClient if newClient == nil { newClient = func(cfg vmware.ClientConfig) (vmwareClient, error) { return vmware.NewClient(cfg) } } client, err := newClient(vmware.ClientConfig{ Host: instance.Host, Port: instance.Port, Username: instance.Username, Password: instance.Password, InsecureSkipVerify: instance.InsecureSkipVerify, Timeout: 10 * time.Second, }) if err != nil { return nil, true, err } defer client.Close() ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second) defer cancel() summary, err := client.TestConnection(ctx) if err != nil { return nil, false, err } return summary, false, nil } func normalizeVMwareInstance(instance *config.VMwareVCenterInstance) { if instance == nil { return } instance.Name = strings.TrimSpace(instance.Name) instance.Host = strings.TrimSpace(instance.Host) instance.Username = strings.TrimSpace(instance.Username) instance.Password = strings.TrimSpace(instance.Password) instance.ApplyDefaults() } func decodeVMwareInstanceRequest( w http.ResponseWriter, r *http.Request, base config.VMwareVCenterInstance, ) (config.VMwareVCenterInstance, bool) { r.Body = http.MaxBytesReader(w, r.Body, 32*1024) defer r.Body.Close() instance := base if err := json.NewDecoder(r.Body).Decode(&instance); err != nil { writeErrorResponse(w, http.StatusBadRequest, "invalid_request", "Invalid request body", map[string]string{"error": err.Error()}) return config.VMwareVCenterInstance{}, false } return instance, true } func decodeOptionalVMwareInstanceRequest( w http.ResponseWriter, r *http.Request, base config.VMwareVCenterInstance, ) (config.VMwareVCenterInstance, bool, bool) { r.Body = http.MaxBytesReader(w, r.Body, 32*1024) defer r.Body.Close() body, err := io.ReadAll(r.Body) if err != nil { writeErrorResponse(w, http.StatusBadRequest, "invalid_request", "Invalid request body", map[string]string{"error": err.Error()}) return config.VMwareVCenterInstance{}, false, false } if len(bytes.TrimSpace(body)) == 0 { return base, false, true } instance := base if err := json.Unmarshal(body, &instance); err != nil { writeErrorResponse(w, http.StatusBadRequest, "invalid_request", "Invalid request body", map[string]string{"error": err.Error()}) return config.VMwareVCenterInstance{}, false, false } return instance, true, true } func (h *VMwareHandlers) featureEnabled(w http.ResponseWriter) bool { if vmware.IsFeatureEnabled() { return true } writeErrorResponse(w, http.StatusNotFound, "vmware_disabled", "VMware integration has been explicitly disabled", nil) return false } func (h *VMwareHandlers) persistenceForRequest(w http.ResponseWriter, ctx context.Context) *config.ConfigPersistence { if h == nil || h.getPersistence == nil { writeErrorResponse(w, http.StatusInternalServerError, "vmware_unavailable", "VMware service unavailable", nil) return nil } persistence := h.getPersistence(ctx) if persistence == nil { writeErrorResponse(w, http.StatusInternalServerError, "vmware_unavailable", "VMware service unavailable", nil) return nil } return persistence } func (h *VMwareHandlers) pollerForRequest(ctx context.Context) *monitoring.VMwarePoller { if h == nil || h.getPoller == nil { return nil } return h.getPoller(ctx) } func (h *VMwareHandlers) monitorForRequest(ctx context.Context) *monitoring.Monitor { if h == nil || h.getMonitor == nil { return nil } return h.getMonitor(ctx) } func (h *VMwareHandlers) previewMonitoredSystemRecords( ctx context.Context, instance config.VMwareVCenterInstance, ) ([]unifiedresources.IngestRecord, bool, error) { if h != nil && h.previewRecords != nil { records, err := h.previewRecords(ctx, instance) return records, false, err } client, err := vmware.NewClient(vmware.ClientConfig{ Host: instance.Host, Port: instance.Port, Username: instance.Username, Password: instance.Password, InsecureSkipVerify: instance.InsecureSkipVerify, Timeout: 20 * time.Second, }) if err != nil { return nil, true, err } provider := vmware.NewAPIProvider(vmware.ProviderMetadata{ ConnectionID: strings.TrimSpace(instance.ID), ConnectionName: strings.TrimSpace(instance.Name), VCenterHost: strings.TrimSpace(instance.Host), }, client) defer provider.Close() refreshCtx, cancel := context.WithTimeout(ctx, 20*time.Second) defer cancel() if err := provider.Refresh(refreshCtx); err != nil { return nil, false, err } return provider.Records(), false, nil } func vmwareMonitoredSystemCandidate( instance config.VMwareVCenterInstance, ) unifiedresources.MonitoredSystemCandidate { return unifiedresources.MonitoredSystemCandidate{ Source: unifiedresources.SourceVMware, Type: unifiedresources.ResourceTypeAgent, Name: strings.TrimSpace(instance.Name), Hostname: pulseTokenHostCandidate(instance.Host), HostURL: strings.TrimSpace(instance.Host), ResourceID: strings.TrimSpace(instance.ID), State: monitoredSystemCandidateStateFromEnabled(instance.Enabled), } } func (h *VMwareHandlers) runtimeStatus(connectionID string) vmwareConnectionRuntimeStatus { if h == nil { return vmwareConnectionRuntimeStatus{} } h.statusMu.RLock() defer h.statusMu.RUnlock() if h.statuses == nil { return vmwareConnectionRuntimeStatus{} } status, ok := h.statuses[connectionID] if !ok { return vmwareConnectionRuntimeStatus{} } return cloneVMwareRuntimeStatus(status) } func (h *VMwareHandlers) recordTestSuccess(connectionID string, summary *vmware.InventorySummary, at time.Time) { if h == nil { return } h.statusMu.Lock() defer h.statusMu.Unlock() if h.statuses == nil { h.statuses = make(map[string]vmwareConnectionRuntimeStatus) } current := h.statuses[connectionID] current.Poll = &monitoring.VMwareConnectionPollStatus{ IntervalSeconds: defaultVMwarePollIntervalSeconds(), LastAttemptAt: timePointer(at), LastSuccessAt: timePointer(at), } if summary != nil { current.Observed = &monitoring.VMwareConnectionObservedSummary{ CollectedAt: timePointer(at), Hosts: summary.Hosts, VMs: summary.VMs, Datastores: summary.Datastores, VIRelease: strings.TrimSpace(summary.VIRelease), } } h.statuses[connectionID] = current } func (h *VMwareHandlers) recordTestFailure(connectionID string, err error, at time.Time) { if h == nil { return } h.statusMu.Lock() defer h.statusMu.Unlock() if h.statuses == nil { h.statuses = make(map[string]vmwareConnectionRuntimeStatus) } current := h.statuses[connectionID] poll := current.Poll if poll == nil { poll = &monitoring.VMwareConnectionPollStatus{ IntervalSeconds: defaultVMwarePollIntervalSeconds(), } } poll.LastAttemptAt = timePointer(at) poll.LastError = &monitoring.VMwareConnectionPollError{ At: timePointer(at), Message: err.Error(), Category: connectionErrorCategory(err), } current.Poll = poll h.statuses[connectionID] = current } func (h *VMwareHandlers) clearRuntimeStatus(connectionID string) { if h == nil { return } h.statusMu.Lock() defer h.statusMu.Unlock() if h.statuses == nil { return } delete(h.statuses, connectionID) } func cloneVMwareRuntimeStatus(status vmwareConnectionRuntimeStatus) vmwareConnectionRuntimeStatus { cloned := vmwareConnectionRuntimeStatus{} if status.Poll != nil { poll := *status.Poll if poll.LastAttemptAt != nil { poll.LastAttemptAt = timePointer(*poll.LastAttemptAt) } if poll.LastSuccessAt != nil { poll.LastSuccessAt = timePointer(*poll.LastSuccessAt) } if poll.LastError != nil { errCopy := *poll.LastError if errCopy.At != nil { errCopy.At = timePointer(*errCopy.At) } poll.LastError = &errCopy } cloned.Poll = &poll } if status.Observed != nil { observed := *status.Observed if observed.CollectedAt != nil { observed.CollectedAt = timePointer(*observed.CollectedAt) } cloned.Observed = &observed } return cloned } func defaultVMwarePollIntervalSeconds() int { return int((60 * time.Second) / time.Second) } func timePointer(value time.Time) *time.Time { v := value return &v } func vmwareConnectionIDFromPath(path string) (string, bool) { if !strings.HasPrefix(path, vmwareConnectionsPathPrefix) { return "", false } raw := strings.Trim(strings.TrimPrefix(path, vmwareConnectionsPathPrefix), "/") if raw == "" || strings.Contains(raw, "/") { return "", false } connectionID, err := url.PathUnescape(raw) if err != nil { return "", false } connectionID = strings.TrimSpace(connectionID) if connectionID == "" || strings.Contains(connectionID, "/") { return "", false } return connectionID, true } func vmwareConnectionIDFromTestPath(path string) (string, bool) { return vmwareConnectionIDFromActionPath(path, "/test") } func vmwareConnectionIDFromPreviewPath(path string) (string, bool) { return vmwareConnectionIDFromActionPath(path, "/preview") } func vmwareConnectionIDFromActionPath(path string, suffix string) (string, bool) { if !strings.HasPrefix(path, vmwareConnectionsPathPrefix) { return "", false } raw := strings.Trim(strings.TrimPrefix(path, vmwareConnectionsPathPrefix), "/") if !strings.HasSuffix(raw, suffix) { return "", false } raw = strings.TrimSuffix(raw, suffix) if raw == "" || strings.Contains(raw, "/") { return "", false } connectionID, err := url.PathUnescape(raw) if err != nil { return "", false } connectionID = strings.TrimSpace(connectionID) if connectionID == "" || strings.Contains(connectionID, "/") { return "", false } return connectionID, true }