mirror of
https://github.com/lukaszraczylo/kportal.git
synced 2026-06-05 23:03:40 +00:00
Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4074a7186c | |||
| a5cc95a26e | |||
| 0f977683cd | |||
| dcebdf718a | |||
| 5967f26c21 | |||
| 285ced6755 | |||
| 9fe076acb2 | |||
| 92746efcf5 | |||
| 391bce366d | |||
| 9fd8f9b03b | |||
| 7032bb5bee | |||
| 6cb4f91ece | |||
| 5d600043f0 | |||
| 9bb6fbc48d | |||
| f4334ebdc9 |
@@ -12,6 +12,8 @@ on:
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
packages: write
|
||||
id-token: write
|
||||
|
||||
jobs:
|
||||
release:
|
||||
|
||||
@@ -71,3 +71,14 @@ homebrew_casks:
|
||||
system_command "/usr/bin/xattr",
|
||||
args: ["-dr", "com.apple.quarantine", "#{staged_path}/kportal"]
|
||||
end
|
||||
|
||||
signs:
|
||||
- cmd: cosign
|
||||
signature: "${artifact}.sigstore.json"
|
||||
args:
|
||||
- sign-blob
|
||||
- "--bundle=${signature}"
|
||||
- "${artifact}"
|
||||
- "--yes"
|
||||
artifacts: checksum
|
||||
output: true
|
||||
|
||||
@@ -83,6 +83,19 @@ cd kportal
|
||||
make build && make install
|
||||
```
|
||||
|
||||
### Verifying Release Signatures
|
||||
|
||||
All release checksums are signed with [cosign](https://github.com/sigstore/cosign) using keyless signing. To verify:
|
||||
|
||||
```bash
|
||||
# Download the checksum file and its sigstore bundle from the release
|
||||
cosign verify-blob \
|
||||
--certificate-identity-regexp "https://github.com/lukaszraczylo/kportal/.*" \
|
||||
--certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
|
||||
--bundle "kportal-<version>-checksums.txt.sigstore.json" \
|
||||
kportal-<version>-checksums.txt
|
||||
```
|
||||
|
||||
## 🚀 Quick Start
|
||||
|
||||
Create `.kportal.yaml`:
|
||||
|
||||
@@ -309,16 +309,26 @@ func main() {
|
||||
bubbleTeaUI.SetHTTPLogSubscriber(func(forwardID string, callback func(entry ui.HTTPLogEntry)) func() {
|
||||
worker := manager.GetWorker(forwardID)
|
||||
if worker == nil {
|
||||
logger.Debug("HTTP log subscription failed: worker not found", map[string]interface{}{
|
||||
"forward_id": forwardID,
|
||||
})
|
||||
return func() {} // No-op cleanup
|
||||
}
|
||||
|
||||
proxy := worker.GetHTTPProxy()
|
||||
if proxy == nil {
|
||||
// This is expected for forwards without httpLog enabled - not an error
|
||||
logger.Debug("HTTP log subscription skipped: proxy not enabled", map[string]interface{}{
|
||||
"forward_id": forwardID,
|
||||
})
|
||||
return func() {} // HTTP logging not enabled for this forward
|
||||
}
|
||||
|
||||
proxyLogger := proxy.GetLogger()
|
||||
if proxyLogger == nil {
|
||||
logger.Debug("HTTP log subscription failed: logger not available", map[string]interface{}{
|
||||
"forward_id": forwardID,
|
||||
})
|
||||
return func() {}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
module github.com/nvm/kportal
|
||||
|
||||
go 1.24.2
|
||||
go 1.25.0
|
||||
|
||||
require (
|
||||
github.com/charmbracelet/bubbletea v1.3.10
|
||||
@@ -10,20 +10,20 @@ require (
|
||||
github.com/grandcat/zeroconf v1.0.0
|
||||
github.com/stretchr/testify v1.11.1
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
k8s.io/api v0.34.2
|
||||
k8s.io/apimachinery v0.34.2
|
||||
k8s.io/client-go v0.34.2
|
||||
k8s.io/api v0.35.0
|
||||
k8s.io/apimachinery v0.35.0
|
||||
k8s.io/client-go v0.35.0
|
||||
k8s.io/klog/v2 v2.130.1
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
||||
github.com/cenkalti/backoff v2.2.1+incompatible // indirect
|
||||
github.com/charmbracelet/colorprofile v0.3.3 // indirect
|
||||
github.com/charmbracelet/x/ansi v0.11.2 // indirect
|
||||
github.com/charmbracelet/colorprofile v0.4.1 // indirect
|
||||
github.com/charmbracelet/x/ansi v0.11.3 // indirect
|
||||
github.com/charmbracelet/x/cellbuf v0.0.14 // indirect
|
||||
github.com/charmbracelet/x/term v0.2.2 // indirect
|
||||
github.com/clipperhouse/displaywidth v0.6.1 // indirect
|
||||
github.com/clipperhouse/displaywidth v0.6.2 // indirect
|
||||
github.com/clipperhouse/stringish v0.1.1 // indirect
|
||||
github.com/clipperhouse/uax29/v2 v2.3.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
@@ -44,7 +44,6 @@ require (
|
||||
github.com/go-openapi/swag/stringutils v0.25.4 // indirect
|
||||
github.com/go-openapi/swag/typeutils v0.25.4 // indirect
|
||||
github.com/go-openapi/swag/yamlutils v0.25.4 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/google/gnostic-models v0.7.1 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect
|
||||
@@ -54,7 +53,7 @@ require (
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-localereader v0.0.1 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.19 // indirect
|
||||
github.com/miekg/dns v1.1.68 // indirect
|
||||
github.com/miekg/dns v1.1.69 // indirect
|
||||
github.com/moby/spdystream v0.5.0 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
|
||||
@@ -79,11 +78,11 @@ require (
|
||||
golang.org/x/text v0.32.0 // indirect
|
||||
golang.org/x/time v0.14.0 // indirect
|
||||
golang.org/x/tools v0.40.0 // indirect
|
||||
google.golang.org/protobuf v1.36.10 // indirect
|
||||
google.golang.org/protobuf v1.36.11 // indirect
|
||||
gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
k8s.io/kube-openapi v0.0.0-20251125145642-4e65d59e963e // indirect
|
||||
k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 // indirect
|
||||
k8s.io/utils v0.0.0-20260106112306-0fe9cd71b2f8 // indirect
|
||||
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect
|
||||
sigs.k8s.io/randfill v1.0.0 // indirect
|
||||
sigs.k8s.io/structured-merge-diff/v6 v6.3.1 // indirect
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0=
|
||||
github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
|
||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
|
||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
|
||||
@@ -8,20 +10,20 @@ github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEe
|
||||
github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
|
||||
github.com/charmbracelet/bubbletea v1.3.10 h1:otUDHWMMzQSB0Pkc87rm691KZ3SWa4KUlvF9nRvCICw=
|
||||
github.com/charmbracelet/bubbletea v1.3.10/go.mod h1:ORQfo0fk8U+po9VaNvnV95UPWA1BitP1E0N6xJPlHr4=
|
||||
github.com/charmbracelet/colorprofile v0.3.3 h1:DjJzJtLP6/NZ8p7Cgjno0CKGr7wwRJGxWUwh2IyhfAI=
|
||||
github.com/charmbracelet/colorprofile v0.3.3/go.mod h1:nB1FugsAbzq284eJcjfah2nhdSLppN2NqvfotkfRYP4=
|
||||
github.com/charmbracelet/colorprofile v0.4.1 h1:a1lO03qTrSIRaK8c3JRxJDZOvhvIeSco3ej+ngLk1kk=
|
||||
github.com/charmbracelet/colorprofile v0.4.1/go.mod h1:U1d9Dljmdf9DLegaJ0nGZNJvoXAhayhmidOdcBwAvKk=
|
||||
github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY=
|
||||
github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30=
|
||||
github.com/charmbracelet/x/ansi v0.11.2 h1:XAG3FSjiVtFvgEgGrNBkCNNYrsucAt8c6bfxHyROLLs=
|
||||
github.com/charmbracelet/x/ansi v0.11.2/go.mod h1:9tY2bzX5SiJCU0iWyskjBeI2BRQfvPqI+J760Mjf+Rg=
|
||||
github.com/charmbracelet/x/ansi v0.11.3 h1:6DcVaqWI82BBVM/atTyq6yBoRLZFBsnoDoX9GCu2YOI=
|
||||
github.com/charmbracelet/x/ansi v0.11.3/go.mod h1:yI7Zslym9tCJcedxz5+WBq+eUGMJT0bM06Fqy1/Y4dI=
|
||||
github.com/charmbracelet/x/cellbuf v0.0.14 h1:iUEMryGyFTelKW3THW4+FfPgi4fkmKnnaLOXuc+/Kj4=
|
||||
github.com/charmbracelet/x/cellbuf v0.0.14/go.mod h1:P447lJl49ywBbil/KjCk2HexGh4tEY9LH0/1QrZZ9rA=
|
||||
github.com/charmbracelet/x/exp/golden v0.0.0-20240806155701-69247e0abc2a h1:G99klV19u0QnhiizODirwVksQB91TJKV/UaTnACcG30=
|
||||
github.com/charmbracelet/x/exp/golden v0.0.0-20240806155701-69247e0abc2a/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U=
|
||||
github.com/charmbracelet/x/term v0.2.2 h1:xVRT/S2ZcKdhhOuSP4t5cLi5o+JxklsoEObBSgfgZRk=
|
||||
github.com/charmbracelet/x/term v0.2.2/go.mod h1:kF8CY5RddLWrsgVwpw4kAa6TESp6EB5y3uxGLeCqzAI=
|
||||
github.com/clipperhouse/displaywidth v0.6.1 h1:/zMlAezfDzT2xy6acHBzwIfyu2ic0hgkT83UX5EY2gY=
|
||||
github.com/clipperhouse/displaywidth v0.6.1/go.mod h1:R+kHuzaYWFkTm7xoMmK1lFydbci4X2CicfbGstSGg0o=
|
||||
github.com/clipperhouse/displaywidth v0.6.2 h1:ZDpTkFfpHOKte4RG5O/BOyf3ysnvFswpyYrV7z2uAKo=
|
||||
github.com/clipperhouse/displaywidth v0.6.2/go.mod h1:R+kHuzaYWFkTm7xoMmK1lFydbci4X2CicfbGstSGg0o=
|
||||
github.com/clipperhouse/stringish v0.1.1 h1:+NSqMOr3GR6k1FdRhhnXrLfztGzuG+VuFDfatpWHKCs=
|
||||
github.com/clipperhouse/stringish v0.1.1/go.mod h1:v/WhFtE1q0ovMta2+m+UbpZ+2/HEXNWYXQgCt4hdOzA=
|
||||
github.com/clipperhouse/uax29/v2 v2.3.0 h1:SNdx9DVUqMoBuBoW3iLOj4FQv3dN5mDtuqwuhIGpJy4=
|
||||
@@ -76,15 +78,13 @@ github.com/go-openapi/testify/v2 v2.0.2 h1:X999g3jeLcoY8qctY/c/Z8iBHTbwLz7R2WXd6
|
||||
github.com/go-openapi/testify/v2 v2.0.2/go.mod h1:HCPmvFFnheKK2BuwSA0TbbdxJ3I16pjwMkYkP4Ywn54=
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
|
||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/google/gnostic-models v0.7.1 h1:SisTfuFKJSKM5CPZkffwi6coztzzeYUhc3v4yxLWH8c=
|
||||
github.com/google/gnostic-models v0.7.1/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db h1:097atOisP2aRj7vFgYQBbFN4U4JNXUNYpxael3UzMyo=
|
||||
github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
|
||||
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J0b1vyeLSOYI8bm5wbJM/8yDe8=
|
||||
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo=
|
||||
@@ -93,8 +93,6 @@ github.com/grandcat/zeroconf v1.0.0 h1:uHhahLBKqwWBV6WZUDAT71044vwOTL+McW0mBJvo6
|
||||
github.com/grandcat/zeroconf v1.0.0/go.mod h1:lTKmG1zh86XyCoUeIHSA4FJMBwCJiQmGfcP2PdzytEs=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
@@ -108,8 +106,8 @@ github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+Ei
|
||||
github.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw=
|
||||
github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
|
||||
github.com/miekg/dns v1.1.27/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
|
||||
github.com/miekg/dns v1.1.68 h1:jsSRkNozw7G/mnmXULynzMNIsgY2dHC8LO6U6Ij2JEA=
|
||||
github.com/miekg/dns v1.1.68/go.mod h1:fujopn7TB3Pu3JM69XaawiU0wqjpL9/8xGop5UrTPps=
|
||||
github.com/miekg/dns v1.1.69 h1:Kb7Y/1Jo+SG+a2GtfoFUfDkG//csdRPwRLkCsxDG9Sc=
|
||||
github.com/miekg/dns v1.1.69/go.mod h1:7OyjD9nEba5OkqQ/hB4fy3PIoxafSZJtducccIelz3g=
|
||||
github.com/moby/spdystream v0.5.0 h1:7r0J1Si3QO/kjRitvSLVVFUjxMEb/YLj6S9FF62JBCU=
|
||||
github.com/moby/spdystream v0.5.0/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
@@ -128,18 +126,18 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus=
|
||||
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
|
||||
github.com/onsi/ginkgo/v2 v2.21.0 h1:7rg/4f3rB88pb5obDgNZrNHrQ4e6WpjonchcpuBRnZM=
|
||||
github.com/onsi/ginkgo/v2 v2.21.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo=
|
||||
github.com/onsi/gomega v1.35.1 h1:Cwbd75ZBPxFSuZ6T+rN/WCb/gOc6YgFBXLlZLhC7Ds4=
|
||||
github.com/onsi/gomega v1.35.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog=
|
||||
github.com/onsi/ginkgo/v2 v2.27.2 h1:LzwLj0b89qtIy6SSASkzlNvX6WktqurSHwkk2ipF/Ns=
|
||||
github.com/onsi/ginkgo/v2 v2.27.2/go.mod h1:ArE1D/XhNXBXCBkKOLkbsb2c81dQHCRcF5zwn/ykDRo=
|
||||
github.com/onsi/gomega v1.38.2 h1:eZCjf2xjZAqe+LeWvKb5weQ+NcPwX84kqJ0cZNxok2A=
|
||||
github.com/onsi/gomega v1.38.2/go.mod h1:W2MJcYxRGV63b418Ai34Ud0hEdTVXq9NW9+Sx6uXf3k=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
|
||||
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
|
||||
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
|
||||
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
@@ -152,41 +150,31 @@ github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
|
||||
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0=
|
||||
go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8=
|
||||
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
|
||||
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
|
||||
golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI=
|
||||
golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
|
||||
golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=
|
||||
golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw=
|
||||
golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
||||
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
|
||||
@@ -194,24 +182,16 @@ golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q=
|
||||
golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
|
||||
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
|
||||
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
|
||||
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA=
|
||||
golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
|
||||
google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
@@ -221,18 +201,18 @@ gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
|
||||
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
k8s.io/api v0.34.2 h1:fsSUNZhV+bnL6Aqrp6O7lMTy6o5x2C4XLjnh//8SLYY=
|
||||
k8s.io/api v0.34.2/go.mod h1:MMBPaWlED2a8w4RSeanD76f7opUoypY8TFYkSM+3XHw=
|
||||
k8s.io/apimachinery v0.34.2 h1:zQ12Uk3eMHPxrsbUJgNF8bTauTVR2WgqJsTmwTE/NW4=
|
||||
k8s.io/apimachinery v0.34.2/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw=
|
||||
k8s.io/client-go v0.34.2 h1:Co6XiknN+uUZqiddlfAjT68184/37PS4QAzYvQvDR8M=
|
||||
k8s.io/client-go v0.34.2/go.mod h1:2VYDl1XXJsdcAxw7BenFslRQX28Dxz91U9MWKjX97fE=
|
||||
k8s.io/api v0.35.0 h1:iBAU5LTyBI9vw3L5glmat1njFK34srdLmktWwLTprlY=
|
||||
k8s.io/api v0.35.0/go.mod h1:AQ0SNTzm4ZAczM03QH42c7l3bih1TbAXYo0DkF8ktnA=
|
||||
k8s.io/apimachinery v0.35.0 h1:Z2L3IHvPVv/MJ7xRxHEtk6GoJElaAqDCCU0S6ncYok8=
|
||||
k8s.io/apimachinery v0.35.0/go.mod h1:jQCgFZFR1F4Ik7hvr2g84RTJSZegBc8yHgFWKn//hns=
|
||||
k8s.io/client-go v0.35.0 h1:IAW0ifFbfQQwQmga0UdoH0yvdqrbwMdq9vIFEhRpxBE=
|
||||
k8s.io/client-go v0.35.0/go.mod h1:q2E5AAyqcbeLGPdoRB+Nxe3KYTfPce1Dnu1myQdqz9o=
|
||||
k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
|
||||
k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
|
||||
k8s.io/kube-openapi v0.0.0-20251125145642-4e65d59e963e h1:iW9ChlU0cU16w8MpVYjXk12dqQ4BPFBEgif+ap7/hqQ=
|
||||
k8s.io/kube-openapi v0.0.0-20251125145642-4e65d59e963e/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ=
|
||||
k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 h1:SjGebBtkBqHFOli+05xYbK8YF1Dzkbzn+gDM4X9T4Ck=
|
||||
k8s.io/utils v0.0.0-20251002143259-bc988d571ff4/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
|
||||
k8s.io/utils v0.0.0-20260106112306-0fe9cd71b2f8 h1:oV4uULAC2QPIdMQwjMaNIwykyhWhnhBwX40yd5h9u3U=
|
||||
k8s.io/utils v0.0.0-20260106112306-0fe9cd71b2f8/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk=
|
||||
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg=
|
||||
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg=
|
||||
sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU=
|
||||
|
||||
@@ -27,16 +27,6 @@ type Config struct {
|
||||
ProgressCallback ProgressCallback // Optional callback for progress updates
|
||||
}
|
||||
|
||||
// DefaultConfig returns a default benchmark configuration
|
||||
func DefaultConfig() Config {
|
||||
return Config{
|
||||
Method: "GET",
|
||||
Concurrency: 10,
|
||||
Requests: 100,
|
||||
Timeout: 30 * time.Second,
|
||||
}
|
||||
}
|
||||
|
||||
// Runner executes HTTP benchmarks
|
||||
type Runner struct {
|
||||
client *http.Client
|
||||
|
||||
@@ -206,15 +206,6 @@ func TestRunnerWithBody(t *testing.T) {
|
||||
assert.Equal(t, int64(15), results.BytesWritten)
|
||||
}
|
||||
|
||||
func TestDefaultConfig(t *testing.T) {
|
||||
cfg := DefaultConfig()
|
||||
|
||||
assert.Equal(t, "GET", cfg.Method)
|
||||
assert.Equal(t, 10, cfg.Concurrency)
|
||||
assert.Equal(t, 100, cfg.Requests)
|
||||
assert.Equal(t, 30*time.Second, cfg.Timeout)
|
||||
}
|
||||
|
||||
func TestRunnerWithProgressCallback(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
time.Sleep(10 * time.Millisecond) // Add small delay so progress ticker can fire
|
||||
|
||||
@@ -22,11 +22,6 @@ type ValidationError struct {
|
||||
Context map[string]string // Additional context information
|
||||
}
|
||||
|
||||
// Error implements the error interface.
|
||||
func (e ValidationError) Error() string {
|
||||
return e.Message
|
||||
}
|
||||
|
||||
// Validator validates configuration files.
|
||||
type Validator struct{}
|
||||
|
||||
|
||||
@@ -621,15 +621,6 @@ func TestFormatValidationErrors(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidationError_Error(t *testing.T) {
|
||||
err := ValidationError{
|
||||
Field: "port",
|
||||
Message: "Invalid port 0",
|
||||
}
|
||||
|
||||
assert.Equal(t, "Invalid port 0", err.Error(), "Error() should return the message")
|
||||
}
|
||||
|
||||
func TestValidator_ValidateStructure(t *testing.T) {
|
||||
validator := NewValidator()
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ type Watcher struct {
|
||||
done chan struct{}
|
||||
verbose bool
|
||||
wg sync.WaitGroup // Ensures watch goroutine exits before Stop returns
|
||||
stopOnce sync.Once // Ensures Stop is safe to call multiple times
|
||||
}
|
||||
|
||||
// NewWatcher creates a new file watcher for the given config file.
|
||||
@@ -61,9 +62,12 @@ func (w *Watcher) Start() {
|
||||
}
|
||||
|
||||
// Stop stops watching the configuration file and waits for the watch goroutine to exit.
|
||||
// Safe to call multiple times.
|
||||
func (w *Watcher) Stop() {
|
||||
close(w.done)
|
||||
_ = w.watcher.Close()
|
||||
w.stopOnce.Do(func() {
|
||||
close(w.done)
|
||||
_ = w.watcher.Close()
|
||||
})
|
||||
w.wg.Wait() // Wait for watch goroutine to exit
|
||||
}
|
||||
|
||||
|
||||
@@ -135,15 +135,6 @@ func (b *Bus) Close() {
|
||||
|
||||
// Helper functions for creating common events
|
||||
|
||||
// NewForwardEvent creates a forward-related event
|
||||
func NewForwardEvent(eventType EventType, forwardID string, data map[string]interface{}) Event {
|
||||
return Event{
|
||||
Type: eventType,
|
||||
ForwardID: forwardID,
|
||||
Data: data,
|
||||
}
|
||||
}
|
||||
|
||||
// NewHealthEvent creates a health status change event
|
||||
func NewHealthEvent(forwardID string, status string, errorMsg string) Event {
|
||||
return Event{
|
||||
|
||||
@@ -149,16 +149,6 @@ func TestBus_ConcurrentAccess(t *testing.T) {
|
||||
assert.Equal(t, int64(100), atomic.LoadInt64(&count))
|
||||
}
|
||||
|
||||
func TestNewForwardEvent(t *testing.T) {
|
||||
event := NewForwardEvent(EventForwardStarting, "test-id", map[string]interface{}{
|
||||
"pod": "my-pod",
|
||||
})
|
||||
|
||||
assert.Equal(t, EventForwardStarting, event.Type)
|
||||
assert.Equal(t, "test-id", event.ForwardID)
|
||||
assert.Equal(t, "my-pod", event.Data["pod"])
|
||||
}
|
||||
|
||||
func TestNewHealthEvent(t *testing.T) {
|
||||
event := NewHealthEvent("test-id", "Active", "")
|
||||
|
||||
|
||||
@@ -139,11 +139,6 @@ func (m *Manager) SetMDNSPublisher(publisher *mdns.Publisher) {
|
||||
m.mdnsPublisher = publisher
|
||||
}
|
||||
|
||||
// GetEventBus returns the event bus for subscribing to manager events
|
||||
func (m *Manager) GetEventBus() *events.Bus {
|
||||
return m.eventBus
|
||||
}
|
||||
|
||||
// Start initializes and starts all port-forwards from the configuration.
|
||||
func (m *Manager) Start(cfg *config.Config) error {
|
||||
if cfg == nil {
|
||||
@@ -493,27 +488,6 @@ func (m *Manager) stopWorkerInternal(id string, removeFromUI bool) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetActiveForwards returns a list of all active forward IDs.
|
||||
func (m *Manager) GetActiveForwards() []string {
|
||||
m.workersMu.RLock()
|
||||
defer m.workersMu.RUnlock()
|
||||
|
||||
ids := make([]string, 0, len(m.workers))
|
||||
for id := range m.workers {
|
||||
ids = append(ids, id)
|
||||
}
|
||||
|
||||
return ids
|
||||
}
|
||||
|
||||
// GetWorkerCount returns the number of active workers.
|
||||
func (m *Manager) GetWorkerCount() int {
|
||||
m.workersMu.RLock()
|
||||
defer m.workersMu.RUnlock()
|
||||
|
||||
return len(m.workers)
|
||||
}
|
||||
|
||||
// GetWorker returns a worker by ID, or nil if not found.
|
||||
func (m *Manager) GetWorker(id string) *ForwardWorker {
|
||||
m.workersMu.RLock()
|
||||
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
"github.com/nvm/kportal/internal/config"
|
||||
"github.com/nvm/kportal/internal/events"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// TestNewManager tests manager creation
|
||||
@@ -53,41 +52,6 @@ func TestManager_SetStatusUI(t *testing.T) {
|
||||
assert.Equal(t, mockUI, manager.statusUI)
|
||||
}
|
||||
|
||||
// TestManager_GetEventBus tests getting the event bus
|
||||
func TestManager_GetEventBus(t *testing.T) {
|
||||
manager, err := NewManager(false)
|
||||
if err != nil {
|
||||
t.Skip("Skipping test - no kubeconfig available")
|
||||
}
|
||||
defer manager.Stop()
|
||||
|
||||
bus := manager.GetEventBus()
|
||||
assert.NotNil(t, bus)
|
||||
}
|
||||
|
||||
// TestManager_GetWorkerCount tests worker count tracking
|
||||
func TestManager_GetWorkerCount(t *testing.T) {
|
||||
manager, err := NewManager(false)
|
||||
if err != nil {
|
||||
t.Skip("Skipping test - no kubeconfig available")
|
||||
}
|
||||
defer manager.Stop()
|
||||
|
||||
assert.Equal(t, 0, manager.GetWorkerCount())
|
||||
}
|
||||
|
||||
// TestManager_GetActiveForwards tests getting active forwards
|
||||
func TestManager_GetActiveForwards(t *testing.T) {
|
||||
manager, err := NewManager(false)
|
||||
if err != nil {
|
||||
t.Skip("Skipping test - no kubeconfig available")
|
||||
}
|
||||
defer manager.Stop()
|
||||
|
||||
forwards := manager.GetActiveForwards()
|
||||
assert.Empty(t, forwards)
|
||||
}
|
||||
|
||||
// TestManager_GetWorker tests getting a worker by ID
|
||||
func TestManager_GetWorker(t *testing.T) {
|
||||
manager, err := NewManager(false)
|
||||
@@ -362,12 +326,8 @@ func TestManager_EventBusIntegration(t *testing.T) {
|
||||
// Event bus should be wired to health checker and watchdog
|
||||
assert.NotNil(t, manager.eventBus)
|
||||
|
||||
// Get event bus
|
||||
bus := manager.GetEventBus()
|
||||
require.NotNil(t, bus)
|
||||
|
||||
// SubscribeAll should work (no return value in this API)
|
||||
bus.SubscribeAll(func(event events.Event) {
|
||||
manager.eventBus.SubscribeAll(func(event events.Event) {
|
||||
// Handler
|
||||
})
|
||||
}
|
||||
|
||||
@@ -336,6 +336,11 @@ func (w *ForwardWorker) establishForward(podName string) error {
|
||||
// Start port forwarding in a goroutine
|
||||
errChan := make(chan error, 1)
|
||||
go func() {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
errChan <- fmt.Errorf("port forward panicked: %v", r)
|
||||
}
|
||||
}()
|
||||
errChan <- w.portForwarder.Forward(forwardCtx, req)
|
||||
}()
|
||||
|
||||
@@ -409,6 +414,14 @@ func (w *ForwardWorker) startHTTPProxy() error {
|
||||
// Calculate internal port for k8s tunnel
|
||||
targetPort := w.forward.LocalPort + httpLogPortOffset
|
||||
|
||||
// Validate that the target port is available before attempting to bind
|
||||
portChecker := NewPortChecker()
|
||||
if !portChecker.isPortAvailable(targetPort) {
|
||||
usedBy := portChecker.getProcessUsingPort(targetPort)
|
||||
return fmt.Errorf("HTTP proxy target port %d is already in use by %s (forward port %d + offset %d)",
|
||||
targetPort, usedBy, w.forward.LocalPort, httpLogPortOffset)
|
||||
}
|
||||
|
||||
proxy, err := httplog.NewProxy(&w.forward, targetPort)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create HTTP proxy: %w", err)
|
||||
|
||||
@@ -365,7 +365,8 @@ func (c *Checker) checkPort(forwardID string) {
|
||||
}
|
||||
}
|
||||
|
||||
// Update health status
|
||||
// Update health status and capture eventBus while holding lock
|
||||
var bus *events.Bus
|
||||
c.mu.Lock()
|
||||
if health, exists := c.ports[forwardID]; exists {
|
||||
health.Status = newStatus
|
||||
@@ -378,17 +379,15 @@ func (c *Checker) checkPort(forwardID string) {
|
||||
health.LastActivity = now
|
||||
}
|
||||
}
|
||||
// Capture eventBus while we have the lock to avoid race condition
|
||||
bus = c.eventBus
|
||||
c.mu.Unlock()
|
||||
|
||||
// Notify if status changed
|
||||
if oldStatus != newStatus {
|
||||
c.notifyStatusChange(forwardID, newStatus, errorMsg)
|
||||
|
||||
// Publish to event bus if available
|
||||
c.mu.RLock()
|
||||
bus := c.eventBus
|
||||
c.mu.RUnlock()
|
||||
|
||||
// Publish to event bus if available (captured while holding lock above)
|
||||
if bus != nil {
|
||||
if newStatus == StatusStale {
|
||||
bus.Publish(events.NewStaleEvent(forwardID, errorMsg))
|
||||
|
||||
@@ -228,7 +228,7 @@ func TestResolveTargetPort(t *testing.T) {
|
||||
for i := range tt.pods {
|
||||
objects = append(objects, &tt.pods[i])
|
||||
}
|
||||
fakeClient := fake.NewSimpleClientset(objects...)
|
||||
fakeClient := fake.NewClientset(objects...)
|
||||
|
||||
// Create discovery instance (we only need it to call resolveTargetPort)
|
||||
d := &Discovery{}
|
||||
|
||||
@@ -195,29 +195,12 @@ func shutdownWithTimeout(server *zeroconf.Server, forwardID string) {
|
||||
}
|
||||
}
|
||||
|
||||
// IsEnabled returns whether mDNS publishing is enabled.
|
||||
func (p *Publisher) IsEnabled() bool {
|
||||
return p.enabled
|
||||
}
|
||||
|
||||
// GetDomain returns the mDNS domain being used (always "local" per RFC 6762).
|
||||
func (p *Publisher) GetDomain() string {
|
||||
return mdnsDomain
|
||||
}
|
||||
|
||||
// GetHostname returns the full mDNS hostname for an alias.
|
||||
// Example: GetHostname("myapp") returns "myapp.local"
|
||||
func GetHostname(alias string) string {
|
||||
return alias + "." + mdnsDomain
|
||||
}
|
||||
|
||||
// GetRegisteredCount returns the number of currently registered hostnames.
|
||||
func (p *Publisher) GetRegisteredCount() int {
|
||||
p.mu.RLock()
|
||||
defer p.mu.RUnlock()
|
||||
return len(p.servers)
|
||||
}
|
||||
|
||||
// getLocalIPs returns the local IP addresses for logging purposes.
|
||||
func getLocalIPs() []string {
|
||||
var ips []string
|
||||
|
||||
@@ -13,15 +13,17 @@ import (
|
||||
func TestNewPublisher_Disabled(t *testing.T) {
|
||||
p := NewPublisher(false)
|
||||
|
||||
assert.False(t, p.IsEnabled())
|
||||
assert.Equal(t, 0, p.GetRegisteredCount())
|
||||
// When disabled, Register should succeed but be a no-op
|
||||
err := p.Register("forward-1", "test-alias", 8080)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestNewPublisher_Enabled(t *testing.T) {
|
||||
p := NewPublisher(true)
|
||||
defer p.Stop()
|
||||
|
||||
assert.True(t, p.IsEnabled())
|
||||
assert.Equal(t, 0, p.GetRegisteredCount())
|
||||
// Enabled publisher should be created successfully
|
||||
assert.NotNil(t, p)
|
||||
}
|
||||
|
||||
func TestRegister_WhenDisabled_NoOp(t *testing.T) {
|
||||
@@ -30,16 +32,17 @@ func TestRegister_WhenDisabled_NoOp(t *testing.T) {
|
||||
err := p.Register("forward-1", "test-alias", 8080)
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 0, p.GetRegisteredCount())
|
||||
// Unregister should also be safe when disabled
|
||||
p.Unregister("forward-1")
|
||||
}
|
||||
|
||||
func TestRegister_EmptyAlias_NoOp(t *testing.T) {
|
||||
p := NewPublisher(true)
|
||||
defer p.Stop()
|
||||
|
||||
err := p.Register("forward-1", "", 8080)
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 0, p.GetRegisteredCount())
|
||||
}
|
||||
|
||||
func TestUnregister_WhenDisabled_NoOp(t *testing.T) {
|
||||
@@ -51,10 +54,10 @@ func TestUnregister_WhenDisabled_NoOp(t *testing.T) {
|
||||
|
||||
func TestUnregister_NotRegistered_NoOp(t *testing.T) {
|
||||
p := NewPublisher(true)
|
||||
defer p.Stop()
|
||||
|
||||
// Should not panic
|
||||
p.Unregister("non-existent")
|
||||
assert.Equal(t, 0, p.GetRegisteredCount())
|
||||
}
|
||||
|
||||
func TestStop_WhenDisabled_NoOp(t *testing.T) {
|
||||
@@ -69,7 +72,6 @@ func TestStop_WhenNoRegistrations(t *testing.T) {
|
||||
|
||||
// Should not panic
|
||||
p.Stop()
|
||||
assert.Equal(t, 0, p.GetRegisteredCount())
|
||||
}
|
||||
|
||||
func TestGetLocalIPs(t *testing.T) {
|
||||
@@ -84,6 +86,11 @@ func TestGetLocalIPs(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetHostname(t *testing.T) {
|
||||
hostname := GetHostname("myapp")
|
||||
assert.Equal(t, "myapp.local", hostname)
|
||||
}
|
||||
|
||||
// Integration tests - only run when explicitly requested
|
||||
// These tests actually register mDNS services and require network access
|
||||
|
||||
@@ -96,9 +103,10 @@ func TestRegister_Integration(t *testing.T) {
|
||||
defer p.Stop()
|
||||
|
||||
err := p.Register("forward-1", "test-service", 8080)
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 1, p.GetRegisteredCount())
|
||||
|
||||
// Verify by checking that unregister doesn't panic
|
||||
p.Unregister("forward-1")
|
||||
}
|
||||
|
||||
func TestRegister_Duplicate_Idempotent_Integration(t *testing.T) {
|
||||
@@ -112,12 +120,10 @@ func TestRegister_Duplicate_Idempotent_Integration(t *testing.T) {
|
||||
// First registration
|
||||
err := p.Register("forward-1", "test-service", 8080)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 1, p.GetRegisteredCount())
|
||||
|
||||
// Second registration with same ID should be idempotent
|
||||
err = p.Register("forward-1", "test-service", 8080)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 1, p.GetRegisteredCount())
|
||||
}
|
||||
|
||||
func TestRegister_MultipleForwards_Integration(t *testing.T) {
|
||||
@@ -135,7 +141,6 @@ func TestRegister_MultipleForwards_Integration(t *testing.T) {
|
||||
assert.NoError(t, err1)
|
||||
assert.NoError(t, err2)
|
||||
assert.NoError(t, err3)
|
||||
assert.Equal(t, 3, p.GetRegisteredCount())
|
||||
}
|
||||
|
||||
func TestUnregister_Success_Integration(t *testing.T) {
|
||||
@@ -146,9 +151,13 @@ func TestUnregister_Success_Integration(t *testing.T) {
|
||||
p := NewPublisher(true)
|
||||
defer p.Stop()
|
||||
|
||||
p.Register("forward-1", "test-service", 8080)
|
||||
assert.Equal(t, 1, p.GetRegisteredCount())
|
||||
err := p.Register("forward-1", "test-service", 8080)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Unregister should not panic and should handle it gracefully
|
||||
p.Unregister("forward-1")
|
||||
assert.Equal(t, 0, p.GetRegisteredCount())
|
||||
|
||||
// Re-registering should work after unregister
|
||||
err = p.Register("forward-1", "test-service-2", 8080)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
@@ -11,6 +11,9 @@ const (
|
||||
initialDelay = 1 * time.Second
|
||||
maxDelay = 10 * time.Second
|
||||
jitterPct = 0.1 // 10% jitter
|
||||
// maxAttempt caps the exponent to prevent math.Pow overflow
|
||||
// 2^30 seconds is ~34 years, well above maxDelay, so this is safe
|
||||
maxAttempt = 30
|
||||
)
|
||||
|
||||
// Backoff implements exponential backoff with jitter for retry logic.
|
||||
@@ -33,8 +36,14 @@ func NewBackoff() *Backoff {
|
||||
// The duration follows exponential backoff: 1s → 2s → 4s → 8s → 10s (max).
|
||||
// A 10% jitter is added to prevent thundering herd effects.
|
||||
func (b *Backoff) Next() time.Duration {
|
||||
// Cap attempt to prevent overflow in math.Pow
|
||||
attempt := b.attempt
|
||||
if attempt > maxAttempt {
|
||||
attempt = maxAttempt
|
||||
}
|
||||
|
||||
// Calculate base delay: 2^attempt seconds
|
||||
exp := math.Pow(2, float64(b.attempt))
|
||||
exp := math.Pow(2, float64(attempt))
|
||||
delay := time.Duration(exp) * time.Second
|
||||
|
||||
// Cap at max delay
|
||||
|
||||
@@ -150,9 +150,3 @@ func parseVersion(v string) []int {
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// FormatUpdateMessage formats a user-friendly update notification
|
||||
func (u *UpdateInfo) FormatUpdateMessage() string {
|
||||
return fmt.Sprintf("New version available: %s (current: %s) - %s",
|
||||
u.LatestVersion, u.CurrentVersion, u.ReleaseURL)
|
||||
}
|
||||
|
||||
@@ -75,16 +75,3 @@ func TestIsNewerVersion(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateInfo_FormatUpdateMessage(t *testing.T) {
|
||||
info := &UpdateInfo{
|
||||
CurrentVersion: "0.1.0",
|
||||
LatestVersion: "0.2.0",
|
||||
ReleaseURL: "https://github.com/nvm/kportal/releases/tag/v0.2.0",
|
||||
}
|
||||
|
||||
msg := info.FormatUpdateMessage()
|
||||
assert.Contains(t, msg, "0.2.0")
|
||||
assert.Contains(t, msg, "0.1.0")
|
||||
assert.Contains(t, msg, "https://github.com/nvm/kportal/releases/tag/v0.2.0")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user