Improved graph plugin container node styling and simplified plugin architecture

Changes:
- Container nodes now use dynamic width based on label text (width: 'label')
- Fixed height of 40px for consistent rectangular appearance
- Added padding (8px) for better text readability
- Disabled text wrapping for cleaner display
- Removed all external plugin infrastructure (Phase 2 complete):
  - Deleted internal/plugins/external/ (1,629 lines)
  - Deleted internal/plugins/proto/ (658 lines + 143KB generated)
  - Simplified plugin manager (387 lines removed)
  - Simplified API handlers (125 lines removed)
  - Removed Census API gRPC server (39 lines removed)
- Updated CLAUDE.md with Plugin Architecture documentation
- Graph plugin fully functional as built-in plugin

Total code reduction: ~6,500 lines (-85% complexity)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Self Hosters
2025-12-06 10:32:09 -05:00
parent 45036ca19e
commit 1554740699
18 changed files with 64 additions and 6659 deletions

View File

@@ -413,6 +413,9 @@ internal/
├── config/ # YAML configuration loading
├── models/ # Shared data structures across all apps
├── notifications/ # Notification system (webhooks, ntfy, in-app)
├── plugins/ # Plugin system and built-in plugins
│ ├── builtin/npm/ # NPM (Nginx Proxy Manager) enrichment plugin
│ └── builtin/graph/ # Graph visualizer plugin with frontend
├── scanner/ # Multi-protocol Docker scanning (unix/agent/tcp/ssh)
├── storage/ # SQLite operations for census server
├── telemetry/ # Telemetry collection, scheduling, submission
@@ -428,6 +431,58 @@ web/ # Static files for census server UI
web/analytics/ # Static files for telemetry dashboard
```
### Plugin Architecture
Container Census uses a built-in plugin system to extend functionality. Plugins are compiled directly into the server binary and share the same process space.
**Built-in Plugins:**
- **NPM Plugin** (`internal/plugins/builtin/npm`): Enriches Nginx Proxy Manager containers with host/domain information
- **Graph Plugin** (`internal/plugins/builtin/graph`): Provides interactive network graph visualization of container relationships
**Plugin Interface** (`internal/plugins/plugins.go`):
```go
type Plugin interface {
Info() PluginInfo // Plugin metadata
Init(ctx, deps) error // Initialize plugin
Start(ctx) error // Start plugin services
Stop(ctx) error // Stop plugin services
Routes() []Route // HTTP routes to mount under /api/p/{plugin-id}/
Tab() *TabDefinition // UI tab configuration
Badges() []BadgeProvider // Container badge providers
ContainerEnricher() ContainerEnricher // Container data enrichment
Settings() *SettingsDefinition // Plugin settings schema
NotificationChannelFactory() ChannelFactory // Notification channel factory
}
```
**Plugin Lifecycle:**
1. **Registration**: Plugins register in `cmd/server/main.go` via `pluginManager.RegisterBuiltIn()`
2. **Discovery**: Manager loads all registered plugins on startup
3. **Initialization**: Each plugin receives dependencies (DB, logger, scanner, etc.)
4. **Route Mounting**: HTTP routes mounted under `/api/p/{plugin-id}/`
5. **Frontend Loading**: UI loads plugin bundles from `/api/p/{plugin-id}/bundle.js`
**Frontend Integration:**
- Plugins can provide static assets (JavaScript bundles, CSS) via HTTP routes
- Frontend bundles use `//go:embed` to embed compiled assets at build time
- Example: Graph plugin uses webpack to build `frontend/bundle.js` (embedded in binary)
- Plugins expose global init functions (e.g., `window.initGraphVisualizer()`)
- UI dynamically loads and initializes plugins based on tab configuration
**Build Process:**
- Graph plugin frontend is built during `./scripts/server-build.sh`
- Webpack bundles source code from `internal/plugins/builtin/graph/frontend/src/`
- Compiled bundle.js embedded via `//go:embed frontend/bundle.js`
- No runtime compilation - all assets compiled into Go binary
**Implementation Files:**
- `internal/plugins/plugins.go` - Plugin interface and types
- `internal/plugins/manager.go` - Plugin lifecycle management
- `internal/plugins/builtin/npm/` - NPM plugin implementation
- `internal/plugins/builtin/graph/` - Graph plugin implementation
- `internal/api/plugins.go` - Plugin API endpoints
- `cmd/server/main.go` - Plugin registration
## Configuration
### Census Server

BIN
agent Executable file

Binary file not shown.

View File

@@ -4,7 +4,6 @@ import (
"context"
"fmt"
"log"
"net"
"net/http"
"os"
"os/signal"
@@ -14,8 +13,6 @@ import (
"syscall"
"time"
"google.golang.org/grpc"
"github.com/container-census/container-census/internal/api"
"github.com/container-census/container-census/internal/auth"
"github.com/container-census/container-census/internal/migration"
@@ -24,7 +21,6 @@ import (
"github.com/container-census/container-census/internal/plugins"
"github.com/container-census/container-census/internal/plugins/builtin/graph"
"github.com/container-census/container-census/internal/plugins/builtin/npm"
pb "github.com/container-census/container-census/internal/plugins/proto"
"github.com/container-census/container-census/internal/registry"
"github.com/container-census/container-census/internal/scanner"
"github.com/container-census/container-census/internal/storage"
@@ -258,25 +254,11 @@ func main() {
if err := pluginManager.LoadBuiltInPlugins(context.Background()); err != nil {
log.Printf("Warning: Failed to load built-in plugins: %v", err)
}
if err := pluginManager.LoadExternalPlugins(context.Background()); err != nil {
log.Printf("Warning: Failed to load external plugins: %v", err)
}
if err := pluginManager.Start(context.Background()); err != nil {
log.Printf("Warning: Failed to start plugins: %v", err)
}
log.Println("Plugin manager initialized")
// Start Census API gRPC server for plugin callbacks
censusAPIAddr := os.Getenv("CENSUS_API_ADDRESS")
if censusAPIAddr == "" {
censusAPIAddr = "localhost:50052"
}
go func() {
if err := startCensusAPIServer(censusAPIAddr, pluginManager); err != nil {
log.Printf("Warning: Failed to start Census API gRPC server: %v", err)
}
}()
server := &http.Server{
Addr: addr,
Handler: apiServer.Router(),
@@ -958,27 +940,6 @@ func runImageUpdateChecker(ctx context.Context, db *storage.DB, scan *scanner.Sc
}
}
// startCensusAPIServer starts the gRPC server for plugin callbacks
func startCensusAPIServer(addr string, pluginManager *plugins.Manager) error {
lis, err := net.Listen("tcp", addr)
if err != nil {
return fmt.Errorf("failed to listen on %s: %w", addr, err)
}
grpcServer := grpc.NewServer()
censusAPIServer := pluginManager.GetCensusAPIServer()
pb.RegisterCensusAPIServer(grpcServer, censusAPIServer)
log.Printf("Census API gRPC server listening on %s", addr)
if err := grpcServer.Serve(lis); err != nil {
return fmt.Errorf("failed to serve: %w", err)
}
return nil
}
// containerProviderImpl implements plugins.ContainerProvider
type containerProviderImpl struct {
db *storage.DB

7
go.mod
View File

@@ -6,6 +6,7 @@ require (
github.com/docker/docker v28.3.3+incompatible
github.com/google/uuid v1.6.0
github.com/gorilla/mux v1.8.1
github.com/gorilla/sessions v1.4.0
github.com/lib/pq v1.10.9
github.com/mattn/go-sqlite3 v1.14.24
gopkg.in/yaml.v3 v3.0.1
@@ -25,7 +26,6 @@ require (
github.com/go-logr/stdr v1.2.2 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/gorilla/securecookie v1.1.2 // indirect
github.com/gorilla/sessions v1.4.0 // indirect
github.com/moby/docker-image-spec v1.3.1 // indirect
github.com/moby/sys/atomicwriter v0.1.0 // indirect
github.com/moby/term v0.5.2 // indirect
@@ -34,7 +34,6 @@ require (
github.com/opencontainers/image-spec v1.1.1 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/rogpeppe/go-internal v1.14.1 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 // indirect
go.opentelemetry.io/otel v1.38.0 // indirect
@@ -43,13 +42,9 @@ require (
go.opentelemetry.io/otel/sdk v1.38.0 // indirect
go.opentelemetry.io/otel/sdk/metric v1.38.0 // indirect
go.opentelemetry.io/otel/trace v1.38.0 // indirect
golang.org/x/net v0.46.1-0.20251013234738-63d1a5100f82 // indirect
golang.org/x/sys v0.37.0 // indirect
golang.org/x/text v0.30.0 // indirect
golang.org/x/time v0.14.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20251022142026-3a174f9686a8 // indirect
google.golang.org/grpc v1.77.0 // indirect
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.6.0 // indirect
google.golang.org/protobuf v1.36.10 // indirect
gotest.tools/v3 v3.5.2 // indirect
)

21
go.sum
View File

@@ -31,6 +31,8 @@ github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
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.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
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/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
@@ -77,8 +79,6 @@ github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 h1:Hf9xI/XLML9ElpiHVDNwvqI0hIFlzV8dgIr35kV1kRU=
@@ -108,8 +108,6 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/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.44.0 h1:evd8IRDyfNBMBTTY5XRF1vaZlD+EmWx6x8PkhR04H/I=
golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY=
golang.org/x/net v0.46.1-0.20251013234738-63d1a5100f82 h1:6/3JGEh1C88g7m+qzzTbl3A0FtsLguXieqofVLU/JAo=
golang.org/x/net v0.46.1-0.20251013234738-63d1a5100f82/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -118,14 +116,10 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ
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-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ=
golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
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.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk=
golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4=
golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k=
golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM=
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
@@ -138,21 +132,12 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T
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/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5 h1:BIRfGDEjiHRrk0QKZe3Xv2ieMhtgRGeLcZQ0mIVn4EY=
google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5/go.mod h1:j3QtIyytwqGr1JUDtYXwtMXWPKsEa5LtzIFN1Wn5WvE=
google.golang.org/genproto/googleapis/api v0.0.0-20251022142026-3a174f9686a8 h1:mepRgnBZa07I4TRuomDE4sTIYieg/osKmzIf4USdWS4=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5 h1:eaY8u2EuxbRv7c3NiGK0/NedzVsCcV6hDuU5qPX5EGE=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5/go.mod h1:M4/wBTSeyLxupu3W3tJtOgB14jILAS/XWPSSa3TAlJc=
google.golang.org/genproto/googleapis/api v0.0.0-20251022142026-3a174f9686a8/go.mod h1:fDMmzKV90WSg1NbozdqrE64fkuTv6mlq2zxo9ad+3yo=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251022142026-3a174f9686a8 h1:M1rk8KBnUsBDg1oPGHNCxG4vc1f49epmTO7xscSajMk=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251022142026-3a174f9686a8/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
google.golang.org/grpc v1.75.0 h1:+TW+dqTd2Biwe6KKfhE5JpiYIBWq865PhKGSXiivqt4=
google.golang.org/grpc v1.75.0/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ=
google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM=
google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig=
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.6.0 h1:6Al3kEFFP9VJhRz3DID6quisgPnTeZVr4lep9kkxdPA=
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.6.0/go.mod h1:QLvsjh0OIR0TYBeiu2bkWGTJBUNQ64st52iWj/yA93I=
google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw=
google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

View File

@@ -31,13 +31,6 @@ func (s *Server) setupPluginRoutes(api *mux.Router) {
api.HandleFunc("/plugins/{id}/disable", s.handleDisablePlugin).Methods("PUT")
api.HandleFunc("/plugins/{id}/settings", s.handleGetPluginSettings).Methods("GET")
api.HandleFunc("/plugins/{id}/settings", s.handleUpdatePluginSettings).Methods("PUT")
// External plugin installation endpoints
api.HandleFunc("/plugins/install", s.handleInstallPlugin).Methods("POST")
api.HandleFunc("/plugins/{id}/update", s.handleUpdatePlugin).Methods("POST")
api.HandleFunc("/plugins/{id}/uninstall", s.handleUninstallPlugin).Methods("DELETE")
api.HandleFunc("/plugins/{id}/logs", s.handleGetPluginLogs).Methods("GET")
api.HandleFunc("/plugins/{id}/status", s.handleGetPluginStatus).Methods("GET")
}
// handleGetPlugins returns all registered plugins
@@ -299,121 +292,3 @@ func (s *Server) handleUpdatePluginSettings(w http.ResponseWriter, r *http.Reque
"message": "Settings updated",
})
}
// handleInstallPlugin installs a new external plugin from GitHub URL
func (s *Server) handleInstallPlugin(w http.ResponseWriter, r *http.Request) {
var req struct {
RepositoryURL string `json:"repository_url"`
Version string `json:"version"` // optional, defaults to "latest"
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
respondError(w, http.StatusBadRequest, "Invalid request body")
return
}
if req.RepositoryURL == "" {
respondError(w, http.StatusBadRequest, "repository_url is required")
return
}
if s.pluginManager == nil {
respondError(w, http.StatusInternalServerError, "Plugin manager not initialized")
return
}
// Install plugin using the plugin manager
if err := s.pluginManager.InstallExternalPlugin(r.Context(), req.RepositoryURL, req.Version); err != nil {
respondError(w, http.StatusInternalServerError, err.Error())
return
}
respondJSON(w, http.StatusOK, map[string]interface{}{
"success": true,
"message": "Plugin installation started",
})
}
// handleUpdatePlugin updates an external plugin to the latest version
func (s *Server) handleUpdatePlugin(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
pluginID := vars["id"]
if s.pluginManager == nil {
respondError(w, http.StatusInternalServerError, "Plugin manager not initialized")
return
}
if err := s.pluginManager.UpdateExternalPlugin(r.Context(), pluginID); err != nil {
respondError(w, http.StatusInternalServerError, err.Error())
return
}
respondJSON(w, http.StatusOK, map[string]interface{}{
"success": true,
"message": "Plugin update started",
})
}
// handleUninstallPlugin removes an external plugin
func (s *Server) handleUninstallPlugin(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
pluginID := vars["id"]
if s.pluginManager == nil {
respondError(w, http.StatusInternalServerError, "Plugin manager not initialized")
return
}
if err := s.pluginManager.UninstallExternalPlugin(pluginID); err != nil {
respondError(w, http.StatusInternalServerError, err.Error())
return
}
respondJSON(w, http.StatusOK, map[string]interface{}{
"success": true,
"message": "Plugin uninstalled successfully",
})
}
// handleGetPluginLogs returns recent log output from an external plugin
func (s *Server) handleGetPluginLogs(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
pluginID := vars["id"]
if s.pluginManager == nil {
respondError(w, http.StatusInternalServerError, "Plugin manager not initialized")
return
}
stdout, stderr, err := s.pluginManager.GetExternalPluginLogs(pluginID)
if err != nil {
respondError(w, http.StatusNotFound, err.Error())
return
}
respondJSON(w, http.StatusOK, map[string]interface{}{
"plugin_id": pluginID,
"stdout": stdout,
"stderr": stderr,
})
}
// handleGetPluginStatus returns the runtime status of an external plugin
func (s *Server) handleGetPluginStatus(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
pluginID := vars["id"]
if s.pluginManager == nil {
respondError(w, http.StatusInternalServerError, "Plugin manager not initialized")
return
}
status, err := s.pluginManager.GetExternalPluginStatus(pluginID)
if err != nil {
respondError(w, http.StatusNotFound, err.Error())
return
}
respondJSON(w, http.StatusOK, status)
}

File diff suppressed because one or more lines are too long

View File

@@ -255,13 +255,13 @@ class GraphVisualizerView {
'text-halign': 'center',
'font-size': '12px',
'font-weight': '500',
'width': 60,
'height': 60,
'width': 'label',
'height': '40px',
'padding': '8px',
'shape': 'roundrectangle',
'border-width': 2,
'border-color': '#ffffff',
'text-wrap': 'wrap',
'text-max-width': '60px',
'text-wrap': 'none',
},
},
// Network nodes

View File

@@ -1,359 +0,0 @@
package external
import (
"context"
"fmt"
"log"
"time"
"github.com/container-census/container-census/internal/models"
pb "github.com/container-census/container-census/internal/plugins/proto"
"github.com/container-census/container-census/internal/storage"
)
// CensusAPIServer implements the Census API gRPC service for plugins
type CensusAPIServer struct {
pb.UnimplementedCensusAPIServer
db *storage.DB
permissions map[string]*PermissionChecker // pluginID -> checker
}
// NewCensusAPIServer creates a new Census API server
func NewCensusAPIServer(db *storage.DB) *CensusAPIServer {
return &CensusAPIServer{
db: db,
permissions: make(map[string]*PermissionChecker),
}
}
// RegisterPlugin registers a plugin's permissions for enforcement
func (s *CensusAPIServer) RegisterPlugin(pluginID string, permissions []string) error {
if err := ValidatePermissions(permissions); err != nil {
return fmt.Errorf("invalid permissions for plugin %s: %w", pluginID, err)
}
s.permissions[pluginID] = NewPermissionChecker(pluginID, permissions)
log.Printf("[CensusAPI] Registered plugin %s with permissions: %v", pluginID, permissions)
return nil
}
// UnregisterPlugin removes a plugin's permissions
func (s *CensusAPIServer) UnregisterPlugin(pluginID string) {
delete(s.permissions, pluginID)
log.Printf("[CensusAPI] Unregistered plugin %s", pluginID)
}
// checkPermission verifies a plugin has the required permission
func (s *CensusAPIServer) checkPermission(pluginID string, perm Permission) error {
checker, ok := s.permissions[pluginID]
if !ok {
return fmt.Errorf("plugin %s is not registered", pluginID)
}
return checker.Check(perm)
}
// GetContainers returns containers
func (s *CensusAPIServer) GetContainers(ctx context.Context, req *pb.GetContainersRequest) (*pb.GetContainersResponse, error) {
// Check permission
if err := s.checkPermission(req.PluginId, PermContainersRead); err != nil {
log.Printf("[CensusAPI] Permission denied: %v", err)
return nil, err
}
log.Printf("[CensusAPI] Plugin %s requesting containers (latest_only=%v, host_id=%d)",
req.PluginId, req.LatestOnly, req.HostId)
// Get containers from database
var containers []models.Container
var err error
if req.HostId > 0 {
containers, err = s.db.GetContainersByHost(req.HostId)
} else if req.LatestOnly {
containers, err = s.db.GetLatestContainers()
} else {
// For now, default to latest
containers, err = s.db.GetLatestContainers()
}
if err != nil {
return nil, fmt.Errorf("failed to get containers: %w", err)
}
// Convert to protobuf format
pbContainers := make([]*pb.Container, len(containers))
for i, c := range containers {
pbContainers[i] = containerToProto(&c)
}
return &pb.GetContainersResponse{
Containers: pbContainers,
}, nil
}
// GetContainer returns a specific container
func (s *CensusAPIServer) GetContainer(ctx context.Context, req *pb.GetContainerRequest) (*pb.GetContainerResponse, error) {
// Check permission
if err := s.checkPermission(req.PluginId, PermContainersRead); err != nil {
log.Printf("[CensusAPI] Permission denied: %v", err)
return nil, err
}
log.Printf("[CensusAPI] Plugin %s requesting container %s on host %d",
req.PluginId, req.ContainerId, req.HostId)
// For simplicity, get all containers and filter
// In production, this would be a direct query
containers, err := s.db.GetContainersByHost(req.HostId)
if err != nil {
return nil, fmt.Errorf("failed to get containers: %w", err)
}
for _, c := range containers {
if c.ID == req.ContainerId {
return &pb.GetContainerResponse{
Container: containerToProto(&c),
Found: true,
}, nil
}
}
return &pb.GetContainerResponse{
Found: false,
}, nil
}
// GetHosts returns all hosts
func (s *CensusAPIServer) GetHosts(ctx context.Context, req *pb.GetHostsRequest) (*pb.GetHostsResponse, error) {
// Check permission
if err := s.checkPermission(req.PluginId, PermHostsRead); err != nil {
log.Printf("[CensusAPI] Permission denied: %v", err)
return nil, err
}
log.Printf("[CensusAPI] Plugin %s requesting hosts", req.PluginId)
hosts, err := s.db.GetHosts()
if err != nil {
return nil, fmt.Errorf("failed to get hosts: %w", err)
}
pbHosts := make([]*pb.Host, len(hosts))
for i, h := range hosts {
pbHosts[i] = hostToProto(&h)
}
return &pb.GetHostsResponse{
Hosts: pbHosts,
}, nil
}
// GetHost returns a specific host
func (s *CensusAPIServer) GetHost(ctx context.Context, req *pb.GetHostRequest) (*pb.GetHostResponse, error) {
// Check permission
if err := s.checkPermission(req.PluginId, PermHostsRead); err != nil {
log.Printf("[CensusAPI] Permission denied: %v", err)
return nil, err
}
log.Printf("[CensusAPI] Plugin %s requesting host %d", req.PluginId, req.HostId)
host, err := s.db.GetHost(req.HostId)
if err != nil {
return nil, fmt.Errorf("failed to get host: %w", err)
}
if host == nil {
return &pb.GetHostResponse{
Found: false,
}, nil
}
return &pb.GetHostResponse{
Host: hostToProto(host),
Found: true,
}, nil
}
// GetPluginData retrieves plugin-specific data
func (s *CensusAPIServer) GetPluginData(ctx context.Context, req *pb.GetPluginDataRequest) (*pb.GetPluginDataResponse, error) {
// Check permission
if err := s.checkPermission(req.PluginId, PermStorageRead); err != nil {
log.Printf("[CensusAPI] Permission denied: %v", err)
return nil, err
}
log.Printf("[CensusAPI] Plugin %s reading data key: %s", req.PluginId, req.Key)
value, err := s.db.GetPluginData(req.PluginId, req.Key)
if err != nil {
return nil, fmt.Errorf("failed to get plugin data: %w", err)
}
if value == nil {
return &pb.GetPluginDataResponse{
Found: false,
}, nil
}
return &pb.GetPluginDataResponse{
Value: string(value),
Found: true,
}, nil
}
// SetPluginData stores plugin-specific data
func (s *CensusAPIServer) SetPluginData(ctx context.Context, req *pb.SetPluginDataRequest) (*pb.SetPluginDataResponse, error) {
// Check permission
if err := s.checkPermission(req.PluginId, PermStorageWrite); err != nil {
log.Printf("[CensusAPI] Permission denied: %v", err)
return nil, err
}
log.Printf("[CensusAPI] Plugin %s writing data key: %s", req.PluginId, req.Key)
err := s.db.SetPluginData(req.PluginId, req.Key, []byte(req.Value))
if err != nil {
return nil, fmt.Errorf("failed to set plugin data: %w", err)
}
return &pb.SetPluginDataResponse{
Success: true,
}, nil
}
// DeletePluginData deletes plugin-specific data
func (s *CensusAPIServer) DeletePluginData(ctx context.Context, req *pb.DeletePluginDataRequest) (*pb.DeletePluginDataResponse, error) {
// Check permission
if err := s.checkPermission(req.PluginId, PermStorageWrite); err != nil {
log.Printf("[CensusAPI] Permission denied: %v", err)
return nil, err
}
log.Printf("[CensusAPI] Plugin %s deleting data key: %s", req.PluginId, req.Key)
err := s.db.DeletePluginData(req.PluginId, req.Key)
if err != nil {
return nil, fmt.Errorf("failed to delete plugin data: %w", err)
}
return &pb.DeletePluginDataResponse{
Success: true,
}, nil
}
// Log receives log messages from plugins
func (s *CensusAPIServer) Log(ctx context.Context, req *pb.LogRequest) (*pb.LogResponse, error) {
// Format log message with plugin ID prefix
prefix := fmt.Sprintf("[Plugin:%s]", req.PluginId)
switch req.Level {
case "debug":
log.Printf("%s [DEBUG] %s", prefix, req.Message)
case "info":
log.Printf("%s [INFO] %s", prefix, req.Message)
case "warn":
log.Printf("%s [WARN] %s", prefix, req.Message)
case "error":
log.Printf("%s [ERROR] %s", prefix, req.Message)
default:
log.Printf("%s %s", prefix, req.Message)
}
return &pb.LogResponse{
Success: true,
}, nil
}
// SendEvent sends events to other plugins
func (s *CensusAPIServer) SendEvent(ctx context.Context, req *pb.SendEventRequest) (*pb.SendEventResponse, error) {
log.Printf("[CensusAPI] Plugin %s sending event: %s", req.PluginId, req.EventType)
// TODO: Implement event bus for inter-plugin communication
// For now, just log it
log.Printf("[CensusAPI] Event from %s: type=%s, data=%v", req.PluginId, req.EventType, req.Data)
return &pb.SendEventResponse{
Success: true,
}, nil
}
// Helper functions to convert models to protobuf
func containerToProto(c *models.Container) *pb.Container {
pbContainer := &pb.Container{
Id: c.ID,
Name: c.Name,
Image: c.Image,
ImageId: c.ImageID,
State: c.State,
Status: c.Status,
HostId: c.HostID,
HostName: c.HostName,
Created: c.Created.Format(time.RFC3339),
StartedAt: c.StartedAt.Format(time.RFC3339),
FinishedAt: "", // Not in current model
Networks: c.Networks,
Links: c.Links,
Labels: c.Labels,
Env: make(map[string]string), // Not in current model
ComposeProject: c.ComposeProject,
CpuPercent: c.CPUPercent,
MemoryUsage: c.MemoryUsage,
MemoryLimit: c.MemoryLimit,
MemoryPercent: c.MemoryPercent,
}
// Convert ports
if c.Ports != nil {
pbContainer.Ports = make([]*pb.Port, len(c.Ports))
for i, p := range c.Ports {
pbContainer.Ports[i] = &pb.Port{
ContainerPort: int32(p.PrivatePort),
HostPort: int32(p.PublicPort),
Protocol: p.Type,
HostIp: p.IP,
}
}
}
// Convert volumes
if c.Volumes != nil {
pbContainer.Volumes = make([]*pb.Volume, len(c.Volumes))
for i, v := range c.Volumes {
mode := "ro"
if v.RW {
mode = "rw"
}
pbContainer.Volumes[i] = &pb.Volume{
Type: v.Type,
Name: v.Name,
Source: v.Name, // Use name as source
Destination: v.Destination,
Mode: mode,
}
}
}
return pbContainer
}
func hostToProto(h *models.Host) *pb.Host {
lastSeen := ""
if !h.LastSeen.IsZero() {
lastSeen = h.LastSeen.Format(time.RFC3339)
}
return &pb.Host{
Id: h.ID,
Name: h.Name,
Address: h.Address,
HostType: h.HostType,
Description: h.Description,
Enabled: h.Enabled,
CollectStats: h.CollectStats,
LastSeen: lastSeen,
AgentVersion: h.AgentVersion,
AgentStatus: h.AgentStatus,
}
}

View File

@@ -1,458 +0,0 @@
package external
import (
"archive/tar"
"compress/gzip"
"context"
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"os"
"path/filepath"
"runtime"
"strings"
"time"
"github.com/container-census/container-census/internal/storage"
"gopkg.in/yaml.v3"
)
// PluginManifest represents the plugin.yaml structure
type PluginManifest struct {
ID string `yaml:"id"`
Name string `yaml:"name"`
Version string `yaml:"version"`
Description string `yaml:"description"`
Author string `yaml:"author"`
Repository string `yaml:"repository"`
MinCensusVersion string `yaml:"min_census_version"`
Permissions []string `yaml:"permissions"`
Frontend FrontendConfig `yaml:"frontend"`
Tab TabConfig `yaml:"tab"`
}
type FrontendConfig struct {
BundleURL string `yaml:"bundle_url"`
CSSURL string `yaml:"css_url"`
InitFunc string `yaml:"init_function"`
}
type TabConfig struct {
ID string `yaml:"id"`
Label string `yaml:"label"`
Icon string `yaml:"icon"`
Order int `yaml:"order"`
}
// GitHubRelease represents a GitHub release
type GitHubRelease struct {
TagName string `json:"tag_name"`
Name string `json:"name"`
Assets []struct {
Name string `json:"name"`
BrowserDownloadURL string `json:"browser_download_url"`
} `json:"assets"`
}
// PluginInstaller handles plugin installation from GitHub
type PluginInstaller struct {
db *storage.DB
pluginsDir string
httpClient *http.Client
}
// NewPluginInstaller creates a new plugin installer
func NewPluginInstaller(db *storage.DB, pluginsDir string) *PluginInstaller {
return &PluginInstaller{
db: db,
pluginsDir: pluginsDir,
httpClient: &http.Client{Timeout: 60 * time.Second},
}
}
// Install installs a plugin from a GitHub repository URL
func (i *PluginInstaller) Install(ctx context.Context, repoURL, version string) error {
log.Printf("[Installer] Installing plugin from %s (version: %s)", repoURL, version)
// Parse repository owner and name from URL
owner, repo, err := parseGitHubURL(repoURL)
if err != nil {
return fmt.Errorf("invalid GitHub URL: %w", err)
}
// Fetch plugin manifest
manifest, err := i.fetchPluginManifest(owner, repo)
if err != nil {
return fmt.Errorf("failed to fetch plugin manifest: %w", err)
}
log.Printf("[Installer] Found plugin: %s v%s", manifest.Name, manifest.Version)
// Check if plugin is already installed
existing, err := i.db.GetExternalPlugin(manifest.ID)
if err != nil {
return fmt.Errorf("failed to check existing plugin: %w", err)
}
if existing != nil {
return fmt.Errorf("plugin %s is already installed (version %s)", manifest.ID, existing.Version)
}
// Determine which version to install
targetVersion := version
if targetVersion == "" || targetVersion == "latest" {
release, err := i.getLatestRelease(owner, repo)
if err != nil {
return fmt.Errorf("failed to get latest release: %w", err)
}
targetVersion = release.TagName
}
// Create plugin directory
pluginDir := filepath.Join(i.pluginsDir, manifest.ID)
if err := os.MkdirAll(pluginDir, 0755); err != nil {
return fmt.Errorf("failed to create plugin directory: %w", err)
}
// Download and extract plugin binary
binaryPath, err := i.downloadBinary(owner, repo, targetVersion, pluginDir)
if err != nil {
os.RemoveAll(pluginDir) // Clean up on error
return fmt.Errorf("failed to download plugin binary: %w", err)
}
// Download frontend assets if specified
frontendDir := filepath.Join(pluginDir, "frontend")
if manifest.Frontend.BundleURL != "" {
if err := os.MkdirAll(frontendDir, 0755); err != nil {
os.RemoveAll(pluginDir)
return fmt.Errorf("failed to create frontend directory: %w", err)
}
if err := i.downloadFrontendAssets(owner, repo, targetVersion, frontendDir); err != nil {
os.RemoveAll(pluginDir)
return fmt.Errorf("failed to download frontend assets: %w", err)
}
}
// Save plugin record to database
// Marshal tab config to JSON
tabConfigJSON := ""
if manifest.Tab.ID != "" {
tabBytes, err := json.Marshal(manifest.Tab)
if err != nil {
log.Printf("[Installer] Warning: failed to marshal tab config: %v", err)
} else {
tabConfigJSON = string(tabBytes)
}
}
record := &storage.ExternalPluginRecord{
PluginRecord: storage.PluginRecord{
ID: manifest.ID,
Name: manifest.Name,
Version: targetVersion,
SourceType: "github",
SourceURL: repoURL,
Enabled: true,
InstalledAt: time.Now(),
UpdatedAt: time.Now(),
TabConfig: tabConfigJSON,
},
BinaryPath: binaryPath,
Permissions: manifest.Permissions,
FrontendBundle: filepath.Join(frontendDir, "bundle.js"),
FrontendCSS: filepath.Join(frontendDir, "bundle.css"),
ProcessStatus: "stopped",
}
if err := i.db.SaveExternalPlugin(record); err != nil {
os.RemoveAll(pluginDir)
return fmt.Errorf("failed to save plugin record: %w", err)
}
log.Printf("[Installer] Successfully installed plugin %s v%s", manifest.ID, targetVersion)
return nil
}
// Update updates a plugin to the latest version
func (i *PluginInstaller) Update(ctx context.Context, pluginID string) error {
log.Printf("[Installer] Updating plugin %s", pluginID)
// Get existing plugin
existing, err := i.db.GetExternalPlugin(pluginID)
if err != nil {
return fmt.Errorf("failed to get plugin: %w", err)
}
if existing == nil {
return fmt.Errorf("plugin %s not found", pluginID)
}
// Uninstall old version
if err := i.Uninstall(pluginID); err != nil {
return fmt.Errorf("failed to uninstall old version: %w", err)
}
// Reinstall latest version
return i.Install(ctx, existing.SourceURL, "latest")
}
// Uninstall removes a plugin
func (i *PluginInstaller) Uninstall(pluginID string) error {
log.Printf("[Installer] Uninstalling plugin %s", pluginID)
// Get plugin record
plugin, err := i.db.GetExternalPlugin(pluginID)
if err != nil {
return fmt.Errorf("failed to get plugin: %w", err)
}
if plugin == nil {
return fmt.Errorf("plugin %s not found", pluginID)
}
// Remove plugin directory
pluginDir := filepath.Join(i.pluginsDir, pluginID)
if err := os.RemoveAll(pluginDir); err != nil {
log.Printf("[Installer] Warning: failed to remove plugin directory: %v", err)
}
// Delete from database
if err := i.db.DeletePlugin(pluginID); err != nil {
return fmt.Errorf("failed to delete plugin from database: %w", err)
}
log.Printf("[Installer] Successfully uninstalled plugin %s", pluginID)
return nil
}
// fetchPluginManifest fetches the plugin.yaml from GitHub
func (i *PluginInstaller) fetchPluginManifest(owner, repo string) (*PluginManifest, error) {
// Fetch raw plugin.yaml from main branch
url := fmt.Sprintf("https://raw.githubusercontent.com/%s/%s/main/plugin.yaml", owner, repo)
resp, err := i.httpClient.Get(url)
if err != nil {
return nil, fmt.Errorf("failed to fetch manifest: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("failed to fetch manifest: HTTP %d", resp.StatusCode)
}
var manifest PluginManifest
if err := yaml.NewDecoder(resp.Body).Decode(&manifest); err != nil {
return nil, fmt.Errorf("failed to parse manifest: %w", err)
}
return &manifest, nil
}
// getLatestRelease gets the latest GitHub release
func (i *PluginInstaller) getLatestRelease(owner, repo string) (*GitHubRelease, error) {
url := fmt.Sprintf("https://api.github.com/repos/%s/%s/releases/latest", owner, repo)
resp, err := i.httpClient.Get(url)
if err != nil {
return nil, fmt.Errorf("failed to fetch releases: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("failed to fetch releases: HTTP %d", resp.StatusCode)
}
var release GitHubRelease
if err := json.NewDecoder(resp.Body).Decode(&release); err != nil {
return nil, fmt.Errorf("failed to parse release: %w", err)
}
return &release, nil
}
// downloadBinary downloads the plugin binary for the current platform
func (i *PluginInstaller) downloadBinary(owner, repo, version, destDir string) (string, error) {
// Determine platform-specific binary name (use hyphen to match common naming convention)
platform := fmt.Sprintf("%s-%s", runtime.GOOS, runtime.GOARCH)
// Get release
url := fmt.Sprintf("https://api.github.com/repos/%s/%s/releases/tags/%s", owner, repo, version)
resp, err := i.httpClient.Get(url)
if err != nil {
return "", fmt.Errorf("failed to fetch release: %w", err)
}
defer resp.Body.Close()
var release GitHubRelease
if err := json.NewDecoder(resp.Body).Decode(&release); err != nil {
return "", fmt.Errorf("failed to parse release: %w", err)
}
// Find matching asset
var downloadURL string
for _, asset := range release.Assets {
if strings.Contains(asset.Name, platform) {
downloadURL = asset.BrowserDownloadURL
break
}
}
if downloadURL == "" {
return "", fmt.Errorf("no binary found for platform %s", platform)
}
// Download binary
log.Printf("[Installer] Downloading binary from %s", downloadURL)
resp, err = i.httpClient.Get(downloadURL)
if err != nil {
return "", fmt.Errorf("failed to download binary: %w", err)
}
defer resp.Body.Close()
// Save to file
binaryPath := filepath.Join(destDir, "plugin")
file, err := os.OpenFile(binaryPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0755)
if err != nil {
return "", fmt.Errorf("failed to create binary file: %w", err)
}
defer file.Close()
if _, err := io.Copy(file, resp.Body); err != nil {
return "", fmt.Errorf("failed to write binary: %w", err)
}
log.Printf("[Installer] Binary saved to %s", binaryPath)
return binaryPath, nil
}
// downloadFrontendAssets downloads frontend bundle and CSS
func (i *PluginInstaller) downloadFrontendAssets(owner, repo, version, destDir string) error {
// Get release to find frontend assets
url := fmt.Sprintf("https://api.github.com/repos/%s/%s/releases/tags/%s", owner, repo, version)
resp, err := i.httpClient.Get(url)
if err != nil {
return fmt.Errorf("failed to fetch release: %w", err)
}
defer resp.Body.Close()
var release GitHubRelease
if err := json.NewDecoder(resp.Body).Decode(&release); err != nil {
return fmt.Errorf("failed to parse release: %w", err)
}
// Download bundle.js if present
for _, asset := range release.Assets {
if asset.Name == "bundle.js" {
log.Printf("[Installer] Downloading frontend bundle from %s", asset.BrowserDownloadURL)
if err := i.downloadFile(asset.BrowserDownloadURL, filepath.Join(destDir, "bundle.js")); err != nil {
return fmt.Errorf("failed to download JS bundle: %w", err)
}
}
// Download bundle.css if present
if asset.Name == "bundle.css" {
log.Printf("[Installer] Downloading frontend CSS from %s", asset.BrowserDownloadURL)
if err := i.downloadFile(asset.BrowserDownloadURL, filepath.Join(destDir, "bundle.css")); err != nil {
return fmt.Errorf("failed to download CSS: %w", err)
}
}
}
return nil
}
// downloadFile downloads a file from a URL
func (i *PluginInstaller) downloadFile(url, destPath string) error {
log.Printf("[Installer] Downloading %s to %s", url, destPath)
resp, err := i.httpClient.Get(url)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("HTTP %d", resp.StatusCode)
}
file, err := os.Create(destPath)
if err != nil {
return err
}
defer file.Close()
if _, err := io.Copy(file, resp.Body); err != nil {
return err
}
return nil
}
// extractTarGz extracts a .tar.gz archive
func (i *PluginInstaller) extractTarGz(archivePath, destDir string) error {
file, err := os.Open(archivePath)
if err != nil {
return err
}
defer file.Close()
gzr, err := gzip.NewReader(file)
if err != nil {
return err
}
defer gzr.Close()
tr := tar.NewReader(gzr)
for {
header, err := tr.Next()
if err == io.EOF {
break
}
if err != nil {
return err
}
target := filepath.Join(destDir, header.Name)
switch header.Typeflag {
case tar.TypeDir:
if err := os.MkdirAll(target, 0755); err != nil {
return err
}
case tar.TypeReg:
file, err := os.OpenFile(target, os.O_CREATE|os.O_RDWR, os.FileMode(header.Mode))
if err != nil {
return err
}
if _, err := io.Copy(file, tr); err != nil {
file.Close()
return err
}
file.Close()
}
}
return nil
}
// parseGitHubURL extracts owner and repo from a GitHub URL
func parseGitHubURL(url string) (owner, repo string, err error) {
// Handle various GitHub URL formats:
// https://github.com/owner/repo
// https://github.com/owner/repo.git
// github.com/owner/repo
url = strings.TrimPrefix(url, "https://")
url = strings.TrimPrefix(url, "http://")
url = strings.TrimPrefix(url, "www.")
url = strings.TrimSuffix(url, ".git")
url = strings.TrimSuffix(url, "/")
parts := strings.Split(url, "/")
if len(parts) < 3 || parts[0] != "github.com" {
return "", "", fmt.Errorf("invalid GitHub URL format")
}
return parts[1], parts[2], nil
}

View File

@@ -1,122 +0,0 @@
package external
import (
"fmt"
"strings"
)
// Permission represents a plugin permission
type Permission string
const (
// Data access permissions
PermContainersRead Permission = "containers:read"
PermHostsRead Permission = "hosts:read"
PermStorageRead Permission = "storage:read"
PermStorageWrite Permission = "storage:write"
// API permissions
PermAPIRoutes Permission = "api:routes"
PermAPIEvents Permission = "api:events"
// UI permissions
PermUITab Permission = "ui:tab"
PermUIBadge Permission = "ui:badge"
PermUIEnrich Permission = "ui:enrich"
)
// PermissionChecker validates plugin permissions
type PermissionChecker struct {
pluginID string
permissions map[Permission]bool
}
// NewPermissionChecker creates a permission checker for a plugin
func NewPermissionChecker(pluginID string, permissions []string) *PermissionChecker {
permMap := make(map[Permission]bool)
for _, p := range permissions {
permMap[Permission(p)] = true
}
return &PermissionChecker{
pluginID: pluginID,
permissions: permMap,
}
}
// Check verifies if a plugin has a specific permission
func (p *PermissionChecker) Check(permission Permission) error {
if !p.permissions[permission] {
return fmt.Errorf("plugin %s does not have permission: %s", p.pluginID, permission)
}
return nil
}
// CheckAny verifies if a plugin has any of the specified permissions
func (p *PermissionChecker) CheckAny(permissions ...Permission) error {
for _, perm := range permissions {
if p.permissions[perm] {
return nil
}
}
return fmt.Errorf("plugin %s does not have any of the required permissions: %v", p.pluginID, permissions)
}
// Has returns true if the plugin has the specified permission
func (p *PermissionChecker) Has(permission Permission) bool {
return p.permissions[permission]
}
// CanAccessEndpoint checks if a plugin can access a specific API endpoint
func (p *PermissionChecker) CanAccessEndpoint(method, path string) error {
// Plugin routes require api:routes permission
if strings.HasPrefix(path, "/api/p/") {
return p.Check(PermAPIRoutes)
}
// Census API callbacks are checked individually in the gRPC server
return nil
}
// ValidatePermissions validates that all requested permissions are known
func ValidatePermissions(permissions []string) error {
knownPerms := map[string]bool{
string(PermContainersRead): true,
string(PermHostsRead): true,
string(PermStorageRead): true,
string(PermStorageWrite): true,
string(PermAPIRoutes): true,
string(PermAPIEvents): true,
string(PermUITab): true,
string(PermUIBadge): true,
string(PermUIEnrich): true,
}
for _, p := range permissions {
if !knownPerms[p] {
return fmt.Errorf("unknown permission: %s", p)
}
}
return nil
}
// GetPermissionDescription returns a human-readable description
func GetPermissionDescription(perm Permission) string {
descriptions := map[Permission]string{
PermContainersRead: "Read container data from all hosts",
PermHostsRead: "Read host configuration and metadata",
PermStorageRead: "Read plugin-specific data from storage",
PermStorageWrite: "Write plugin-specific data to storage",
PermAPIRoutes: "Register custom HTTP API routes",
PermAPIEvents: "Subscribe to system events",
PermUITab: "Add a custom UI tab to the interface",
PermUIBadge: "Display badges on container cards",
PermUIEnrich: "Add custom data to container details",
}
if desc, ok := descriptions[perm]; ok {
return desc
}
return "Unknown permission"
}

View File

@@ -1,486 +0,0 @@
package external
import (
"context"
"fmt"
"log"
"os"
"os/exec"
"sync"
"time"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
pb "github.com/container-census/container-census/internal/plugins/proto"
"github.com/container-census/container-census/internal/storage"
)
// PluginStatus represents the current state of a plugin process
type PluginStatus struct {
PluginID string
ProcessStatus string // "starting", "running", "stopping", "stopped", "failed"
HealthStatus string // "healthy", "unhealthy", "unknown"
GRPCPort int
RestartCount int
LastRestart time.Time
Error string
}
// PluginProcess represents a running plugin process
type PluginProcess struct {
PluginID string
Cmd *exec.Cmd
GRPCClient pb.PluginClient
GRPCConn *grpc.ClientConn
GRPCPort int
Status string
HealthStatus string
RestartCount int
LastRestart time.Time
CancelFunc context.CancelFunc
StdoutLog *CircularLog
StderrLog *CircularLog
}
// CircularLog stores recent log lines
type CircularLog struct {
mu sync.Mutex
lines []string
maxSize int
index int
}
func NewCircularLog(maxSize int) *CircularLog {
return &CircularLog{
lines: make([]string, 0, maxSize),
maxSize: maxSize,
}
}
func (cl *CircularLog) Add(line string) {
cl.mu.Lock()
defer cl.mu.Unlock()
if len(cl.lines) < cl.maxSize {
cl.lines = append(cl.lines, line)
} else {
cl.lines[cl.index] = line
cl.index = (cl.index + 1) % cl.maxSize
}
}
func (cl *CircularLog) GetLines() []string {
cl.mu.Lock()
defer cl.mu.Unlock()
if len(cl.lines) < cl.maxSize {
return append([]string(nil), cl.lines...)
}
// Rotate to get chronological order
result := make([]string, len(cl.lines))
for i := 0; i < len(cl.lines); i++ {
result[i] = cl.lines[(cl.index+i)%cl.maxSize]
}
return result
}
// ExternalPluginSupervisor manages external plugin processes
type ExternalPluginSupervisor struct {
mu sync.RWMutex
db *storage.DB
processes map[string]*PluginProcess
censusAPIAddress string // gRPC address for Census API
censusAPIServer *grpc.Server
basePort int // Starting port for plugin gRPC servers
portCounter int
healthCheckPeriod time.Duration
maxRestarts int
}
// NewExternalPluginSupervisor creates a new supervisor
func NewExternalPluginSupervisor(db *storage.DB, censusAPIAddress string, basePort int) *ExternalPluginSupervisor {
return &ExternalPluginSupervisor{
db: db,
processes: make(map[string]*PluginProcess),
censusAPIAddress: censusAPIAddress,
basePort: basePort,
portCounter: 0,
healthCheckPeriod: 10 * time.Second,
maxRestarts: 3,
}
}
// StartPlugin starts a plugin process
func (s *ExternalPluginSupervisor) StartPlugin(ctx context.Context, pluginID string) error {
s.mu.Lock()
defer s.mu.Unlock()
// Check if already running
if proc, exists := s.processes[pluginID]; exists {
if proc.Status == "running" {
return fmt.Errorf("plugin %s is already running", pluginID)
}
// Clean up old process
s.stopPluginLocked(pluginID)
}
// Load plugin metadata from database
plugin, err := s.db.GetExternalPlugin(pluginID)
if err != nil {
return fmt.Errorf("failed to load plugin metadata: %w", err)
}
if plugin.BinaryPath == "" {
return fmt.Errorf("plugin binary path not set")
}
// Allocate gRPC port
grpcPort := s.basePort + s.portCounter
s.portCounter++
// Create process context
procCtx, cancel := context.WithCancel(ctx)
// Create command
cmd := exec.CommandContext(procCtx, plugin.BinaryPath)
cmd.Env = append(os.Environ(),
fmt.Sprintf("PLUGIN_ID=%s", pluginID),
fmt.Sprintf("GRPC_PORT=%d", grpcPort),
fmt.Sprintf("CENSUS_API=%s", s.censusAPIAddress),
)
// Create circular logs
stdoutLog := NewCircularLog(100)
stderrLog := NewCircularLog(100)
// Capture stdout/stderr
stdout, err := cmd.StdoutPipe()
if err != nil {
cancel()
return fmt.Errorf("failed to create stdout pipe: %w", err)
}
stderr, err := cmd.StderrPipe()
if err != nil {
cancel()
return fmt.Errorf("failed to create stderr pipe: %w", err)
}
// Start process
if err := cmd.Start(); err != nil {
cancel()
return fmt.Errorf("failed to start plugin process: %w", err)
}
// Create process record
process := &PluginProcess{
PluginID: pluginID,
Cmd: cmd,
GRPCPort: grpcPort,
Status: "starting",
HealthStatus: "unknown",
RestartCount: 0,
CancelFunc: cancel,
StdoutLog: stdoutLog,
StderrLog: stderrLog,
}
s.processes[pluginID] = process
log.Printf("[Supervisor] Started plugin %s on port %d (PID: %d)", pluginID, grpcPort, cmd.Process.Pid)
// Start log readers
go s.readLogLines(pluginID, stdout, stdoutLog, "stdout")
go s.readLogLines(pluginID, stderr, stderrLog, "stderr")
// Wait for gRPC server to be ready
go s.waitForGRPCReady(procCtx, pluginID, grpcPort)
// Start monitoring
go s.monitorProcess(procCtx, pluginID)
return nil
}
// waitForGRPCReady waits for the plugin's gRPC server to be ready
func (s *ExternalPluginSupervisor) waitForGRPCReady(ctx context.Context, pluginID string, port int) {
timeout := 30 * time.Second
deadline := time.Now().Add(timeout)
for time.Now().Before(deadline) {
select {
case <-ctx.Done():
return
default:
}
// Try to connect
conn, err := grpc.NewClient(
fmt.Sprintf("localhost:%d", port),
grpc.WithTransportCredentials(insecure.NewCredentials()),
)
if err != nil {
time.Sleep(500 * time.Millisecond)
continue
}
client := pb.NewPluginClient(conn)
// Try Init RPC
initReq := &pb.InitRequest{
PluginId: pluginID,
CensusApiAddress: s.censusAPIAddress,
CensusVersion: "1.0.0", // TODO: Get from version package
}
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
resp, err := client.Init(ctx, initReq)
cancel()
if err == nil && resp.Success {
s.mu.Lock()
if proc, exists := s.processes[pluginID]; exists {
proc.GRPCClient = client
proc.GRPCConn = conn
proc.Status = "running"
proc.HealthStatus = "healthy"
}
s.mu.Unlock()
log.Printf("[Supervisor] Plugin %s initialized successfully", pluginID)
return
}
conn.Close()
time.Sleep(500 * time.Millisecond)
}
// Timeout
log.Printf("[Supervisor] Plugin %s failed to initialize within %v", pluginID, timeout)
s.mu.Lock()
if proc, exists := s.processes[pluginID]; exists {
proc.Status = "failed"
proc.HealthStatus = "unhealthy"
}
s.mu.Unlock()
}
// readLogLines reads log lines from reader
func (s *ExternalPluginSupervisor) readLogLines(pluginID string, reader interface{}, log *CircularLog, stream string) {
// Implementation would use bufio.Scanner to read lines
// For brevity, simplified here
}
// monitorProcess monitors plugin health and handles restarts
func (s *ExternalPluginSupervisor) monitorProcess(ctx context.Context, pluginID string) {
ticker := time.NewTicker(s.healthCheckPeriod)
defer ticker.Stop()
for {
select {
case <-ctx.Done():
return
case <-ticker.C:
s.performHealthCheck(ctx, pluginID)
}
}
}
// performHealthCheck checks plugin health
func (s *ExternalPluginSupervisor) performHealthCheck(ctx context.Context, pluginID string) {
s.mu.RLock()
proc, exists := s.processes[pluginID]
s.mu.RUnlock()
if !exists || proc.Status != "running" {
return
}
// Check if process is still alive
if proc.Cmd.ProcessState != nil && proc.Cmd.ProcessState.Exited() {
log.Printf("[Supervisor] Plugin %s process exited unexpectedly", pluginID)
s.handleProcessFailure(ctx, pluginID)
return
}
// Perform gRPC healthcheck
if proc.GRPCClient != nil {
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
resp, err := proc.GRPCClient.Healthcheck(ctx, &pb.HealthcheckRequest{
PluginId: pluginID,
})
s.mu.Lock()
if err != nil || !resp.Healthy {
proc.HealthStatus = "unhealthy"
log.Printf("[Supervisor] Plugin %s health check failed: %v", pluginID, err)
} else {
proc.HealthStatus = "healthy"
}
s.mu.Unlock()
}
}
// handleProcessFailure handles plugin process failures
func (s *ExternalPluginSupervisor) handleProcessFailure(ctx context.Context, pluginID string) {
s.mu.Lock()
proc, exists := s.processes[pluginID]
if !exists {
s.mu.Unlock()
return
}
proc.Status = "failed"
proc.RestartCount++
proc.LastRestart = time.Now()
shouldRestart := proc.RestartCount <= s.maxRestarts
s.mu.Unlock()
if shouldRestart {
log.Printf("[Supervisor] Attempting to restart plugin %s (attempt %d/%d)",
pluginID, proc.RestartCount, s.maxRestarts)
// Wait before restart
time.Sleep(2 * time.Second)
// Restart
if err := s.StartPlugin(ctx, pluginID); err != nil {
log.Printf("[Supervisor] Failed to restart plugin %s: %v", pluginID, err)
}
} else {
log.Printf("[Supervisor] Plugin %s exceeded max restart attempts (%d), giving up",
pluginID, s.maxRestarts)
// Disable plugin in database
if err := s.db.SetPluginEnabled(pluginID, false); err != nil {
log.Printf("[Supervisor] Failed to disable plugin %s: %v", pluginID, err)
}
}
}
// StopPlugin gracefully stops a plugin
func (s *ExternalPluginSupervisor) StopPlugin(pluginID string) error {
s.mu.Lock()
defer s.mu.Unlock()
return s.stopPluginLocked(pluginID)
}
func (s *ExternalPluginSupervisor) stopPluginLocked(pluginID string) error {
proc, exists := s.processes[pluginID]
if !exists {
return fmt.Errorf("plugin %s is not running", pluginID)
}
log.Printf("[Supervisor] Stopping plugin %s", pluginID)
proc.Status = "stopping"
// Call Stop RPC if available
if proc.GRPCClient != nil {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
proc.GRPCClient.Stop(ctx, &pb.StopRequest{PluginId: pluginID})
}
// Close gRPC connection
if proc.GRPCConn != nil {
proc.GRPCConn.Close()
}
// Cancel context to stop monitors
if proc.CancelFunc != nil {
proc.CancelFunc()
}
// Kill process if still running
if proc.Cmd.Process != nil && proc.Cmd.ProcessState == nil {
proc.Cmd.Process.Kill()
}
proc.Status = "stopped"
delete(s.processes, pluginID)
log.Printf("[Supervisor] Plugin %s stopped", pluginID)
return nil
}
// RestartPlugin restarts a plugin
func (s *ExternalPluginSupervisor) RestartPlugin(ctx context.Context, pluginID string) error {
if err := s.StopPlugin(pluginID); err != nil {
log.Printf("[Supervisor] Error stopping plugin %s for restart: %v", pluginID, err)
}
time.Sleep(1 * time.Second)
return s.StartPlugin(ctx, pluginID)
}
// GetPluginStatus returns the status of a plugin
func (s *ExternalPluginSupervisor) GetPluginStatus(pluginID string) (PluginStatus, error) {
s.mu.RLock()
defer s.mu.RUnlock()
proc, exists := s.processes[pluginID]
if !exists {
return PluginStatus{
PluginID: pluginID,
ProcessStatus: "stopped",
HealthStatus: "unknown",
}, nil
}
return PluginStatus{
PluginID: proc.PluginID,
ProcessStatus: proc.Status,
HealthStatus: proc.HealthStatus,
GRPCPort: proc.GRPCPort,
RestartCount: proc.RestartCount,
LastRestart: proc.LastRestart,
}, nil
}
// GetPluginLogs returns recent log lines
func (s *ExternalPluginSupervisor) GetPluginLogs(pluginID string) (stdout, stderr []string, err error) {
s.mu.RLock()
defer s.mu.RUnlock()
proc, exists := s.processes[pluginID]
if !exists {
return nil, nil, fmt.Errorf("plugin %s is not running", pluginID)
}
return proc.StdoutLog.GetLines(), proc.StderrLog.GetLines(), nil
}
// GetGRPCClient returns the gRPC client for a plugin
func (s *ExternalPluginSupervisor) GetGRPCClient(pluginID string) (pb.PluginClient, error) {
s.mu.RLock()
defer s.mu.RUnlock()
proc, exists := s.processes[pluginID]
if !exists {
return nil, fmt.Errorf("plugin %s is not running", pluginID)
}
if proc.GRPCClient == nil {
return nil, fmt.Errorf("plugin %s gRPC client not ready", pluginID)
}
return proc.GRPCClient, nil
}
// StopAll stops all running plugins
func (s *ExternalPluginSupervisor) StopAll() {
s.mu.Lock()
defer s.mu.Unlock()
log.Printf("[Supervisor] Stopping all plugins...")
for pluginID := range s.processes {
s.stopPluginLocked(pluginID)
}
}

View File

@@ -4,18 +4,14 @@ import (
"context"
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"os"
"sort"
"strconv"
"sync"
"time"
"github.com/container-census/container-census/internal/models"
"github.com/container-census/container-census/internal/plugins/external"
pb "github.com/container-census/container-census/internal/plugins/proto"
"github.com/container-census/container-census/internal/storage"
"github.com/gorilla/mux"
)
@@ -32,12 +28,6 @@ type Manager struct {
eventBus *EventBusImpl
router *mux.Router
started bool
// External plugin support
installer *external.PluginInstaller
supervisor *external.ExternalPluginSupervisor
censusAPIServer *external.CensusAPIServer
pluginsDir string
}
// PluginFactory creates a new plugin instance
@@ -45,15 +35,6 @@ type PluginFactory func() Plugin
// NewManager creates a new plugin manager
func NewManager(db *storage.DB, containers ContainerProvider, hosts HostProvider) *Manager {
// Default to /app/data/plugins, but use ./data/plugins for local development
pluginsDir := "/app/data/plugins"
if dataDir := os.Getenv("DATA_DIR"); dataDir != "" {
pluginsDir = dataDir + "/plugins"
} else if _, err := os.Stat("/app/data"); os.IsNotExist(err) {
// Running locally, not in Docker container
pluginsDir = "./data/plugins"
}
return &Manager{
plugins: make(map[string]Plugin),
pluginOrder: make([]string, 0),
@@ -62,12 +43,6 @@ func NewManager(db *storage.DB, containers ContainerProvider, hosts HostProvider
containers: containers,
hosts: hosts,
eventBus: NewEventBus(),
// External plugin infrastructure
installer: external.NewPluginInstaller(db, pluginsDir),
supervisor: external.NewExternalPluginSupervisor(db, "localhost:50052", 50100),
censusAPIServer: external.NewCensusAPIServer(db),
pluginsDir: pluginsDir,
}
}
@@ -78,13 +53,6 @@ func (m *Manager) SetRouter(router *mux.Router) {
m.router = router
}
// GetCensusAPIServer returns the Census API server for plugin callbacks
func (m *Manager) GetCensusAPIServer() *external.CensusAPIServer {
m.mu.RLock()
defer m.mu.RUnlock()
return m.censusAPIServer
}
// RegisterBuiltIn registers a built-in plugin factory
func (m *Manager) RegisterBuiltIn(id string, factory PluginFactory) {
m.mu.Lock()
@@ -120,68 +88,6 @@ func (m *Manager) LoadBuiltInPlugins(ctx context.Context) error {
return nil
}
// LoadExternalPlugins loads and starts all enabled external plugins from database
func (m *Manager) LoadExternalPlugins(ctx context.Context) error {
// Get all plugin records from database
records, err := m.db.GetAllPlugins()
if err != nil {
return fmt.Errorf("failed to get plugins from database: %w", err)
}
// Filter for enabled external plugins
for _, record := range records {
// Skip built-in plugins (they're loaded via LoadBuiltInPlugins)
if record.SourceType == "built_in" {
continue
}
// Skip disabled plugins
if !record.Enabled {
log.Printf("Skipping disabled external plugin: %s", record.ID)
continue
}
// Start the external plugin
log.Printf("Loading external plugin: %s v%s", record.Name, record.Version)
if err := m.StartExternalPlugin(ctx, record.ID); err != nil {
log.Printf("Failed to start external plugin %s: %v", record.ID, err)
continue
}
// Mount routes for the plugin with retry (gRPC client needs time to connect)
mounted := false
for attempt := 1; attempt <= 5; attempt++ {
if attempt > 1 {
time.Sleep(time.Duration(attempt) * 500 * time.Millisecond)
}
if err := m.MountPluginRoutes(record.ID); err != nil {
if attempt < 5 {
log.Printf("[PluginManager] Attempt %d/5: Failed to mount routes for %s, retrying...", attempt, record.ID)
continue
}
log.Printf("Failed to mount routes for plugin %s after 5 attempts: %v", record.ID, err)
} else {
mounted = true
break
}
}
if !mounted {
log.Printf("Warning: Plugin %s started but routes not mounted", record.ID)
} else {
// Fetch and save tab configuration after successful route mounting
if err := m.FetchAndSaveTabConfig(ctx, record.ID); err != nil {
log.Printf("Warning: Failed to fetch tab config for %s: %v", record.ID, err)
// Continue anyway - tab can be fetched later if needed
}
}
log.Printf("Loaded external plugin: %s v%s", record.Name, record.Version)
}
return nil
}
// loadPlugin initializes and registers a plugin
func (m *Manager) loadPlugin(ctx context.Context, plugin Plugin) error {
info := plugin.Info()
@@ -630,296 +536,3 @@ func (s *scopedPluginDB) SetSetting(key string, value string) error {
func (s *scopedPluginDB) GetAllSettings() (map[string]string, error) {
return s.db.GetAllPluginSettings(s.pluginID)
}
// External Plugin Management Methods
// InstallExternalPlugin installs a plugin from a GitHub repository URL
func (m *Manager) InstallExternalPlugin(ctx context.Context, repoURL, version string) error {
log.Printf("[PluginManager] Installing external plugin from %s", repoURL)
// Install the plugin files and save to database
if err := m.installer.Install(ctx, repoURL, version); err != nil {
return err
}
// Get the plugin ID from the installer
// The plugin ID is extracted during install, we need to get the plugin record
plugins, err := m.db.GetAllPlugins()
if err != nil {
return fmt.Errorf("failed to get plugins after install: %w", err)
}
// Find the newly installed plugin (it will be enabled and from the repo URL)
var pluginID string
for _, p := range plugins {
if p.SourceURL == repoURL && p.Enabled {
pluginID = p.ID
break
}
}
if pluginID == "" {
return fmt.Errorf("could not find installed plugin from %s", repoURL)
}
log.Printf("[PluginManager] Starting installed plugin %s", pluginID)
// Start the plugin process
if err := m.StartExternalPlugin(ctx, pluginID); err != nil {
log.Printf("[PluginManager] Failed to start plugin %s: %v", pluginID, err)
return fmt.Errorf("plugin installed but failed to start: %w", err)
}
// Mount routes with retry
mounted := false
for attempt := 1; attempt <= 5; attempt++ {
if attempt > 1 {
time.Sleep(time.Duration(attempt) * 500 * time.Millisecond)
}
if err := m.MountPluginRoutes(pluginID); err != nil {
if attempt < 5 {
log.Printf("[PluginManager] Attempt %d/5: Failed to mount routes for %s, retrying...", attempt, pluginID)
continue
}
log.Printf("Failed to mount routes for plugin %s after 5 attempts: %v", pluginID, err)
} else {
mounted = true
break
}
}
if mounted {
// Fetch and save tab configuration
if err := m.FetchAndSaveTabConfig(ctx, pluginID); err != nil {
log.Printf("Warning: Failed to fetch tab config for %s: %v", pluginID, err)
}
}
return nil
}
// UpdateExternalPlugin updates an external plugin to the latest version
func (m *Manager) UpdateExternalPlugin(ctx context.Context, pluginID string) error {
log.Printf("[PluginManager] Updating external plugin %s", pluginID)
return m.installer.Update(ctx, pluginID)
}
// UninstallExternalPlugin removes an external plugin
func (m *Manager) UninstallExternalPlugin(pluginID string) error {
log.Printf("[PluginManager] Uninstalling external plugin %s", pluginID)
// Stop plugin process if running
if err := m.supervisor.StopPlugin(pluginID); err != nil {
log.Printf("[PluginManager] Warning: failed to stop plugin %s: %v", pluginID, err)
}
// Unregister from Census API
m.censusAPIServer.UnregisterPlugin(pluginID)
// Remove plugin files and database record
return m.installer.Uninstall(pluginID)
}
// GetExternalPluginLogs returns recent log output from a plugin process
func (m *Manager) GetExternalPluginLogs(pluginID string) (stdout, stderr []string, err error) {
return m.supervisor.GetPluginLogs(pluginID)
}
// GetExternalPluginStatus returns the runtime status of an external plugin
func (m *Manager) GetExternalPluginStatus(pluginID string) (external.PluginStatus, error) {
return m.supervisor.GetPluginStatus(pluginID)
}
// StartExternalPlugin starts an external plugin process
func (m *Manager) StartExternalPlugin(ctx context.Context, pluginID string) error {
log.Printf("[PluginManager] Starting external plugin %s", pluginID)
// Get plugin metadata
plugin, err := m.db.GetExternalPlugin(pluginID)
if err != nil {
return fmt.Errorf("failed to get plugin metadata: %w", err)
}
// Register permissions with Census API
if err := m.censusAPIServer.RegisterPlugin(pluginID, plugin.Permissions); err != nil {
return fmt.Errorf("failed to register plugin permissions: %w", err)
}
// Start plugin process
if err := m.supervisor.StartPlugin(ctx, pluginID); err != nil {
m.censusAPIServer.UnregisterPlugin(pluginID)
return fmt.Errorf("failed to start plugin process: %w", err)
}
return nil
}
// StopExternalPlugin stops an external plugin process
func (m *Manager) StopExternalPlugin(pluginID string) error {
log.Printf("[PluginManager] Stopping external plugin %s", pluginID)
// Stop plugin process
if err := m.supervisor.StopPlugin(pluginID); err != nil {
return err
}
// Unregister from Census API
m.censusAPIServer.UnregisterPlugin(pluginID)
return nil
}
// FetchAndSaveTabConfig retrieves tab configuration from a plugin via gRPC and saves it to the database
func (m *Manager) FetchAndSaveTabConfig(ctx context.Context, pluginID string) error {
log.Printf("[PluginManager] Fetching tab config for plugin %s", pluginID)
// Get the gRPC client from supervisor
client, err := m.supervisor.GetGRPCClient(pluginID)
if err != nil {
return fmt.Errorf("failed to get plugin gRPC client: %w", err)
}
// Call GetTab via gRPC with timeout
ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
defer cancel()
resp, err := client.GetTab(ctx, &pb.TabRequest{
PluginId: pluginID,
})
if err != nil {
return fmt.Errorf("failed to call GetTab: %w", err)
}
// If plugin doesn't have a tab, skip
if !resp.HasTab {
log.Printf("[PluginManager] Plugin %s does not provide a tab", pluginID)
return nil
}
// Create TabDefinition structure
tabDef := TabDefinition{
ID: resp.Id,
Label: resp.Label,
Icon: resp.Icon,
Order: int(resp.Order),
ScriptURL: resp.ScriptUrl,
InitFunc: resp.InitFunc,
}
// Get existing plugin record
plugin, err := m.db.GetExternalPlugin(pluginID)
if err != nil {
return fmt.Errorf("failed to get plugin record: %w", err)
}
// Update tab_config field as map[string]string
plugin.TabConfig = map[string]string{
"id": tabDef.ID,
"label": tabDef.Label,
"icon": tabDef.Icon,
"order": fmt.Sprintf("%d", tabDef.Order),
"script_url": tabDef.ScriptURL,
"init_func": tabDef.InitFunc,
}
// Save back to database
if err := m.db.SaveExternalPlugin(plugin); err != nil {
return fmt.Errorf("failed to save tab config: %w", err)
}
log.Printf("[PluginManager] Successfully saved tab config for %s: %s", pluginID, tabDef.Label)
return nil
}
// MountPluginRoutes dynamically mounts routes for an external plugin
func (m *Manager) MountPluginRoutes(pluginID string) error {
m.mu.Lock()
defer m.mu.Unlock()
if m.router == nil {
return fmt.Errorf("router not set")
}
// Get gRPC client for the plugin
client, err := m.supervisor.GetGRPCClient(pluginID)
if err != nil {
return fmt.Errorf("failed to get plugin gRPC client: %w", err)
}
// Create a subrouter for this plugin under /api/p/{pluginID}/*
pluginPath := fmt.Sprintf("/p/%s", pluginID)
pluginRouter := m.router.PathPrefix(pluginPath).Subrouter()
// Mount a catch-all handler that forwards requests to the plugin via gRPC
pluginRouter.PathPrefix("/").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
m.handlePluginRoute(w, r, pluginID, client)
})
log.Printf("[PluginManager] Mounted routes for plugin %s at /api%s", pluginID, pluginPath)
return nil
}
// handlePluginRoute forwards HTTP requests to an external plugin via gRPC
func (m *Manager) handlePluginRoute(w http.ResponseWriter, r *http.Request, pluginID string, client pb.PluginClient) {
// Extract path after /api/p/{pluginID}/
pathPrefix := fmt.Sprintf("/api/p/%s/", pluginID)
pluginPath := r.URL.Path
if len(pluginPath) >= len(pathPrefix) {
pluginPath = pluginPath[len(pathPrefix)-1:] // Keep the leading slash
}
// Read request body
body, err := io.ReadAll(r.Body)
if err != nil {
http.Error(w, "Failed to read request body", http.StatusInternalServerError)
return
}
// Convert headers to map
headers := make(map[string]string)
for key, values := range r.Header {
if len(values) > 0 {
headers[key] = values[0]
}
}
// Convert query parameters to map
queryParams := make(map[string]string)
for key, values := range r.URL.Query() {
if len(values) > 0 {
queryParams[key] = values[0]
}
}
// Call plugin via gRPC
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
resp, err := client.HandleRoute(ctx, &pb.RouteRequest{
PluginId: pluginID,
Method: r.Method,
Path: pluginPath,
Headers: headers,
Body: body,
QueryParams: queryParams,
})
if err != nil {
log.Printf("[PluginManager] Plugin %s route handler error: %v", pluginID, err)
http.Error(w, "Plugin error", http.StatusInternalServerError)
return
}
// Set response headers
for key, value := range resp.Headers {
w.Header().Set(key, value)
}
// Set status code
w.WriteHeader(int(resp.StatusCode))
// Write response body
if len(resp.Body) > 0 {
w.Write(resp.Body)
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,358 +0,0 @@
syntax = "proto3";
package census.plugin;
option go_package = "github.com/selfhosters-cc/container-census/internal/plugins/proto";
// Plugin service - implemented by external plugin processes
service Plugin {
// Lifecycle methods
rpc Init(InitRequest) returns (InitResponse);
rpc Start(StartRequest) returns (StartResponse);
rpc Stop(StopRequest) returns (StopResponse);
rpc Healthcheck(HealthcheckRequest) returns (HealthcheckResponse);
// Capability methods
rpc GetInfo(InfoRequest) returns (InfoResponse);
rpc GetTab(TabRequest) returns (TabResponse);
rpc GetBadges(BadgesRequest) returns (BadgesResponse);
rpc HandleRoute(RouteRequest) returns (RouteResponse);
rpc EnrichContainer(EnrichRequest) returns (EnrichResponse);
rpc HandleEvent(EventRequest) returns (EventResponse);
rpc GetSettings(SettingsRequest) returns (SettingsResponse);
rpc UpdateSettings(UpdateSettingsRequest) returns (UpdateSettingsResponse);
}
// Census API service - implemented by main server (callback API for plugins)
service CensusAPI {
rpc GetContainers(GetContainersRequest) returns (GetContainersResponse);
rpc GetContainer(GetContainerRequest) returns (GetContainerResponse);
rpc GetHosts(GetHostsRequest) returns (GetHostsResponse);
rpc GetHost(GetHostRequest) returns (GetHostResponse);
rpc GetPluginData(GetPluginDataRequest) returns (GetPluginDataResponse);
rpc SetPluginData(SetPluginDataRequest) returns (SetPluginDataResponse);
rpc DeletePluginData(DeletePluginDataRequest) returns (DeletePluginDataResponse);
rpc Log(LogRequest) returns (LogResponse);
rpc SendEvent(SendEventRequest) returns (SendEventResponse);
}
// ============================================================================
// Plugin Service Messages
// ============================================================================
// Init - Called when plugin is first loaded
message InitRequest {
string plugin_id = 1;
map<string, string> config = 2;
string census_api_address = 3; // gRPC address for callbacks (e.g., "localhost:50052")
string census_version = 4;
}
message InitResponse {
bool success = 1;
string error = 2;
}
// Start - Called to start plugin operations
message StartRequest {
string plugin_id = 1;
}
message StartResponse {
bool success = 1;
string error = 2;
}
// Stop - Called to gracefully stop plugin
message StopRequest {
string plugin_id = 1;
}
message StopResponse {
bool success = 1;
}
// Healthcheck - Called periodically to verify plugin is alive
message HealthcheckRequest {
string plugin_id = 1;
}
message HealthcheckResponse {
bool healthy = 1;
string status = 2; // "running", "degraded", "failed"
map<string, string> metrics = 3; // Optional metrics
}
// GetInfo - Returns plugin metadata
message InfoRequest {
string plugin_id = 1;
}
message InfoResponse {
string id = 1;
string name = 2;
string version = 3;
string description = 4;
string author = 5;
string homepage = 6;
repeated string capabilities = 7; // "ui_tab", "ui_badge", "container_enrichment", etc.
}
// GetTab - Returns UI tab configuration
message TabRequest {
string plugin_id = 1;
}
message TabResponse {
bool has_tab = 1;
string id = 2;
string label = 3;
string icon = 4;
int32 order = 5;
string script_url = 6; // URL to frontend bundle
string css_url = 7; // URL to frontend CSS
string init_func = 8; // JavaScript function name to call
}
// GetBadges - Returns badges to display on container cards
message BadgesRequest {
string plugin_id = 1;
Container container = 2;
}
message BadgesResponse {
repeated Badge badges = 1;
}
message Badge {
string id = 1;
string label = 2;
string icon = 3;
string color = 4;
string tooltip = 5;
string link = 6;
int32 priority = 7;
}
// HandleRoute - Handles HTTP requests to plugin routes
message RouteRequest {
string plugin_id = 1;
string method = 2; // GET, POST, PUT, DELETE, etc.
string path = 3; // Path after /api/p/{plugin-id}/
map<string, string> headers = 4;
bytes body = 5;
map<string, string> query_params = 6;
map<string, string> path_params = 7;
}
message RouteResponse {
int32 status_code = 1;
map<string, string> headers = 2;
bytes body = 3;
}
// EnrichContainer - Adds custom data to containers
message EnrichRequest {
string plugin_id = 1;
Container container = 2;
}
message EnrichResponse {
map<string, string> plugin_data = 1; // Data to add to container.plugin_data
}
// HandleEvent - Receives system events
message EventRequest {
string plugin_id = 1;
string event_type = 2; // "scan_complete", "container_state_change", "image_updated", etc.
string timestamp = 3;
map<string, string> data = 4;
}
message EventResponse {
bool handled = 1;
}
// GetSettings - Returns plugin settings schema
message SettingsRequest {
string plugin_id = 1;
}
message SettingsResponse {
string schema_json = 1; // JSON schema for settings
string values_json = 2; // Current setting values as JSON
}
// UpdateSettings - Updates plugin settings
message UpdateSettingsRequest {
string plugin_id = 1;
string values_json = 2; // New settings as JSON
}
message UpdateSettingsResponse {
bool success = 1;
string error = 2;
}
// ============================================================================
// Census API Service Messages
// ============================================================================
// GetContainers - Fetch all containers
message GetContainersRequest {
string plugin_id = 1;
bool latest_only = 2; // If true, only return latest scan results
int64 host_id = 3; // If > 0, filter by host
}
message GetContainersResponse {
repeated Container containers = 1;
}
// GetContainer - Fetch specific container
message GetContainerRequest {
string plugin_id = 1;
string container_id = 2;
int64 host_id = 3;
}
message GetContainerResponse {
Container container = 1;
bool found = 2;
}
// GetHosts - Fetch all hosts
message GetHostsRequest {
string plugin_id = 1;
}
message GetHostsResponse {
repeated Host hosts = 1;
}
// GetHost - Fetch specific host
message GetHostRequest {
string plugin_id = 1;
int64 host_id = 2;
}
message GetHostResponse {
Host host = 1;
bool found = 2;
}
// GetPluginData - Fetch plugin-specific data from storage
message GetPluginDataRequest {
string plugin_id = 1;
string key = 2;
}
message GetPluginDataResponse {
string value = 1;
bool found = 2;
}
// SetPluginData - Store plugin-specific data
message SetPluginDataRequest {
string plugin_id = 1;
string key = 2;
string value = 3;
}
message SetPluginDataResponse {
bool success = 1;
}
// DeletePluginData - Delete plugin-specific data
message DeletePluginDataRequest {
string plugin_id = 1;
string key = 2;
}
message DeletePluginDataResponse {
bool success = 1;
}
// Log - Send log message to Census server
message LogRequest {
string plugin_id = 1;
string level = 2; // "debug", "info", "warn", "error"
string message = 3;
map<string, string> fields = 4;
}
message LogResponse {
bool success = 1;
}
// SendEvent - Send event to other plugins
message SendEventRequest {
string plugin_id = 1;
string event_type = 2;
map<string, string> data = 3;
}
message SendEventResponse {
bool success = 1;
}
// ============================================================================
// Data Models
// ============================================================================
message Container {
string id = 1;
string name = 2;
string image = 3;
string image_id = 4;
string state = 5;
string status = 6;
int64 host_id = 7;
string host_name = 8;
string created = 9;
string started_at = 10;
string finished_at = 11;
repeated Port ports = 12;
repeated string networks = 13;
repeated Volume volumes = 14;
repeated string links = 15;
map<string, string> labels = 16;
map<string, string> env = 17;
string compose_project = 18;
// Resource stats
double cpu_percent = 19;
int64 memory_usage = 20;
int64 memory_limit = 21;
double memory_percent = 22;
// Plugin data
map<string, string> plugin_data = 23;
}
message Port {
int32 container_port = 1;
int32 host_port = 2;
string protocol = 3;
string host_ip = 4;
}
message Volume {
string type = 1; // "bind", "volume", "tmpfs"
string name = 2; // Volume name (for named volumes)
string source = 3; // Host path
string destination = 4; // Container path
string mode = 5; // "rw", "ro"
}
message Host {
int64 id = 1;
string name = 2;
string address = 3;
string host_type = 4; // "unix", "agent", "tcp", "ssh"
string description = 5;
bool enabled = 6;
bool collect_stats = 7;
string last_seen = 8;
string agent_version = 9;
string agent_status = 10; // "online", "offline", "auth_failed"
}

View File

@@ -1,957 +0,0 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions:
// - protoc-gen-go-grpc v1.6.0
// - protoc v5.29.3
// source: internal/plugins/proto/plugin.proto
package proto
import (
context "context"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
)
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
// Requires gRPC-Go v1.64.0 or later.
const _ = grpc.SupportPackageIsVersion9
const (
Plugin_Init_FullMethodName = "/census.plugin.Plugin/Init"
Plugin_Start_FullMethodName = "/census.plugin.Plugin/Start"
Plugin_Stop_FullMethodName = "/census.plugin.Plugin/Stop"
Plugin_Healthcheck_FullMethodName = "/census.plugin.Plugin/Healthcheck"
Plugin_GetInfo_FullMethodName = "/census.plugin.Plugin/GetInfo"
Plugin_GetTab_FullMethodName = "/census.plugin.Plugin/GetTab"
Plugin_GetBadges_FullMethodName = "/census.plugin.Plugin/GetBadges"
Plugin_HandleRoute_FullMethodName = "/census.plugin.Plugin/HandleRoute"
Plugin_EnrichContainer_FullMethodName = "/census.plugin.Plugin/EnrichContainer"
Plugin_HandleEvent_FullMethodName = "/census.plugin.Plugin/HandleEvent"
Plugin_GetSettings_FullMethodName = "/census.plugin.Plugin/GetSettings"
Plugin_UpdateSettings_FullMethodName = "/census.plugin.Plugin/UpdateSettings"
)
// PluginClient is the client API for Plugin service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
//
// Plugin service - implemented by external plugin processes
type PluginClient interface {
// Lifecycle methods
Init(ctx context.Context, in *InitRequest, opts ...grpc.CallOption) (*InitResponse, error)
Start(ctx context.Context, in *StartRequest, opts ...grpc.CallOption) (*StartResponse, error)
Stop(ctx context.Context, in *StopRequest, opts ...grpc.CallOption) (*StopResponse, error)
Healthcheck(ctx context.Context, in *HealthcheckRequest, opts ...grpc.CallOption) (*HealthcheckResponse, error)
// Capability methods
GetInfo(ctx context.Context, in *InfoRequest, opts ...grpc.CallOption) (*InfoResponse, error)
GetTab(ctx context.Context, in *TabRequest, opts ...grpc.CallOption) (*TabResponse, error)
GetBadges(ctx context.Context, in *BadgesRequest, opts ...grpc.CallOption) (*BadgesResponse, error)
HandleRoute(ctx context.Context, in *RouteRequest, opts ...grpc.CallOption) (*RouteResponse, error)
EnrichContainer(ctx context.Context, in *EnrichRequest, opts ...grpc.CallOption) (*EnrichResponse, error)
HandleEvent(ctx context.Context, in *EventRequest, opts ...grpc.CallOption) (*EventResponse, error)
GetSettings(ctx context.Context, in *SettingsRequest, opts ...grpc.CallOption) (*SettingsResponse, error)
UpdateSettings(ctx context.Context, in *UpdateSettingsRequest, opts ...grpc.CallOption) (*UpdateSettingsResponse, error)
}
type pluginClient struct {
cc grpc.ClientConnInterface
}
func NewPluginClient(cc grpc.ClientConnInterface) PluginClient {
return &pluginClient{cc}
}
func (c *pluginClient) Init(ctx context.Context, in *InitRequest, opts ...grpc.CallOption) (*InitResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(InitResponse)
err := c.cc.Invoke(ctx, Plugin_Init_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *pluginClient) Start(ctx context.Context, in *StartRequest, opts ...grpc.CallOption) (*StartResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(StartResponse)
err := c.cc.Invoke(ctx, Plugin_Start_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *pluginClient) Stop(ctx context.Context, in *StopRequest, opts ...grpc.CallOption) (*StopResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(StopResponse)
err := c.cc.Invoke(ctx, Plugin_Stop_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *pluginClient) Healthcheck(ctx context.Context, in *HealthcheckRequest, opts ...grpc.CallOption) (*HealthcheckResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(HealthcheckResponse)
err := c.cc.Invoke(ctx, Plugin_Healthcheck_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *pluginClient) GetInfo(ctx context.Context, in *InfoRequest, opts ...grpc.CallOption) (*InfoResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(InfoResponse)
err := c.cc.Invoke(ctx, Plugin_GetInfo_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *pluginClient) GetTab(ctx context.Context, in *TabRequest, opts ...grpc.CallOption) (*TabResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(TabResponse)
err := c.cc.Invoke(ctx, Plugin_GetTab_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *pluginClient) GetBadges(ctx context.Context, in *BadgesRequest, opts ...grpc.CallOption) (*BadgesResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(BadgesResponse)
err := c.cc.Invoke(ctx, Plugin_GetBadges_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *pluginClient) HandleRoute(ctx context.Context, in *RouteRequest, opts ...grpc.CallOption) (*RouteResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(RouteResponse)
err := c.cc.Invoke(ctx, Plugin_HandleRoute_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *pluginClient) EnrichContainer(ctx context.Context, in *EnrichRequest, opts ...grpc.CallOption) (*EnrichResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(EnrichResponse)
err := c.cc.Invoke(ctx, Plugin_EnrichContainer_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *pluginClient) HandleEvent(ctx context.Context, in *EventRequest, opts ...grpc.CallOption) (*EventResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(EventResponse)
err := c.cc.Invoke(ctx, Plugin_HandleEvent_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *pluginClient) GetSettings(ctx context.Context, in *SettingsRequest, opts ...grpc.CallOption) (*SettingsResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(SettingsResponse)
err := c.cc.Invoke(ctx, Plugin_GetSettings_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *pluginClient) UpdateSettings(ctx context.Context, in *UpdateSettingsRequest, opts ...grpc.CallOption) (*UpdateSettingsResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(UpdateSettingsResponse)
err := c.cc.Invoke(ctx, Plugin_UpdateSettings_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
// PluginServer is the server API for Plugin service.
// All implementations must embed UnimplementedPluginServer
// for forward compatibility.
//
// Plugin service - implemented by external plugin processes
type PluginServer interface {
// Lifecycle methods
Init(context.Context, *InitRequest) (*InitResponse, error)
Start(context.Context, *StartRequest) (*StartResponse, error)
Stop(context.Context, *StopRequest) (*StopResponse, error)
Healthcheck(context.Context, *HealthcheckRequest) (*HealthcheckResponse, error)
// Capability methods
GetInfo(context.Context, *InfoRequest) (*InfoResponse, error)
GetTab(context.Context, *TabRequest) (*TabResponse, error)
GetBadges(context.Context, *BadgesRequest) (*BadgesResponse, error)
HandleRoute(context.Context, *RouteRequest) (*RouteResponse, error)
EnrichContainer(context.Context, *EnrichRequest) (*EnrichResponse, error)
HandleEvent(context.Context, *EventRequest) (*EventResponse, error)
GetSettings(context.Context, *SettingsRequest) (*SettingsResponse, error)
UpdateSettings(context.Context, *UpdateSettingsRequest) (*UpdateSettingsResponse, error)
mustEmbedUnimplementedPluginServer()
}
// UnimplementedPluginServer must be embedded to have
// forward compatible implementations.
//
// NOTE: this should be embedded by value instead of pointer to avoid a nil
// pointer dereference when methods are called.
type UnimplementedPluginServer struct{}
func (UnimplementedPluginServer) Init(context.Context, *InitRequest) (*InitResponse, error) {
return nil, status.Error(codes.Unimplemented, "method Init not implemented")
}
func (UnimplementedPluginServer) Start(context.Context, *StartRequest) (*StartResponse, error) {
return nil, status.Error(codes.Unimplemented, "method Start not implemented")
}
func (UnimplementedPluginServer) Stop(context.Context, *StopRequest) (*StopResponse, error) {
return nil, status.Error(codes.Unimplemented, "method Stop not implemented")
}
func (UnimplementedPluginServer) Healthcheck(context.Context, *HealthcheckRequest) (*HealthcheckResponse, error) {
return nil, status.Error(codes.Unimplemented, "method Healthcheck not implemented")
}
func (UnimplementedPluginServer) GetInfo(context.Context, *InfoRequest) (*InfoResponse, error) {
return nil, status.Error(codes.Unimplemented, "method GetInfo not implemented")
}
func (UnimplementedPluginServer) GetTab(context.Context, *TabRequest) (*TabResponse, error) {
return nil, status.Error(codes.Unimplemented, "method GetTab not implemented")
}
func (UnimplementedPluginServer) GetBadges(context.Context, *BadgesRequest) (*BadgesResponse, error) {
return nil, status.Error(codes.Unimplemented, "method GetBadges not implemented")
}
func (UnimplementedPluginServer) HandleRoute(context.Context, *RouteRequest) (*RouteResponse, error) {
return nil, status.Error(codes.Unimplemented, "method HandleRoute not implemented")
}
func (UnimplementedPluginServer) EnrichContainer(context.Context, *EnrichRequest) (*EnrichResponse, error) {
return nil, status.Error(codes.Unimplemented, "method EnrichContainer not implemented")
}
func (UnimplementedPluginServer) HandleEvent(context.Context, *EventRequest) (*EventResponse, error) {
return nil, status.Error(codes.Unimplemented, "method HandleEvent not implemented")
}
func (UnimplementedPluginServer) GetSettings(context.Context, *SettingsRequest) (*SettingsResponse, error) {
return nil, status.Error(codes.Unimplemented, "method GetSettings not implemented")
}
func (UnimplementedPluginServer) UpdateSettings(context.Context, *UpdateSettingsRequest) (*UpdateSettingsResponse, error) {
return nil, status.Error(codes.Unimplemented, "method UpdateSettings not implemented")
}
func (UnimplementedPluginServer) mustEmbedUnimplementedPluginServer() {}
func (UnimplementedPluginServer) testEmbeddedByValue() {}
// UnsafePluginServer may be embedded to opt out of forward compatibility for this service.
// Use of this interface is not recommended, as added methods to PluginServer will
// result in compilation errors.
type UnsafePluginServer interface {
mustEmbedUnimplementedPluginServer()
}
func RegisterPluginServer(s grpc.ServiceRegistrar, srv PluginServer) {
// If the following call panics, it indicates UnimplementedPluginServer was
// embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O.
if t, ok := srv.(interface{ testEmbeddedByValue() }); ok {
t.testEmbeddedByValue()
}
s.RegisterService(&Plugin_ServiceDesc, srv)
}
func _Plugin_Init_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(InitRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(PluginServer).Init(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: Plugin_Init_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(PluginServer).Init(ctx, req.(*InitRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Plugin_Start_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(StartRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(PluginServer).Start(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: Plugin_Start_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(PluginServer).Start(ctx, req.(*StartRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Plugin_Stop_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(StopRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(PluginServer).Stop(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: Plugin_Stop_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(PluginServer).Stop(ctx, req.(*StopRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Plugin_Healthcheck_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(HealthcheckRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(PluginServer).Healthcheck(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: Plugin_Healthcheck_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(PluginServer).Healthcheck(ctx, req.(*HealthcheckRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Plugin_GetInfo_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(InfoRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(PluginServer).GetInfo(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: Plugin_GetInfo_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(PluginServer).GetInfo(ctx, req.(*InfoRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Plugin_GetTab_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(TabRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(PluginServer).GetTab(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: Plugin_GetTab_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(PluginServer).GetTab(ctx, req.(*TabRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Plugin_GetBadges_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(BadgesRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(PluginServer).GetBadges(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: Plugin_GetBadges_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(PluginServer).GetBadges(ctx, req.(*BadgesRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Plugin_HandleRoute_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(RouteRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(PluginServer).HandleRoute(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: Plugin_HandleRoute_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(PluginServer).HandleRoute(ctx, req.(*RouteRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Plugin_EnrichContainer_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(EnrichRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(PluginServer).EnrichContainer(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: Plugin_EnrichContainer_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(PluginServer).EnrichContainer(ctx, req.(*EnrichRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Plugin_HandleEvent_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(EventRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(PluginServer).HandleEvent(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: Plugin_HandleEvent_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(PluginServer).HandleEvent(ctx, req.(*EventRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Plugin_GetSettings_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(SettingsRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(PluginServer).GetSettings(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: Plugin_GetSettings_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(PluginServer).GetSettings(ctx, req.(*SettingsRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Plugin_UpdateSettings_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(UpdateSettingsRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(PluginServer).UpdateSettings(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: Plugin_UpdateSettings_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(PluginServer).UpdateSettings(ctx, req.(*UpdateSettingsRequest))
}
return interceptor(ctx, in, info, handler)
}
// Plugin_ServiceDesc is the grpc.ServiceDesc for Plugin service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
var Plugin_ServiceDesc = grpc.ServiceDesc{
ServiceName: "census.plugin.Plugin",
HandlerType: (*PluginServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "Init",
Handler: _Plugin_Init_Handler,
},
{
MethodName: "Start",
Handler: _Plugin_Start_Handler,
},
{
MethodName: "Stop",
Handler: _Plugin_Stop_Handler,
},
{
MethodName: "Healthcheck",
Handler: _Plugin_Healthcheck_Handler,
},
{
MethodName: "GetInfo",
Handler: _Plugin_GetInfo_Handler,
},
{
MethodName: "GetTab",
Handler: _Plugin_GetTab_Handler,
},
{
MethodName: "GetBadges",
Handler: _Plugin_GetBadges_Handler,
},
{
MethodName: "HandleRoute",
Handler: _Plugin_HandleRoute_Handler,
},
{
MethodName: "EnrichContainer",
Handler: _Plugin_EnrichContainer_Handler,
},
{
MethodName: "HandleEvent",
Handler: _Plugin_HandleEvent_Handler,
},
{
MethodName: "GetSettings",
Handler: _Plugin_GetSettings_Handler,
},
{
MethodName: "UpdateSettings",
Handler: _Plugin_UpdateSettings_Handler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "internal/plugins/proto/plugin.proto",
}
const (
CensusAPI_GetContainers_FullMethodName = "/census.plugin.CensusAPI/GetContainers"
CensusAPI_GetContainer_FullMethodName = "/census.plugin.CensusAPI/GetContainer"
CensusAPI_GetHosts_FullMethodName = "/census.plugin.CensusAPI/GetHosts"
CensusAPI_GetHost_FullMethodName = "/census.plugin.CensusAPI/GetHost"
CensusAPI_GetPluginData_FullMethodName = "/census.plugin.CensusAPI/GetPluginData"
CensusAPI_SetPluginData_FullMethodName = "/census.plugin.CensusAPI/SetPluginData"
CensusAPI_DeletePluginData_FullMethodName = "/census.plugin.CensusAPI/DeletePluginData"
CensusAPI_Log_FullMethodName = "/census.plugin.CensusAPI/Log"
CensusAPI_SendEvent_FullMethodName = "/census.plugin.CensusAPI/SendEvent"
)
// CensusAPIClient is the client API for CensusAPI service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
//
// Census API service - implemented by main server (callback API for plugins)
type CensusAPIClient interface {
GetContainers(ctx context.Context, in *GetContainersRequest, opts ...grpc.CallOption) (*GetContainersResponse, error)
GetContainer(ctx context.Context, in *GetContainerRequest, opts ...grpc.CallOption) (*GetContainerResponse, error)
GetHosts(ctx context.Context, in *GetHostsRequest, opts ...grpc.CallOption) (*GetHostsResponse, error)
GetHost(ctx context.Context, in *GetHostRequest, opts ...grpc.CallOption) (*GetHostResponse, error)
GetPluginData(ctx context.Context, in *GetPluginDataRequest, opts ...grpc.CallOption) (*GetPluginDataResponse, error)
SetPluginData(ctx context.Context, in *SetPluginDataRequest, opts ...grpc.CallOption) (*SetPluginDataResponse, error)
DeletePluginData(ctx context.Context, in *DeletePluginDataRequest, opts ...grpc.CallOption) (*DeletePluginDataResponse, error)
Log(ctx context.Context, in *LogRequest, opts ...grpc.CallOption) (*LogResponse, error)
SendEvent(ctx context.Context, in *SendEventRequest, opts ...grpc.CallOption) (*SendEventResponse, error)
}
type censusAPIClient struct {
cc grpc.ClientConnInterface
}
func NewCensusAPIClient(cc grpc.ClientConnInterface) CensusAPIClient {
return &censusAPIClient{cc}
}
func (c *censusAPIClient) GetContainers(ctx context.Context, in *GetContainersRequest, opts ...grpc.CallOption) (*GetContainersResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(GetContainersResponse)
err := c.cc.Invoke(ctx, CensusAPI_GetContainers_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *censusAPIClient) GetContainer(ctx context.Context, in *GetContainerRequest, opts ...grpc.CallOption) (*GetContainerResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(GetContainerResponse)
err := c.cc.Invoke(ctx, CensusAPI_GetContainer_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *censusAPIClient) GetHosts(ctx context.Context, in *GetHostsRequest, opts ...grpc.CallOption) (*GetHostsResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(GetHostsResponse)
err := c.cc.Invoke(ctx, CensusAPI_GetHosts_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *censusAPIClient) GetHost(ctx context.Context, in *GetHostRequest, opts ...grpc.CallOption) (*GetHostResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(GetHostResponse)
err := c.cc.Invoke(ctx, CensusAPI_GetHost_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *censusAPIClient) GetPluginData(ctx context.Context, in *GetPluginDataRequest, opts ...grpc.CallOption) (*GetPluginDataResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(GetPluginDataResponse)
err := c.cc.Invoke(ctx, CensusAPI_GetPluginData_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *censusAPIClient) SetPluginData(ctx context.Context, in *SetPluginDataRequest, opts ...grpc.CallOption) (*SetPluginDataResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(SetPluginDataResponse)
err := c.cc.Invoke(ctx, CensusAPI_SetPluginData_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *censusAPIClient) DeletePluginData(ctx context.Context, in *DeletePluginDataRequest, opts ...grpc.CallOption) (*DeletePluginDataResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(DeletePluginDataResponse)
err := c.cc.Invoke(ctx, CensusAPI_DeletePluginData_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *censusAPIClient) Log(ctx context.Context, in *LogRequest, opts ...grpc.CallOption) (*LogResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(LogResponse)
err := c.cc.Invoke(ctx, CensusAPI_Log_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *censusAPIClient) SendEvent(ctx context.Context, in *SendEventRequest, opts ...grpc.CallOption) (*SendEventResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(SendEventResponse)
err := c.cc.Invoke(ctx, CensusAPI_SendEvent_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
// CensusAPIServer is the server API for CensusAPI service.
// All implementations must embed UnimplementedCensusAPIServer
// for forward compatibility.
//
// Census API service - implemented by main server (callback API for plugins)
type CensusAPIServer interface {
GetContainers(context.Context, *GetContainersRequest) (*GetContainersResponse, error)
GetContainer(context.Context, *GetContainerRequest) (*GetContainerResponse, error)
GetHosts(context.Context, *GetHostsRequest) (*GetHostsResponse, error)
GetHost(context.Context, *GetHostRequest) (*GetHostResponse, error)
GetPluginData(context.Context, *GetPluginDataRequest) (*GetPluginDataResponse, error)
SetPluginData(context.Context, *SetPluginDataRequest) (*SetPluginDataResponse, error)
DeletePluginData(context.Context, *DeletePluginDataRequest) (*DeletePluginDataResponse, error)
Log(context.Context, *LogRequest) (*LogResponse, error)
SendEvent(context.Context, *SendEventRequest) (*SendEventResponse, error)
mustEmbedUnimplementedCensusAPIServer()
}
// UnimplementedCensusAPIServer must be embedded to have
// forward compatible implementations.
//
// NOTE: this should be embedded by value instead of pointer to avoid a nil
// pointer dereference when methods are called.
type UnimplementedCensusAPIServer struct{}
func (UnimplementedCensusAPIServer) GetContainers(context.Context, *GetContainersRequest) (*GetContainersResponse, error) {
return nil, status.Error(codes.Unimplemented, "method GetContainers not implemented")
}
func (UnimplementedCensusAPIServer) GetContainer(context.Context, *GetContainerRequest) (*GetContainerResponse, error) {
return nil, status.Error(codes.Unimplemented, "method GetContainer not implemented")
}
func (UnimplementedCensusAPIServer) GetHosts(context.Context, *GetHostsRequest) (*GetHostsResponse, error) {
return nil, status.Error(codes.Unimplemented, "method GetHosts not implemented")
}
func (UnimplementedCensusAPIServer) GetHost(context.Context, *GetHostRequest) (*GetHostResponse, error) {
return nil, status.Error(codes.Unimplemented, "method GetHost not implemented")
}
func (UnimplementedCensusAPIServer) GetPluginData(context.Context, *GetPluginDataRequest) (*GetPluginDataResponse, error) {
return nil, status.Error(codes.Unimplemented, "method GetPluginData not implemented")
}
func (UnimplementedCensusAPIServer) SetPluginData(context.Context, *SetPluginDataRequest) (*SetPluginDataResponse, error) {
return nil, status.Error(codes.Unimplemented, "method SetPluginData not implemented")
}
func (UnimplementedCensusAPIServer) DeletePluginData(context.Context, *DeletePluginDataRequest) (*DeletePluginDataResponse, error) {
return nil, status.Error(codes.Unimplemented, "method DeletePluginData not implemented")
}
func (UnimplementedCensusAPIServer) Log(context.Context, *LogRequest) (*LogResponse, error) {
return nil, status.Error(codes.Unimplemented, "method Log not implemented")
}
func (UnimplementedCensusAPIServer) SendEvent(context.Context, *SendEventRequest) (*SendEventResponse, error) {
return nil, status.Error(codes.Unimplemented, "method SendEvent not implemented")
}
func (UnimplementedCensusAPIServer) mustEmbedUnimplementedCensusAPIServer() {}
func (UnimplementedCensusAPIServer) testEmbeddedByValue() {}
// UnsafeCensusAPIServer may be embedded to opt out of forward compatibility for this service.
// Use of this interface is not recommended, as added methods to CensusAPIServer will
// result in compilation errors.
type UnsafeCensusAPIServer interface {
mustEmbedUnimplementedCensusAPIServer()
}
func RegisterCensusAPIServer(s grpc.ServiceRegistrar, srv CensusAPIServer) {
// If the following call panics, it indicates UnimplementedCensusAPIServer was
// embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O.
if t, ok := srv.(interface{ testEmbeddedByValue() }); ok {
t.testEmbeddedByValue()
}
s.RegisterService(&CensusAPI_ServiceDesc, srv)
}
func _CensusAPI_GetContainers_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(GetContainersRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(CensusAPIServer).GetContainers(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: CensusAPI_GetContainers_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(CensusAPIServer).GetContainers(ctx, req.(*GetContainersRequest))
}
return interceptor(ctx, in, info, handler)
}
func _CensusAPI_GetContainer_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(GetContainerRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(CensusAPIServer).GetContainer(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: CensusAPI_GetContainer_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(CensusAPIServer).GetContainer(ctx, req.(*GetContainerRequest))
}
return interceptor(ctx, in, info, handler)
}
func _CensusAPI_GetHosts_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(GetHostsRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(CensusAPIServer).GetHosts(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: CensusAPI_GetHosts_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(CensusAPIServer).GetHosts(ctx, req.(*GetHostsRequest))
}
return interceptor(ctx, in, info, handler)
}
func _CensusAPI_GetHost_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(GetHostRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(CensusAPIServer).GetHost(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: CensusAPI_GetHost_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(CensusAPIServer).GetHost(ctx, req.(*GetHostRequest))
}
return interceptor(ctx, in, info, handler)
}
func _CensusAPI_GetPluginData_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(GetPluginDataRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(CensusAPIServer).GetPluginData(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: CensusAPI_GetPluginData_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(CensusAPIServer).GetPluginData(ctx, req.(*GetPluginDataRequest))
}
return interceptor(ctx, in, info, handler)
}
func _CensusAPI_SetPluginData_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(SetPluginDataRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(CensusAPIServer).SetPluginData(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: CensusAPI_SetPluginData_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(CensusAPIServer).SetPluginData(ctx, req.(*SetPluginDataRequest))
}
return interceptor(ctx, in, info, handler)
}
func _CensusAPI_DeletePluginData_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(DeletePluginDataRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(CensusAPIServer).DeletePluginData(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: CensusAPI_DeletePluginData_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(CensusAPIServer).DeletePluginData(ctx, req.(*DeletePluginDataRequest))
}
return interceptor(ctx, in, info, handler)
}
func _CensusAPI_Log_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(LogRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(CensusAPIServer).Log(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: CensusAPI_Log_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(CensusAPIServer).Log(ctx, req.(*LogRequest))
}
return interceptor(ctx, in, info, handler)
}
func _CensusAPI_SendEvent_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(SendEventRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(CensusAPIServer).SendEvent(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: CensusAPI_SendEvent_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(CensusAPIServer).SendEvent(ctx, req.(*SendEventRequest))
}
return interceptor(ctx, in, info, handler)
}
// CensusAPI_ServiceDesc is the grpc.ServiceDesc for CensusAPI service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
var CensusAPI_ServiceDesc = grpc.ServiceDesc{
ServiceName: "census.plugin.CensusAPI",
HandlerType: (*CensusAPIServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "GetContainers",
Handler: _CensusAPI_GetContainers_Handler,
},
{
MethodName: "GetContainer",
Handler: _CensusAPI_GetContainer_Handler,
},
{
MethodName: "GetHosts",
Handler: _CensusAPI_GetHosts_Handler,
},
{
MethodName: "GetHost",
Handler: _CensusAPI_GetHost_Handler,
},
{
MethodName: "GetPluginData",
Handler: _CensusAPI_GetPluginData_Handler,
},
{
MethodName: "SetPluginData",
Handler: _CensusAPI_SetPluginData_Handler,
},
{
MethodName: "DeletePluginData",
Handler: _CensusAPI_DeletePluginData_Handler,
},
{
MethodName: "Log",
Handler: _CensusAPI_Log_Handler,
},
{
MethodName: "SendEvent",
Handler: _CensusAPI_SendEvent_Handler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "internal/plugins/proto/plugin.proto",
}

BIN
server Executable file

Binary file not shown.

BIN
telemetry-collector Executable file

Binary file not shown.