Some checks failed
Go / build (.exe, 386, windows, windows-386) (push) Has been cancelled
Go / build (.exe, amd64, windows, windows-amd64) (push) Has been cancelled
Go / build (.exe, arm64, windows, windows-arm64) (push) Has been cancelled
Go / build (386, freebsd, freebsd-386) (push) Has been cancelled
Go / build (386, linux, linux-386) (push) Has been cancelled
Go / build (386, netbsd, netbsd-386) (push) Has been cancelled
Go / build (386, openbsd, openbsd-386) (push) Has been cancelled
Go / build (386, plan9, plan9-386) (push) Has been cancelled
Go / build (amd64, darwin, darwin-amd64) (push) Has been cancelled
Go / build (amd64, dragonfly, dragonfly-amd64) (push) Has been cancelled
Go / build (amd64, freebsd, freebsd-amd64) (push) Has been cancelled
Go / build (amd64, illumos, illumos-amd64) (push) Has been cancelled
Go / build (amd64, linux, linux-amd64) (push) Has been cancelled
Go / build (amd64, netbsd, netbsd-amd64) (push) Has been cancelled
Go / build (amd64, openbsd, openbsd-amd64) (push) Has been cancelled
Go / build (amd64, plan9, plan9-amd64) (push) Has been cancelled
Go / build (amd64, solaris, solaris-amd64) (push) Has been cancelled
Go / build (arm, 6, linux, linux-armv6) (push) Has been cancelled
Go / build (arm, 7, linux, linux-armv7) (push) Has been cancelled
Go / build (arm, freebsd, freebsd-arm) (push) Has been cancelled
Go / build (arm, netbsd, netbsd-arm) (push) Has been cancelled
Go / build (arm, openbsd, openbsd-arm) (push) Has been cancelled
Go / build (arm, plan9, plan9-arm) (push) Has been cancelled
Go / build (arm64, darwin, darwin-arm64) (push) Has been cancelled
Go / build (arm64, freebsd, freebsd-arm64) (push) Has been cancelled
Go / build (arm64, linux, linux-arm64) (push) Has been cancelled
Go / build (arm64, netbsd, netbsd-arm64) (push) Has been cancelled
Go / build (arm64, openbsd, openbsd-arm64) (push) Has been cancelled
Go / build (loong64, linux, linux-loong64) (push) Has been cancelled
Go / build (mips, linux, linux-mips) (push) Has been cancelled
Go / build (mips64, linux, linux-mips64) (push) Has been cancelled
Go / build (mips64le, linux, linux-mips64le) (push) Has been cancelled
Go / build (mipsle, linux, linux-mipsle) (push) Has been cancelled
Go / build (ppc64, aix, aix-ppc64) (push) Has been cancelled
Go / build (ppc64, linux, linux-ppc64) (push) Has been cancelled
Go / build (ppc64, openbsd, openbsd-ppc64) (push) Has been cancelled
Go / build (ppc64le, linux, linux-ppc64le) (push) Has been cancelled
Go / build (riscv64, freebsd, freebsd-riscv64) (push) Has been cancelled
Go / build (riscv64, linux, linux-riscv64) (push) Has been cancelled
Go / build (riscv64, openbsd, openbsd-riscv64) (push) Has been cancelled
Go / build (s390x, linux, linux-s390x) (push) Has been cancelled
Docker Image / docker (push) Has been cancelled
Go / merge-artifacts (push) Has been cancelled
1534 lines
54 KiB
Go
1534 lines
54 KiB
Go
// cmd/gateway/admin_plugin_handlers.go 承载插件制品、期望状态、配置、密钥、运维操作和治理检查相关的 Admin API。
|
|
|
|
package main
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"net/http"
|
|
"os"
|
|
"path/filepath"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/tursom/mc-gateway/internal/adminhttp"
|
|
"github.com/tursom/mc-gateway/internal/pluginmanager"
|
|
)
|
|
|
|
func handleAdminPluginArtifacts(w http.ResponseWriter, r *http.Request) {
|
|
session, ok := requireRole(w, r, adminRoleMember)
|
|
if !ok {
|
|
return
|
|
}
|
|
if pluginsManager == nil {
|
|
adminhttp.WriteAPIError(w, http.StatusServiceUnavailable, "plugin manager is not initialized")
|
|
return
|
|
}
|
|
|
|
switch r.Method {
|
|
case http.MethodGet:
|
|
artifacts, err := pluginsManager.ListArtifacts(r.Context(), r.URL.Query().Get("plugin_id"))
|
|
if err != nil {
|
|
adminhttp.WriteAPIError(w, http.StatusInternalServerError, err.Error())
|
|
return
|
|
}
|
|
adminhttp.WriteJSON(w, http.StatusOK, map[string]any{"artifacts": artifacts})
|
|
case http.MethodPost:
|
|
if session.Role != adminRoleAdmin {
|
|
adminhttp.WriteAPIError(w, http.StatusForbidden, "forbidden")
|
|
return
|
|
}
|
|
artifact, err := receivePluginArtifact(r, session.Username)
|
|
if err != nil {
|
|
recordAudit(r.Context(), session.Username, adminhttp.RequestSourceIP(r), "plugin_artifact_upload", "plugin_artifact", "", false, err.Error())
|
|
adminhttp.WriteAPIError(w, http.StatusBadRequest, err.Error())
|
|
return
|
|
}
|
|
recordAuditMetadata(r.Context(), session.Username, adminhttp.RequestSourceIP(r), "plugin_artifact_upload", "plugin_artifact", artifact.ID, true, "artifact uploaded", map[string]any{
|
|
"plugin_id": artifact.PluginID,
|
|
"version": artifact.Version,
|
|
"sha256": artifact.SHA256,
|
|
"package_sha256": artifact.PackageSHA256,
|
|
})
|
|
adminhttp.WriteJSON(w, http.StatusCreated, map[string]any{"artifact": artifact})
|
|
default:
|
|
adminhttp.WriteAPIError(w, http.StatusMethodNotAllowed, "method not allowed")
|
|
}
|
|
}
|
|
|
|
func handleAdminPluginSources(w http.ResponseWriter, r *http.Request) {
|
|
session, ok := requireRole(w, r, adminRoleMember)
|
|
if !ok {
|
|
return
|
|
}
|
|
if pluginsManager == nil {
|
|
adminhttp.WriteAPIError(w, http.StatusServiceUnavailable, "plugin manager is not initialized")
|
|
return
|
|
}
|
|
switch r.Method {
|
|
case http.MethodGet:
|
|
artifacts, err := pluginsManager.ListArtifacts(r.Context(), r.URL.Query().Get("plugin_id"))
|
|
if err != nil {
|
|
adminhttp.WriteAPIError(w, http.StatusInternalServerError, err.Error())
|
|
return
|
|
}
|
|
var sources []pluginmanager.ArtifactRecord
|
|
for _, artifact := range artifacts {
|
|
if artifact.ArtifactType == pluginmanager.ArtifactTypeSource {
|
|
sources = append(sources, artifact)
|
|
}
|
|
}
|
|
adminhttp.WriteJSON(w, http.StatusOK, map[string]any{"sources": sources})
|
|
case http.MethodPost:
|
|
if session.Role != adminRoleAdmin {
|
|
adminhttp.WriteAPIError(w, http.StatusForbidden, "forbidden")
|
|
return
|
|
}
|
|
source, err := receivePluginSource(r, session.Username)
|
|
if err != nil {
|
|
recordAudit(r.Context(), session.Username, adminhttp.RequestSourceIP(r), "plugin_source_upload", "plugin_source", "", false, err.Error())
|
|
adminhttp.WriteAPIError(w, http.StatusBadRequest, err.Error())
|
|
return
|
|
}
|
|
recordAuditMetadata(r.Context(), session.Username, adminhttp.RequestSourceIP(r), "plugin_source_upload", "plugin_source", source.ID, true, "source package uploaded", map[string]any{
|
|
"plugin_id": source.PluginID,
|
|
"version": source.Version,
|
|
"source_sha256": source.SHA256,
|
|
})
|
|
adminhttp.WriteJSON(w, http.StatusCreated, map[string]any{"source": source})
|
|
default:
|
|
adminhttp.WriteAPIError(w, http.StatusMethodNotAllowed, "method not allowed")
|
|
}
|
|
}
|
|
|
|
func handleAdminPluginBuilds(w http.ResponseWriter, r *http.Request) {
|
|
session, ok := requireRole(w, r, adminRoleMember)
|
|
if !ok {
|
|
return
|
|
}
|
|
if pluginsManager == nil {
|
|
adminhttp.WriteAPIError(w, http.StatusServiceUnavailable, "plugin manager is not initialized")
|
|
return
|
|
}
|
|
switch r.Method {
|
|
case http.MethodGet:
|
|
builds, err := pluginsManager.ListBuilds(r.Context(), r.URL.Query().Get("plugin_id"))
|
|
if err != nil {
|
|
adminhttp.WriteAPIError(w, http.StatusInternalServerError, err.Error())
|
|
return
|
|
}
|
|
adminhttp.WriteJSON(w, http.StatusOK, map[string]any{"builds": builds})
|
|
case http.MethodPost:
|
|
if session.Role != adminRoleAdmin {
|
|
adminhttp.WriteAPIError(w, http.StatusForbidden, "forbidden")
|
|
return
|
|
}
|
|
var req pluginmanager.BuildRequest
|
|
if !adminhttp.DecodeJSONRequest(w, r, &req) {
|
|
return
|
|
}
|
|
build, err := pluginsManager.CreateBuild(r.Context(), session.Username, req)
|
|
if err != nil {
|
|
recordAudit(r.Context(), session.Username, adminhttp.RequestSourceIP(r), "plugin_source_build", "plugin_build", req.SourceID, false, err.Error())
|
|
adminhttp.WriteAPIError(w, http.StatusBadRequest, err.Error())
|
|
return
|
|
}
|
|
recordAuditMetadata(r.Context(), session.Username, adminhttp.RequestSourceIP(r), "plugin_source_build_queue", "plugin_build", strconv.FormatInt(build.ID, 10), true, "build queued", map[string]any{
|
|
"plugin_id": build.PluginID,
|
|
"source_sha256": build.SourceSHA256,
|
|
"artifact_sha256": build.ArtifactSHA256,
|
|
"builder_type": build.BuilderType,
|
|
"go_version": build.GoVersion,
|
|
})
|
|
adminhttp.WriteJSON(w, http.StatusCreated, map[string]any{"build": build})
|
|
default:
|
|
adminhttp.WriteAPIError(w, http.StatusMethodNotAllowed, "method not allowed")
|
|
}
|
|
}
|
|
|
|
func handleAdminPluginBuild(w http.ResponseWriter, r *http.Request, rawSegment string) {
|
|
session, ok := requireRole(w, r, adminRoleMember)
|
|
if !ok {
|
|
return
|
|
}
|
|
if pluginsManager == nil {
|
|
adminhttp.WriteAPIError(w, http.StatusServiceUnavailable, "plugin manager is not initialized")
|
|
return
|
|
}
|
|
parts := strings.Split(rawSegment, "/")
|
|
id, err := strconv.ParseInt(parts[0], 10, 64)
|
|
if err != nil {
|
|
adminhttp.WriteAPIError(w, http.StatusBadRequest, "invalid build id")
|
|
return
|
|
}
|
|
if len(parts) == 1 && r.Method == http.MethodGet {
|
|
build, err := pluginsManager.Build(r.Context(), id)
|
|
if err != nil {
|
|
adminhttp.WriteAPIError(w, http.StatusNotFound, err.Error())
|
|
return
|
|
}
|
|
adminhttp.WriteJSON(w, http.StatusOK, map[string]any{"build": build})
|
|
return
|
|
}
|
|
if session.Role != adminRoleAdmin {
|
|
adminhttp.WriteAPIError(w, http.StatusForbidden, "forbidden")
|
|
return
|
|
}
|
|
if len(parts) != 2 || r.Method != http.MethodPost {
|
|
adminhttp.WriteAPIError(w, http.StatusMethodNotAllowed, "method not allowed")
|
|
return
|
|
}
|
|
action, err := adminhttp.PathSegment(parts[1])
|
|
if err != nil {
|
|
adminhttp.WriteAPIError(w, http.StatusBadRequest, err.Error())
|
|
return
|
|
}
|
|
var build pluginmanager.BuildRecord
|
|
switch action {
|
|
case "run":
|
|
build, err = pluginsManager.RunBuild(r.Context(), session.Username, id)
|
|
case "cancel":
|
|
build, err = pluginsManager.CancelBuild(r.Context(), session.Username, id)
|
|
case "retry":
|
|
build, err = pluginsManager.RetryBuild(r.Context(), session.Username, id)
|
|
default:
|
|
adminhttp.WriteAPIError(w, http.StatusBadRequest, "unknown build action")
|
|
return
|
|
}
|
|
if err != nil {
|
|
recordAudit(r.Context(), session.Username, adminhttp.RequestSourceIP(r), "plugin_build_"+action, "plugin_build", strconv.FormatInt(id, 10), false, err.Error())
|
|
adminhttp.WriteAPIError(w, http.StatusBadRequest, err.Error())
|
|
return
|
|
}
|
|
recordAudit(r.Context(), session.Username, adminhttp.RequestSourceIP(r), "plugin_build_"+action, "plugin_build", strconv.FormatInt(id, 10), true, "build "+action+" succeeded")
|
|
adminhttp.WriteJSON(w, http.StatusOK, map[string]any{"build": build})
|
|
}
|
|
|
|
func handleAdminPluginGC(w http.ResponseWriter, r *http.Request) {
|
|
session, ok := requireRole(w, r, adminRoleMember)
|
|
if !ok {
|
|
return
|
|
}
|
|
if pluginsManager == nil {
|
|
adminhttp.WriteAPIError(w, http.StatusServiceUnavailable, "plugin manager is not initialized")
|
|
return
|
|
}
|
|
dryRun := r.Method == http.MethodGet
|
|
if r.Method == http.MethodPost && session.Role != adminRoleAdmin {
|
|
adminhttp.WriteAPIError(w, http.StatusForbidden, "forbidden")
|
|
return
|
|
}
|
|
candidates, err := pluginsManager.RunGC(r.Context(), session.Username, dryRun)
|
|
if err != nil {
|
|
adminhttp.WriteAPIError(w, http.StatusInternalServerError, err.Error())
|
|
return
|
|
}
|
|
adminhttp.WriteJSON(w, http.StatusOK, map[string]any{
|
|
"dry_run": dryRun,
|
|
"candidates": candidates,
|
|
})
|
|
}
|
|
|
|
func handleAdminPluginArtifact(w http.ResponseWriter, r *http.Request, rawArtifactID string) {
|
|
if _, ok := requireRole(w, r, adminRoleMember); !ok {
|
|
return
|
|
}
|
|
if pluginsManager == nil {
|
|
adminhttp.WriteAPIError(w, http.StatusServiceUnavailable, "plugin manager is not initialized")
|
|
return
|
|
}
|
|
artifactID, err := adminhttp.PathSegment(rawArtifactID)
|
|
if err != nil {
|
|
adminhttp.WriteAPIError(w, http.StatusBadRequest, err.Error())
|
|
return
|
|
}
|
|
if r.Method != http.MethodGet {
|
|
adminhttp.WriteAPIError(w, http.StatusMethodNotAllowed, "method not allowed")
|
|
return
|
|
}
|
|
artifact, err := pluginsManager.Artifact(r.Context(), artifactID)
|
|
if err != nil {
|
|
writePluginManagerError(w, err)
|
|
return
|
|
}
|
|
adminhttp.WriteJSON(w, http.StatusOK, map[string]any{"artifact": artifact})
|
|
}
|
|
|
|
func handleAdminPluginsList(w http.ResponseWriter, r *http.Request) {
|
|
if _, ok := requireRole(w, r, adminRoleMember); !ok {
|
|
return
|
|
}
|
|
if pluginsManager == nil {
|
|
adminhttp.WriteAPIError(w, http.StatusServiceUnavailable, "plugin manager is not initialized")
|
|
return
|
|
}
|
|
plugins, err := pluginsManager.ListPlugins(r.Context())
|
|
if err != nil {
|
|
adminhttp.WriteAPIError(w, http.StatusInternalServerError, err.Error())
|
|
return
|
|
}
|
|
views, err := pluginViews(r, plugins)
|
|
if err != nil {
|
|
adminhttp.WriteAPIError(w, http.StatusInternalServerError, err.Error())
|
|
return
|
|
}
|
|
adminhttp.WriteJSON(w, http.StatusOK, map[string]any{"plugins": views})
|
|
}
|
|
|
|
func handleAdminPluginItem(w http.ResponseWriter, r *http.Request, rawPluginID string) {
|
|
session, ok := requireRole(w, r, adminRoleMember)
|
|
if !ok {
|
|
return
|
|
}
|
|
if pluginsManager == nil {
|
|
adminhttp.WriteAPIError(w, http.StatusServiceUnavailable, "plugin manager is not initialized")
|
|
return
|
|
}
|
|
if strings.HasSuffix(rawPluginID, "/proxy-connections") {
|
|
handleAdminPluginProxyConnections(w, r, strings.TrimSuffix(rawPluginID, "/proxy-connections"))
|
|
return
|
|
}
|
|
|
|
pluginID, err := adminhttp.PathSegment(rawPluginID)
|
|
if err != nil {
|
|
adminhttp.WriteAPIError(w, http.StatusBadRequest, err.Error())
|
|
return
|
|
}
|
|
|
|
switch r.Method {
|
|
case http.MethodGet:
|
|
plugin, err := pluginsManager.Plugin(r.Context(), pluginID)
|
|
if err != nil {
|
|
writePluginManagerError(w, err)
|
|
return
|
|
}
|
|
view, err := pluginView(r, plugin, true)
|
|
if err != nil {
|
|
adminhttp.WriteAPIError(w, http.StatusInternalServerError, err.Error())
|
|
return
|
|
}
|
|
adminhttp.WriteJSON(w, http.StatusOK, map[string]any{"plugin": view})
|
|
case http.MethodPut:
|
|
if session.Role != adminRoleAdmin {
|
|
adminhttp.WriteAPIError(w, http.StatusForbidden, "forbidden")
|
|
return
|
|
}
|
|
var req adminhttp.PluginDesiredRequest
|
|
if !adminhttp.DecodeJSONRequest(w, r, &req) {
|
|
return
|
|
}
|
|
configJSON, err := pluginConfigJSON(req)
|
|
if err != nil {
|
|
adminhttp.WriteAPIError(w, http.StatusBadRequest, err.Error())
|
|
return
|
|
}
|
|
plugin, err := pluginsManager.SetDesired(r.Context(), session.Username, pluginID, req.ArtifactID, req.DesiredState, configJSON, req.Priority)
|
|
if err != nil {
|
|
recordAudit(r.Context(), session.Username, adminhttp.RequestSourceIP(r), "plugin_desired_update", "plugin", pluginID, false, err.Error())
|
|
writePluginManagerError(w, err)
|
|
return
|
|
}
|
|
recordAuditMetadata(r.Context(), session.Username, adminhttp.RequestSourceIP(r), "plugin_desired_update", "plugin", pluginID, true, "desired state updated", map[string]any{
|
|
"artifact_id": req.ArtifactID,
|
|
"desired_state": plugin.DesiredState,
|
|
"desired_generation": plugin.DesiredGeneration,
|
|
"priority": plugin.Priority,
|
|
})
|
|
adminhttp.WriteJSON(w, http.StatusOK, map[string]any{"plugin": plugin})
|
|
default:
|
|
adminhttp.WriteAPIError(w, http.StatusMethodNotAllowed, "method not allowed")
|
|
}
|
|
}
|
|
|
|
func handleAdminPluginProxyConnections(w http.ResponseWriter, r *http.Request, rawPluginID string) {
|
|
if r.Method != http.MethodGet {
|
|
adminhttp.WriteAPIError(w, http.StatusMethodNotAllowed, "method not allowed")
|
|
return
|
|
}
|
|
pluginID, err := adminhttp.PathSegment(rawPluginID)
|
|
if err != nil {
|
|
adminhttp.WriteAPIError(w, http.StatusBadRequest, err.Error())
|
|
return
|
|
}
|
|
connections, err := pluginsManager.ActiveProxyConnections(r.Context(), pluginID)
|
|
if err != nil {
|
|
adminhttp.WriteAPIError(w, http.StatusInternalServerError, err.Error())
|
|
return
|
|
}
|
|
adminhttp.WriteJSON(w, http.StatusOK, map[string]any{"proxy_connections": connections})
|
|
}
|
|
|
|
func handleAdminPluginConfig(w http.ResponseWriter, r *http.Request, rawSegment string) {
|
|
session, ok := requireRole(w, r, adminRoleMember)
|
|
if !ok {
|
|
return
|
|
}
|
|
if pluginsManager == nil {
|
|
adminhttp.WriteAPIError(w, http.StatusServiceUnavailable, "plugin manager is not initialized")
|
|
return
|
|
}
|
|
pluginID, action, ok := splitPluginSubresource(w, rawSegment, "config")
|
|
if !ok {
|
|
return
|
|
}
|
|
switch {
|
|
case r.Method == http.MethodGet && action == "snapshots":
|
|
snapshots, err := pluginsManager.ListConfigSnapshots(r.Context(), pluginID)
|
|
if err != nil {
|
|
adminhttp.WriteAPIError(w, http.StatusInternalServerError, err.Error())
|
|
return
|
|
}
|
|
adminhttp.WriteJSON(w, http.StatusOK, map[string]any{"snapshots": snapshots})
|
|
case r.Method == http.MethodGet && strings.HasPrefix(action, "snapshots/") && strings.HasSuffix(action, "/diff"):
|
|
parts := strings.Split(action, "/")
|
|
if len(parts) != 3 {
|
|
adminhttp.WriteAPIError(w, http.StatusBadRequest, "invalid snapshot diff path")
|
|
return
|
|
}
|
|
snapshotID, err := strconv.ParseInt(parts[1], 10, 64)
|
|
if err != nil {
|
|
adminhttp.WriteAPIError(w, http.StatusBadRequest, "invalid snapshot id")
|
|
return
|
|
}
|
|
diff, err := pluginsManager.ConfigSnapshotDiff(r.Context(), snapshotID)
|
|
if err != nil {
|
|
writePluginManagerError(w, err)
|
|
return
|
|
}
|
|
if diff.PluginID != pluginID {
|
|
adminhttp.WriteAPIError(w, http.StatusNotFound, "snapshot not found")
|
|
return
|
|
}
|
|
adminhttp.WriteJSON(w, http.StatusOK, map[string]any{"diff": diff})
|
|
case r.Method == http.MethodPost && action == "dry-run":
|
|
if session.Role != adminRoleAdmin {
|
|
adminhttp.WriteAPIError(w, http.StatusForbidden, "forbidden")
|
|
return
|
|
}
|
|
var req adminhttp.PluginConfigRequest
|
|
if !adminhttp.DecodeJSONRequest(w, r, &req) {
|
|
return
|
|
}
|
|
configJSON, err := pluginConfigRequestJSON(req)
|
|
if err != nil {
|
|
adminhttp.WriteAPIError(w, http.StatusBadRequest, err.Error())
|
|
return
|
|
}
|
|
if req.ArtifactID == "" {
|
|
plugin, err := pluginsManager.Plugin(r.Context(), pluginID)
|
|
if err != nil {
|
|
writePluginManagerError(w, err)
|
|
return
|
|
}
|
|
req.ArtifactID = plugin.DesiredArtifactID
|
|
}
|
|
result, err := pluginsManager.DryRunConfig(r.Context(), pluginID, req.ArtifactID, configJSON)
|
|
if err != nil {
|
|
recordAuditMetadata(r.Context(), session.Username, adminhttp.RequestSourceIP(r), "plugin_config_dry_run", "plugin", pluginID, false, err.Error(), map[string]any{
|
|
"artifact_id": req.ArtifactID,
|
|
})
|
|
writePluginManagerError(w, err)
|
|
return
|
|
}
|
|
recordAuditMetadata(r.Context(), session.Username, adminhttp.RequestSourceIP(r), "plugin_config_dry_run", "plugin", pluginID, true, "config dry-run succeeded", map[string]any{
|
|
"artifact_id": req.ArtifactID,
|
|
"restart_required": result.RestartRequired,
|
|
"sensitive_paths": result.SensitivePaths,
|
|
"redacted_diff_json": result.RedactedDiffJSON,
|
|
})
|
|
adminhttp.WriteJSON(w, http.StatusOK, map[string]any{"result": result})
|
|
case r.Method == http.MethodPut && action == "":
|
|
if session.Role != adminRoleAdmin {
|
|
adminhttp.WriteAPIError(w, http.StatusForbidden, "forbidden")
|
|
return
|
|
}
|
|
var req adminhttp.PluginConfigRequest
|
|
if !adminhttp.DecodeJSONRequest(w, r, &req) {
|
|
return
|
|
}
|
|
configJSON, err := pluginConfigRequestJSON(req)
|
|
if err != nil {
|
|
adminhttp.WriteAPIError(w, http.StatusBadRequest, err.Error())
|
|
return
|
|
}
|
|
current, err := pluginsManager.Plugin(r.Context(), pluginID)
|
|
if err != nil {
|
|
writePluginManagerError(w, err)
|
|
return
|
|
}
|
|
if req.ArtifactID == "" {
|
|
req.ArtifactID = current.DesiredArtifactID
|
|
}
|
|
if req.DesiredState == "" {
|
|
req.DesiredState = current.DesiredState
|
|
}
|
|
if req.Priority == 0 {
|
|
req.Priority = current.Priority
|
|
}
|
|
plugin, err := pluginsManager.SetDesired(r.Context(), session.Username, pluginID, req.ArtifactID, req.DesiredState, configJSON, req.Priority)
|
|
if err != nil {
|
|
recordAudit(r.Context(), session.Username, adminhttp.RequestSourceIP(r), "plugin_config_update", "plugin", pluginID, false, err.Error())
|
|
writePluginManagerError(w, err)
|
|
return
|
|
}
|
|
recordAuditMetadata(r.Context(), session.Username, adminhttp.RequestSourceIP(r), "plugin_config_update", "plugin", pluginID, true, "config desired state updated", map[string]any{
|
|
"artifact_id": plugin.DesiredArtifactID,
|
|
"desired_generation": plugin.DesiredGeneration,
|
|
"desired_state": plugin.DesiredState,
|
|
})
|
|
adminhttp.WriteJSON(w, http.StatusOK, map[string]any{"plugin": plugin})
|
|
default:
|
|
adminhttp.WriteAPIError(w, http.StatusMethodNotAllowed, "method not allowed")
|
|
}
|
|
}
|
|
|
|
func handleAdminPluginSecrets(w http.ResponseWriter, r *http.Request, rawSegment string) {
|
|
session, ok := requireRole(w, r, adminRoleMember)
|
|
if !ok {
|
|
return
|
|
}
|
|
if pluginsManager == nil {
|
|
adminhttp.WriteAPIError(w, http.StatusServiceUnavailable, "plugin manager is not initialized")
|
|
return
|
|
}
|
|
pluginID, action, ok := splitPluginSubresource(w, rawSegment, "secrets")
|
|
if !ok {
|
|
return
|
|
}
|
|
switch {
|
|
case r.Method == http.MethodGet && action == "":
|
|
secrets, err := pluginsManager.ListSecrets(r.Context(), pluginID)
|
|
if err != nil {
|
|
adminhttp.WriteAPIError(w, http.StatusInternalServerError, err.Error())
|
|
return
|
|
}
|
|
adminhttp.WriteJSON(w, http.StatusOK, map[string]any{"secrets": secrets})
|
|
case r.Method == http.MethodPost && action == "":
|
|
if session.Role != adminRoleAdmin {
|
|
adminhttp.WriteAPIError(w, http.StatusForbidden, "forbidden")
|
|
return
|
|
}
|
|
var req adminhttp.PluginSecretRequest
|
|
if !adminhttp.DecodeJSONRequest(w, r, &req) {
|
|
return
|
|
}
|
|
secret, err := pluginsManager.UpsertSecret(r.Context(), session.Username, pluginID, req.ArtifactID, req.Name, req.Value, req.ReloadRequired, req.HotReload)
|
|
if err != nil {
|
|
recordAuditMetadata(r.Context(), session.Username, adminhttp.RequestSourceIP(r), "plugin_secret_update", "plugin_secret", "plugin://"+pluginID+"/"+req.Name, false, "secret update failed", map[string]any{
|
|
"error": err.Error(),
|
|
})
|
|
adminhttp.WriteAPIError(w, http.StatusBadRequest, err.Error())
|
|
return
|
|
}
|
|
recordAuditMetadata(r.Context(), session.Username, adminhttp.RequestSourceIP(r), "plugin_secret_update", "plugin_secret", "plugin://"+pluginID+"/"+secret.Name, true, "secret updated", map[string]any{
|
|
"current_version": secret.CurrentVersion,
|
|
"previous_version": secret.PreviousVersion,
|
|
"reload_required": secret.ReloadRequired,
|
|
"hot_reload": secret.HotReload,
|
|
})
|
|
adminhttp.WriteJSON(w, http.StatusOK, map[string]any{"secret": secret})
|
|
default:
|
|
adminhttp.WriteAPIError(w, http.StatusMethodNotAllowed, "method not allowed")
|
|
}
|
|
}
|
|
|
|
func handleAdminPluginRollback(w http.ResponseWriter, r *http.Request, rawSegment string) {
|
|
session, ok := requireRole(w, r, adminRoleAdmin)
|
|
if !ok {
|
|
return
|
|
}
|
|
if pluginsManager == nil {
|
|
adminhttp.WriteAPIError(w, http.StatusServiceUnavailable, "plugin manager is not initialized")
|
|
return
|
|
}
|
|
if r.Method != http.MethodPost {
|
|
adminhttp.WriteAPIError(w, http.StatusMethodNotAllowed, "method not allowed")
|
|
return
|
|
}
|
|
pluginID, action, ok := splitPluginSubresource(w, rawSegment, "rollback")
|
|
if !ok {
|
|
return
|
|
}
|
|
var req adminhttp.PluginRollbackRequest
|
|
if !adminhttp.DecodeJSONRequest(w, r, &req) {
|
|
return
|
|
}
|
|
var plugin pluginmanager.PluginRecord
|
|
var err error
|
|
switch action {
|
|
case "artifact":
|
|
plugin, err = pluginsManager.RollbackArtifact(r.Context(), session.Username, pluginID, req.ArtifactID)
|
|
case "config":
|
|
plugin, err = pluginsManager.RollbackConfigSnapshot(r.Context(), session.Username, req.SnapshotID, req.FullDesired)
|
|
default:
|
|
adminhttp.WriteAPIError(w, http.StatusBadRequest, "unknown rollback action")
|
|
return
|
|
}
|
|
if err != nil {
|
|
recordAudit(r.Context(), session.Username, adminhttp.RequestSourceIP(r), "plugin_rollback_"+action, "plugin", pluginID, false, err.Error())
|
|
writePluginManagerError(w, err)
|
|
return
|
|
}
|
|
recordAuditMetadata(r.Context(), session.Username, adminhttp.RequestSourceIP(r), "plugin_rollback_"+action, "plugin", pluginID, true, "rollback desired state updated", map[string]any{
|
|
"desired_generation": plugin.DesiredGeneration,
|
|
"active_changed": false,
|
|
})
|
|
adminhttp.WriteJSON(w, http.StatusOK, map[string]any{"plugin": plugin})
|
|
}
|
|
|
|
func handleAdminPluginOperations(w http.ResponseWriter, r *http.Request, rawSegment string) {
|
|
session, ok := requireRole(w, r, adminRoleMember)
|
|
if !ok {
|
|
return
|
|
}
|
|
if pluginsManager == nil {
|
|
adminhttp.WriteAPIError(w, http.StatusServiceUnavailable, "plugin manager is not initialized")
|
|
return
|
|
}
|
|
pluginID, action, ok := splitPluginSubresource(w, rawSegment, "operations")
|
|
if !ok {
|
|
return
|
|
}
|
|
switch {
|
|
case r.Method == http.MethodGet && action == "":
|
|
snapshot, err := pluginsManager.OperationsSnapshot(r.Context(), pluginID)
|
|
if err != nil {
|
|
adminhttp.WriteAPIError(w, http.StatusInternalServerError, err.Error())
|
|
return
|
|
}
|
|
adminhttp.WriteJSON(w, http.StatusOK, map[string]any{"operations": snapshot})
|
|
case r.Method == http.MethodPost && strings.HasPrefix(action, "tasks/") && strings.HasSuffix(action, "/trigger"):
|
|
if session.Role != adminRoleAdmin {
|
|
adminhttp.WriteAPIError(w, http.StatusForbidden, "forbidden")
|
|
return
|
|
}
|
|
parts := strings.Split(action, "/")
|
|
if len(parts) != 3 {
|
|
adminhttp.WriteAPIError(w, http.StatusBadRequest, "invalid task trigger path")
|
|
return
|
|
}
|
|
taskID, err := adminhttp.PathSegment(parts[1])
|
|
if err != nil {
|
|
adminhttp.WriteAPIError(w, http.StatusBadRequest, err.Error())
|
|
return
|
|
}
|
|
var req struct {
|
|
ConfirmToken string `json:"confirm_token"`
|
|
}
|
|
if !adminhttp.DecodeJSONRequest(w, r, &req) {
|
|
return
|
|
}
|
|
task, err := pluginsManager.TriggerBackgroundTask(r.Context(), session.Username, pluginID, taskID, req.ConfirmToken)
|
|
if err != nil {
|
|
recordAudit(r.Context(), session.Username, adminhttp.RequestSourceIP(r), "plugin_background_task_trigger", "plugin", pluginID, false, err.Error())
|
|
adminhttp.WriteAPIError(w, http.StatusBadRequest, err.Error())
|
|
return
|
|
}
|
|
recordAuditMetadata(r.Context(), session.Username, adminhttp.RequestSourceIP(r), "plugin_background_task_trigger", "plugin", pluginID, true, "background task triggered", map[string]any{"task_id": taskID})
|
|
adminhttp.WriteJSON(w, http.StatusOK, map[string]any{"task": task})
|
|
case r.Method == http.MethodGet && action == "diagnostic":
|
|
data, summary, err := pluginsManager.DiagnosticPackage(r.Context(), session.Username, pluginID)
|
|
if err != nil {
|
|
adminhttp.WriteAPIError(w, http.StatusInternalServerError, err.Error())
|
|
return
|
|
}
|
|
var body any
|
|
if err := json.Unmarshal(data, &body); err != nil {
|
|
body = map[string]any{"error": "diagnostic package could not be decoded"}
|
|
}
|
|
adminhttp.WriteJSON(w, http.StatusOK, map[string]any{"diagnostic": body, "summary": summary})
|
|
case (r.Method == http.MethodGet || r.Method == http.MethodPost) && action == "gc":
|
|
if r.Method == http.MethodPost && session.Role != adminRoleAdmin {
|
|
adminhttp.WriteAPIError(w, http.StatusForbidden, "forbidden")
|
|
return
|
|
}
|
|
dryRun := r.Method == http.MethodGet
|
|
candidates, err := pluginsManager.RunOperationsGC(r.Context(), session.Username, pluginID, dryRun)
|
|
if err != nil {
|
|
adminhttp.WriteAPIError(w, http.StatusInternalServerError, err.Error())
|
|
return
|
|
}
|
|
adminhttp.WriteJSON(w, http.StatusOK, map[string]any{"dry_run": dryRun, "candidates": candidates})
|
|
default:
|
|
adminhttp.WriteAPIError(w, http.StatusMethodNotAllowed, "method not allowed")
|
|
}
|
|
}
|
|
|
|
func handleAdminPluginOperationsGC(w http.ResponseWriter, r *http.Request) {
|
|
session, ok := requireRole(w, r, adminRoleMember)
|
|
if !ok {
|
|
return
|
|
}
|
|
if pluginsManager == nil {
|
|
adminhttp.WriteAPIError(w, http.StatusServiceUnavailable, "plugin manager is not initialized")
|
|
return
|
|
}
|
|
if r.Method == http.MethodPost && session.Role != adminRoleAdmin {
|
|
adminhttp.WriteAPIError(w, http.StatusForbidden, "forbidden")
|
|
return
|
|
}
|
|
dryRun := r.Method == http.MethodGet
|
|
candidates, err := pluginsManager.RunOperationsGC(r.Context(), session.Username, r.URL.Query().Get("plugin_id"), dryRun)
|
|
if err != nil {
|
|
adminhttp.WriteAPIError(w, http.StatusInternalServerError, err.Error())
|
|
return
|
|
}
|
|
adminhttp.WriteJSON(w, http.StatusOK, map[string]any{"dry_run": dryRun, "candidates": candidates})
|
|
}
|
|
|
|
func handleAdminPluginDiagnostics(w http.ResponseWriter, r *http.Request, rawPluginID string) {
|
|
session, ok := requireRole(w, r, adminRoleAdmin)
|
|
if !ok {
|
|
return
|
|
}
|
|
if pluginsManager == nil {
|
|
adminhttp.WriteAPIError(w, http.StatusServiceUnavailable, "plugin manager is not initialized")
|
|
return
|
|
}
|
|
if r.Method != http.MethodGet {
|
|
adminhttp.WriteAPIError(w, http.StatusMethodNotAllowed, "method not allowed")
|
|
return
|
|
}
|
|
pluginID, err := adminhttp.PathSegment(rawPluginID)
|
|
if err != nil {
|
|
adminhttp.WriteAPIError(w, http.StatusBadRequest, err.Error())
|
|
return
|
|
}
|
|
data, summary, err := pluginsManager.DiagnosticPackage(r.Context(), session.Username, pluginID)
|
|
if err != nil {
|
|
writePluginManagerError(w, err)
|
|
return
|
|
}
|
|
var body any
|
|
if err := json.Unmarshal(data, &body); err != nil {
|
|
body = map[string]any{"error": "diagnostic package could not be decoded"}
|
|
}
|
|
adminhttp.WriteJSON(w, http.StatusOK, map[string]any{"diagnostic": body, "summary": summary})
|
|
}
|
|
|
|
func handleAdminPluginAction(w http.ResponseWriter, r *http.Request, rawSegment string) {
|
|
session, ok := requireRole(w, r, adminRoleAdmin)
|
|
if !ok {
|
|
return
|
|
}
|
|
if pluginsManager == nil {
|
|
adminhttp.WriteAPIError(w, http.StatusServiceUnavailable, "plugin manager is not initialized")
|
|
return
|
|
}
|
|
if r.Method != http.MethodPost {
|
|
adminhttp.WriteAPIError(w, http.StatusMethodNotAllowed, "method not allowed")
|
|
return
|
|
}
|
|
parts := strings.Split(rawSegment, "/")
|
|
if len(parts) != 2 {
|
|
adminhttp.WriteAPIError(w, http.StatusBadRequest, "invalid plugin action")
|
|
return
|
|
}
|
|
pluginID, err := adminhttp.PathSegment(parts[0])
|
|
if err != nil {
|
|
adminhttp.WriteAPIError(w, http.StatusBadRequest, err.Error())
|
|
return
|
|
}
|
|
action, err := adminhttp.PathSegment(parts[1])
|
|
if err != nil {
|
|
adminhttp.WriteAPIError(w, http.StatusBadRequest, err.Error())
|
|
return
|
|
}
|
|
|
|
var plugin pluginmanager.PluginRecord
|
|
switch action {
|
|
case "load":
|
|
plugin, err = pluginsManager.Load(r.Context(), session.Username, pluginID)
|
|
case "enable":
|
|
plugin, err = pluginsManager.Enable(r.Context(), session.Username, pluginID)
|
|
case "disable":
|
|
plugin, err = pluginsManager.Disable(r.Context(), session.Username, pluginID)
|
|
case "delete":
|
|
err = pluginsManager.Delete(r.Context(), session.Username, pluginID)
|
|
default:
|
|
adminhttp.WriteAPIError(w, http.StatusBadRequest, "unknown plugin action")
|
|
return
|
|
}
|
|
if err != nil {
|
|
recordAudit(r.Context(), session.Username, adminhttp.RequestSourceIP(r), "plugin_"+action, "plugin", pluginID, false, err.Error())
|
|
writePluginManagerError(w, err)
|
|
return
|
|
}
|
|
recordAudit(r.Context(), session.Username, adminhttp.RequestSourceIP(r), "plugin_"+action, "plugin", pluginID, true, "plugin "+action+" succeeded")
|
|
if action == "delete" {
|
|
adminhttp.WriteJSON(w, http.StatusOK, map[string]any{"ok": true})
|
|
return
|
|
}
|
|
adminhttp.WriteJSON(w, http.StatusOK, map[string]any{"plugin": plugin})
|
|
}
|
|
|
|
func handleAdminPluginDraining(w http.ResponseWriter, r *http.Request, rawPluginID string) {
|
|
session, ok := requireRole(w, r, adminRoleAdmin)
|
|
if !ok {
|
|
return
|
|
}
|
|
if pluginsManager == nil {
|
|
adminhttp.WriteAPIError(w, http.StatusServiceUnavailable, "plugin manager is not initialized")
|
|
return
|
|
}
|
|
if r.Method != http.MethodPost {
|
|
adminhttp.WriteAPIError(w, http.StatusMethodNotAllowed, "method not allowed")
|
|
return
|
|
}
|
|
pluginID, err := adminhttp.PathSegment(rawPluginID)
|
|
if err != nil {
|
|
adminhttp.WriteAPIError(w, http.StatusBadRequest, err.Error())
|
|
return
|
|
}
|
|
closed, err := pluginsManager.ForceCloseDraining(r.Context(), session.Username, pluginID)
|
|
if err != nil {
|
|
recordAudit(r.Context(), session.Username, adminhttp.RequestSourceIP(r), "plugin_force_close_draining", "plugin", pluginID, false, err.Error())
|
|
writePluginManagerError(w, err)
|
|
return
|
|
}
|
|
recordAuditMetadata(r.Context(), session.Username, adminhttp.RequestSourceIP(r), "plugin_force_close_draining", "plugin", pluginID, true, "draining protocol-proxy connections force closed", map[string]any{
|
|
"closed": closed,
|
|
})
|
|
adminhttp.WriteJSON(w, http.StatusOK, map[string]any{"closed": closed})
|
|
}
|
|
|
|
func handleAdminPluginDispatchPlan(w http.ResponseWriter, r *http.Request) {
|
|
session, ok := requireRole(w, r, adminRoleMember)
|
|
if !ok {
|
|
return
|
|
}
|
|
if pluginsManager == nil {
|
|
adminhttp.WriteAPIError(w, http.StatusServiceUnavailable, "plugin manager is not initialized")
|
|
return
|
|
}
|
|
switch r.Method {
|
|
case http.MethodGet:
|
|
adminhttp.WriteJSON(w, http.StatusOK, map[string]any{"dispatch_plan": pluginsManager.DispatchPlan(r.Context())})
|
|
case http.MethodPost:
|
|
if session.Role != adminRoleAdmin {
|
|
adminhttp.WriteAPIError(w, http.StatusForbidden, "forbidden")
|
|
return
|
|
}
|
|
var req struct {
|
|
Action string `json:"action"`
|
|
}
|
|
if !adminhttp.DecodeJSONRequest(w, r, &req) {
|
|
return
|
|
}
|
|
switch req.Action {
|
|
case "refresh-routes":
|
|
cache := pluginsManager.RefreshRouteProviders(r.Context(), session.Username)
|
|
recordAuditMetadata(r.Context(), session.Username, adminhttp.RequestSourceIP(r), "plugin_route_provider_refresh", "plugin_dispatch", "", true, "route provider cache refreshed", map[string]any{"cache_entries": len(cache)})
|
|
adminhttp.WriteJSON(w, http.StatusOK, map[string]any{"route_cache": cache})
|
|
case "replay-subscribers":
|
|
count := pluginsManager.ReplaySubscriberDeadLetters(r.Context(), session.Username)
|
|
recordAuditMetadata(r.Context(), session.Username, adminhttp.RequestSourceIP(r), "plugin_event_subscriber_replay", "plugin_dispatch", "", true, "event subscriber dead letters replay requested", map[string]any{"replayed": count})
|
|
adminhttp.WriteJSON(w, http.StatusOK, map[string]any{"replayed": count})
|
|
case "drop-subscriber-dead-letter":
|
|
count := pluginsManager.DropSubscriberDeadLetters(r.Context(), session.Username)
|
|
recordAuditMetadata(r.Context(), session.Username, adminhttp.RequestSourceIP(r), "plugin_event_subscriber_drop", "plugin_dispatch", "", true, "event subscriber dead letters dropped", map[string]any{"dropped": count})
|
|
adminhttp.WriteJSON(w, http.StatusOK, map[string]any{"dropped": count})
|
|
default:
|
|
adminhttp.WriteAPIError(w, http.StatusBadRequest, "unknown dispatch action")
|
|
}
|
|
default:
|
|
adminhttp.WriteAPIError(w, http.StatusMethodNotAllowed, "method not allowed")
|
|
}
|
|
}
|
|
|
|
func handleAdminPluginGovernance(w http.ResponseWriter, r *http.Request, rawSegment string) {
|
|
session, ok := requireRole(w, r, adminRoleMember)
|
|
if !ok {
|
|
return
|
|
}
|
|
if pluginsManager == nil {
|
|
adminhttp.WriteAPIError(w, http.StatusServiceUnavailable, "plugin manager is not initialized")
|
|
return
|
|
}
|
|
pluginID, action, ok := splitPluginSubresource(w, rawSegment, "governance")
|
|
if !ok {
|
|
return
|
|
}
|
|
switch {
|
|
case r.Method == http.MethodGet && action == "":
|
|
status, err := pluginsManager.GovernanceStatus(r.Context(), pluginID, r.URL.Query().Get("artifact_id"), r.URL.Query().Get("profile"))
|
|
if err != nil {
|
|
writePluginManagerError(w, err)
|
|
return
|
|
}
|
|
adminhttp.WriteJSON(w, http.StatusOK, map[string]any{"governance": status})
|
|
case r.Method == http.MethodPost && action == "review":
|
|
if session.Role != adminRoleAdmin {
|
|
adminhttp.WriteAPIError(w, http.StatusForbidden, "forbidden")
|
|
return
|
|
}
|
|
var req pluginmanager.GovernanceReviewRequest
|
|
if !adminhttp.DecodeJSONRequest(w, r, &req) {
|
|
return
|
|
}
|
|
review, err := pluginsManager.CreateReview(r.Context(), session.Username, pluginID, req)
|
|
if err != nil {
|
|
recordAudit(r.Context(), session.Username, adminhttp.RequestSourceIP(r), "plugin_governance_review", "plugin", pluginID, false, err.Error())
|
|
writePluginManagerError(w, err)
|
|
return
|
|
}
|
|
recordAuditMetadata(r.Context(), session.Username, adminhttp.RequestSourceIP(r), "plugin_governance_review", "plugin", pluginID, true, "governance review recorded", map[string]any{
|
|
"artifact_id": review.ArtifactID,
|
|
"profile": review.Profile,
|
|
"policy_hash": review.PolicyHash,
|
|
"decision": review.Decision,
|
|
})
|
|
adminhttp.WriteJSON(w, http.StatusOK, map[string]any{"review": review})
|
|
case r.Method == http.MethodPost && action == "override":
|
|
if session.Role != adminRoleAdmin {
|
|
adminhttp.WriteAPIError(w, http.StatusForbidden, "forbidden")
|
|
return
|
|
}
|
|
var req pluginmanager.WarningOverrideRequest
|
|
if !adminhttp.DecodeJSONRequest(w, r, &req) {
|
|
return
|
|
}
|
|
override, err := pluginsManager.CreateWarningOverride(r.Context(), session.Username, pluginID, req)
|
|
if err != nil {
|
|
recordAudit(r.Context(), session.Username, adminhttp.RequestSourceIP(r), "plugin_governance_override", "plugin", pluginID, false, err.Error())
|
|
writePluginManagerError(w, err)
|
|
return
|
|
}
|
|
recordAuditMetadata(r.Context(), session.Username, adminhttp.RequestSourceIP(r), "plugin_governance_override", "plugin", pluginID, true, "governance warning override recorded", map[string]any{
|
|
"artifact_id": override.ArtifactID,
|
|
"profile": override.Profile,
|
|
"action": override.Action,
|
|
"expires_at": override.ExpiresAt,
|
|
})
|
|
adminhttp.WriteJSON(w, http.StatusOK, map[string]any{"override": override})
|
|
case r.Method == http.MethodPost && action == "preflight":
|
|
if session.Role != adminRoleAdmin {
|
|
adminhttp.WriteAPIError(w, http.StatusForbidden, "forbidden")
|
|
return
|
|
}
|
|
var req pluginmanager.PreflightRequest
|
|
if !adminhttp.DecodeJSONRequest(w, r, &req) {
|
|
return
|
|
}
|
|
result, err := pluginsManager.RunPreflight(r.Context(), session.Username, pluginID, req)
|
|
if err != nil {
|
|
recordAudit(r.Context(), session.Username, adminhttp.RequestSourceIP(r), "plugin_governance_preflight", "plugin", pluginID, false, err.Error())
|
|
writePluginManagerError(w, err)
|
|
return
|
|
}
|
|
recordAuditMetadata(r.Context(), session.Username, adminhttp.RequestSourceIP(r), "plugin_governance_preflight", "plugin", pluginID, result.OK, "governance preflight completed", map[string]any{"profile": result.Profile})
|
|
adminhttp.WriteJSON(w, http.StatusOK, map[string]any{"preflight": result})
|
|
case r.Method == http.MethodPost && action == "self-test":
|
|
if session.Role != adminRoleAdmin {
|
|
adminhttp.WriteAPIError(w, http.StatusForbidden, "forbidden")
|
|
return
|
|
}
|
|
var req pluginmanager.SelfTestRequest
|
|
if !adminhttp.DecodeJSONRequest(w, r, &req) {
|
|
return
|
|
}
|
|
result, err := pluginsManager.RunSelfTest(r.Context(), session.Username, pluginID, req)
|
|
if err != nil {
|
|
recordAudit(r.Context(), session.Username, adminhttp.RequestSourceIP(r), "plugin_governance_self_test", "plugin", pluginID, false, err.Error())
|
|
writePluginManagerError(w, err)
|
|
return
|
|
}
|
|
recordAuditMetadata(r.Context(), session.Username, adminhttp.RequestSourceIP(r), "plugin_governance_self_test", "plugin", pluginID, result.OK, "governance self-test completed", map[string]any{"profile": result.Profile})
|
|
adminhttp.WriteJSON(w, http.StatusOK, map[string]any{"self_test": result})
|
|
case r.Method == http.MethodPost && action == "benchmark":
|
|
if session.Role != adminRoleAdmin {
|
|
adminhttp.WriteAPIError(w, http.StatusForbidden, "forbidden")
|
|
return
|
|
}
|
|
var req pluginmanager.BenchmarkRequest
|
|
if !adminhttp.DecodeJSONRequest(w, r, &req) {
|
|
return
|
|
}
|
|
benchmark, err := pluginsManager.SaveBenchmark(r.Context(), session.Username, req)
|
|
if err != nil {
|
|
recordAudit(r.Context(), session.Username, adminhttp.RequestSourceIP(r), "plugin_governance_benchmark", "plugin", pluginID, false, err.Error())
|
|
writePluginManagerError(w, err)
|
|
return
|
|
}
|
|
recordAuditMetadata(r.Context(), session.Username, adminhttp.RequestSourceIP(r), "plugin_governance_benchmark", "plugin", pluginID, true, "governance benchmark recorded", map[string]any{
|
|
"artifact_id": benchmark.ArtifactID,
|
|
"profile": benchmark.Profile,
|
|
"baseline_diff": benchmark.BaselineDiff,
|
|
})
|
|
adminhttp.WriteJSON(w, http.StatusOK, map[string]any{"benchmark": benchmark})
|
|
default:
|
|
adminhttp.WriteAPIError(w, http.StatusMethodNotAllowed, "method not allowed")
|
|
}
|
|
}
|
|
|
|
func handleAdminPluginAdvisories(w http.ResponseWriter, r *http.Request) {
|
|
session, ok := requireRole(w, r, adminRoleMember)
|
|
if !ok {
|
|
return
|
|
}
|
|
if pluginsManager == nil {
|
|
adminhttp.WriteAPIError(w, http.StatusServiceUnavailable, "plugin manager is not initialized")
|
|
return
|
|
}
|
|
switch r.Method {
|
|
case http.MethodGet:
|
|
advisories, err := pluginsManager.ListAdvisories(r.Context(), r.URL.Query().Get("plugin_id"))
|
|
if err != nil {
|
|
adminhttp.WriteAPIError(w, http.StatusInternalServerError, err.Error())
|
|
return
|
|
}
|
|
adminhttp.WriteJSON(w, http.StatusOK, map[string]any{"advisories": advisories})
|
|
case http.MethodPost:
|
|
if session.Role != adminRoleAdmin {
|
|
adminhttp.WriteAPIError(w, http.StatusForbidden, "forbidden")
|
|
return
|
|
}
|
|
var req pluginmanager.AdvisoryRequest
|
|
if !adminhttp.DecodeJSONRequest(w, r, &req) {
|
|
return
|
|
}
|
|
advisory, err := pluginsManager.UpsertAdvisory(r.Context(), session.Username, req)
|
|
if err != nil {
|
|
recordAudit(r.Context(), session.Username, adminhttp.RequestSourceIP(r), "plugin_governance_advisory", "plugin_advisory", req.AdvisoryID, false, err.Error())
|
|
writePluginManagerError(w, err)
|
|
return
|
|
}
|
|
recordAuditMetadata(r.Context(), session.Username, adminhttp.RequestSourceIP(r), "plugin_governance_advisory", "plugin_advisory", advisory.AdvisoryID, true, "plugin advisory upserted", map[string]any{
|
|
"action": advisory.Action,
|
|
"status": advisory.Status,
|
|
"artifact_sha256": advisory.ArtifactSHA256,
|
|
"plugin_id": advisory.PluginID,
|
|
})
|
|
adminhttp.WriteJSON(w, http.StatusOK, map[string]any{"advisory": advisory})
|
|
default:
|
|
adminhttp.WriteAPIError(w, http.StatusMethodNotAllowed, "method not allowed")
|
|
}
|
|
}
|
|
|
|
func receivePluginArtifact(r *http.Request, actor string) (pluginmanager.ArtifactRecord, error) {
|
|
if err := r.ParseMultipartForm(64 << 20); err != nil {
|
|
return pluginmanager.ArtifactRecord{}, err
|
|
}
|
|
file, header, err := r.FormFile("artifact")
|
|
if err != nil {
|
|
return pluginmanager.ArtifactRecord{}, err
|
|
}
|
|
defer file.Close()
|
|
|
|
tmp, err := os.CreateTemp("", "mc-gateway-plugin-*.mcgp")
|
|
if err != nil {
|
|
return pluginmanager.ArtifactRecord{}, err
|
|
}
|
|
tmpPath := tmp.Name()
|
|
defer os.Remove(tmpPath)
|
|
defer tmp.Close()
|
|
|
|
if _, err := tmp.ReadFrom(file); err != nil {
|
|
return pluginmanager.ArtifactRecord{}, err
|
|
}
|
|
if err := tmp.Close(); err != nil {
|
|
return pluginmanager.ArtifactRecord{}, err
|
|
}
|
|
|
|
upload := pluginmanager.ArtifactUpload{
|
|
SourcePath: tmpPath,
|
|
FileName: filepath.Base(header.Filename),
|
|
Actor: actor,
|
|
}
|
|
manifest, err := readPackageManifest(tmpPath)
|
|
if err != nil {
|
|
return pluginmanager.ArtifactRecord{}, err
|
|
}
|
|
if manifest.ArtifactType == pluginmanager.ArtifactTypeSource {
|
|
return pluginsManager.UploadSource(r.Context(), upload)
|
|
}
|
|
return pluginsManager.UploadArtifact(r.Context(), upload)
|
|
}
|
|
|
|
func receivePluginSource(r *http.Request, actor string) (pluginmanager.ArtifactRecord, error) {
|
|
if err := r.ParseMultipartForm(64 << 20); err != nil {
|
|
return pluginmanager.ArtifactRecord{}, err
|
|
}
|
|
file, header, err := r.FormFile("artifact")
|
|
if err != nil {
|
|
file, header, err = r.FormFile("source")
|
|
}
|
|
if err != nil {
|
|
return pluginmanager.ArtifactRecord{}, err
|
|
}
|
|
defer file.Close()
|
|
|
|
tmp, err := os.CreateTemp("", "mc-gateway-plugin-source-*.mcgp")
|
|
if err != nil {
|
|
return pluginmanager.ArtifactRecord{}, err
|
|
}
|
|
tmpPath := tmp.Name()
|
|
defer os.Remove(tmpPath)
|
|
defer tmp.Close()
|
|
|
|
if _, err := tmp.ReadFrom(file); err != nil {
|
|
return pluginmanager.ArtifactRecord{}, err
|
|
}
|
|
if err := tmp.Close(); err != nil {
|
|
return pluginmanager.ArtifactRecord{}, err
|
|
}
|
|
|
|
return pluginsManager.UploadSource(r.Context(), pluginmanager.ArtifactUpload{
|
|
SourcePath: tmpPath,
|
|
FileName: filepath.Base(header.Filename),
|
|
Actor: actor,
|
|
})
|
|
}
|
|
|
|
func pluginConfigJSON(req adminhttp.PluginDesiredRequest) (string, error) {
|
|
if strings.TrimSpace(req.ConfigJSON) != "" {
|
|
if !json.Valid([]byte(req.ConfigJSON)) {
|
|
return "", errors.New("config_json must be valid JSON")
|
|
}
|
|
return req.ConfigJSON, nil
|
|
}
|
|
if req.Config == nil {
|
|
return "{}", nil
|
|
}
|
|
data, err := json.Marshal(req.Config)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return string(data), nil
|
|
}
|
|
|
|
func pluginConfigRequestJSON(req adminhttp.PluginConfigRequest) (string, error) {
|
|
if strings.TrimSpace(req.ConfigJSON) != "" {
|
|
if !json.Valid([]byte(req.ConfigJSON)) {
|
|
return "", errors.New("config_json must be valid JSON")
|
|
}
|
|
return req.ConfigJSON, nil
|
|
}
|
|
if req.Config == nil {
|
|
return "{}", nil
|
|
}
|
|
data, err := json.Marshal(req.Config)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return string(data), nil
|
|
}
|
|
|
|
func splitPluginSubresource(w http.ResponseWriter, rawSegment, resource string) (pluginID, action string, ok bool) {
|
|
parts := strings.Split(rawSegment, "/")
|
|
if len(parts) < 2 || parts[1] != resource {
|
|
adminhttp.WriteAPIError(w, http.StatusBadRequest, "invalid plugin "+resource+" path")
|
|
return "", "", false
|
|
}
|
|
id, err := adminhttp.PathSegment(parts[0])
|
|
if err != nil {
|
|
adminhttp.WriteAPIError(w, http.StatusBadRequest, err.Error())
|
|
return "", "", false
|
|
}
|
|
if len(parts) > 2 {
|
|
action = strings.Join(parts[2:], "/")
|
|
}
|
|
return id, action, true
|
|
}
|
|
|
|
func pluginViews(r *http.Request, plugins []pluginmanager.PluginRecord) ([]map[string]any, error) {
|
|
views := make([]map[string]any, 0, len(plugins))
|
|
for _, plugin := range plugins {
|
|
view, err := pluginView(r, plugin, false)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
views = append(views, view)
|
|
}
|
|
return views, nil
|
|
}
|
|
|
|
func pluginView(r *http.Request, plugin pluginmanager.PluginRecord, detail bool) (map[string]any, error) {
|
|
artifacts, err := pluginsManager.ListArtifacts(r.Context(), plugin.ID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
builds, err := pluginsManager.ListBuilds(r.Context(), plugin.ID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
secrets, err := pluginsManager.ListSecrets(r.Context(), plugin.ID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
snapshots, err := pluginsManager.ListConfigSnapshots(r.Context(), plugin.ID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
artifactByID := make(map[string]pluginmanager.ArtifactRecord, len(artifacts))
|
|
for _, artifact := range artifacts {
|
|
artifactByID[artifact.ID] = artifact
|
|
}
|
|
desiredArtifact := artifactByID[plugin.DesiredArtifactID]
|
|
activeArtifact := artifactByID[plugin.ActiveArtifactID]
|
|
loadedArtifact := artifactByID[plugin.LoadedArtifactID]
|
|
manifest := pluginManifest(desiredArtifact)
|
|
view := map[string]any{
|
|
"id": plugin.ID,
|
|
"name": manifest.Name,
|
|
"version": desiredArtifact.Version,
|
|
"artifact_type": desiredArtifact.ArtifactType,
|
|
"runtime_type": desiredArtifact.RuntimeType,
|
|
"desired_state": plugin.DesiredState,
|
|
"runtime_state": plugin.RuntimeState,
|
|
"desired_artifact_id": plugin.DesiredArtifactID,
|
|
"active_artifact_id": plugin.ActiveArtifactID,
|
|
"loaded_artifact_id": plugin.LoadedArtifactID,
|
|
"desired_artifact": desiredArtifact,
|
|
"active_artifact": activeArtifact,
|
|
"loaded_artifact": loadedArtifact,
|
|
"extension_points": jsonArrayString(desiredArtifact.ExtensionPointsJSON),
|
|
"priority": plugin.Priority,
|
|
"scope": pluginScope(manifest),
|
|
"rollout": pluginRollout(manifest),
|
|
"restart_required": pluginRestartRequired(plugin),
|
|
"health": pluginHealth(plugin),
|
|
"last_error": plugin.LastError,
|
|
"runtime_summary": jsonObjectString(plugin.RuntimeSummaryJSON),
|
|
"dispatch_summary": jsonArrayString(plugin.DispatchSummaryJSON),
|
|
"capabilities_summary": jsonObjectString(desiredArtifact.CapabilitiesSummaryJSON),
|
|
"extension_status": pluginExtensionStatus(r.Context(), plugin.ID),
|
|
"minecraft": pluginMinecraftSummary(desiredArtifact),
|
|
"config_json": plugin.ConfigJSON,
|
|
"config_schema": jsonObjectString(manifestConfigSchemaString(manifest)),
|
|
"secrets": secrets,
|
|
"snapshots": snapshots,
|
|
"builds": builds,
|
|
"artifacts": artifacts,
|
|
"updated_at": plugin.UpdatedAt,
|
|
}
|
|
if detail {
|
|
view["manifest"] = manifest
|
|
view["operations_path"] = "/plugin-artifacts?plugin_id=" + plugin.ID
|
|
governance, err := pluginsManager.GovernanceStatus(r.Context(), plugin.ID, plugin.DesiredArtifactID, pluginsManager.PolicyProfile())
|
|
if err != nil {
|
|
view["governance_error"] = err.Error()
|
|
} else {
|
|
view["governance"] = governance
|
|
}
|
|
view["active_proxy_connections"] = activeProxyConnections(plugin)
|
|
connections, err := pluginsManager.ActiveProxyConnections(r.Context(), plugin.ID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
view["proxy_connections"] = connections
|
|
}
|
|
return view, nil
|
|
}
|
|
|
|
func pluginExtensionStatus(ctx context.Context, pluginID string) map[string]any {
|
|
plan := pluginsManager.DispatchPlan(ctx)
|
|
filter := func(items []pluginmanager.DispatchHandlerSummary) []pluginmanager.DispatchHandlerSummary {
|
|
var out []pluginmanager.DispatchHandlerSummary
|
|
for _, item := range items {
|
|
if item.PluginID == pluginID {
|
|
out = append(out, item)
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
var providers []pluginmanager.ProviderSummary
|
|
for _, provider := range plan.Providers {
|
|
if provider.PluginID == pluginID {
|
|
providers = append(providers, provider)
|
|
}
|
|
}
|
|
return map[string]any{
|
|
"routes": filter(plan.Routes),
|
|
"statuses": filter(plan.Statuses),
|
|
"middleware": filter(plan.Middleware),
|
|
"subscribers": filter(plan.Subscribers),
|
|
"providers": providers,
|
|
"route_cache": plan.RouteCache,
|
|
}
|
|
}
|
|
|
|
func pluginManifest(artifact pluginmanager.ArtifactRecord) pluginmanager.Manifest {
|
|
var manifest pluginmanager.Manifest
|
|
_ = json.Unmarshal([]byte(artifact.MetadataJSON), &manifest)
|
|
return manifest
|
|
}
|
|
|
|
func manifestConfigSchemaString(manifest pluginmanager.Manifest) string {
|
|
if len(manifest.ConfigSchema) == 0 {
|
|
return "{}"
|
|
}
|
|
return string(manifest.ConfigSchema)
|
|
}
|
|
|
|
func pluginScope(manifest pluginmanager.Manifest) any {
|
|
var caps map[string]any
|
|
if len(manifest.Capabilities) == 0 || json.Unmarshal(manifest.Capabilities, &caps) != nil {
|
|
return map[string]any{"type": "global"}
|
|
}
|
|
if scope, ok := caps["scope"]; ok {
|
|
return scope
|
|
}
|
|
return map[string]any{"type": "global"}
|
|
}
|
|
|
|
func pluginRollout(manifest pluginmanager.Manifest) any {
|
|
var caps map[string]any
|
|
if len(manifest.Capabilities) == 0 || json.Unmarshal(manifest.Capabilities, &caps) != nil {
|
|
return map[string]any{"mode": "all"}
|
|
}
|
|
if rollout, ok := caps["rollout"]; ok {
|
|
return rollout
|
|
}
|
|
return map[string]any{"mode": "all"}
|
|
}
|
|
|
|
func pluginHealth(plugin pluginmanager.PluginRecord) string {
|
|
if plugin.LastError != "" || plugin.RuntimeState == pluginmanager.RuntimeFailed {
|
|
return "error"
|
|
}
|
|
if plugin.RuntimeState == pluginmanager.RuntimeEnabled {
|
|
return "healthy"
|
|
}
|
|
if plugin.RuntimeState == pluginmanager.RuntimeDraining {
|
|
return "draining"
|
|
}
|
|
return "inactive"
|
|
}
|
|
|
|
func pluginRestartRequired(plugin pluginmanager.PluginRecord) bool {
|
|
return plugin.LoadedArtifactID != "" && plugin.DesiredArtifactID != "" && plugin.LoadedArtifactID != plugin.DesiredArtifactID
|
|
}
|
|
|
|
func pluginMinecraftSummary(artifact pluginmanager.ArtifactRecord) any {
|
|
var summary pluginmanager.CapabilitySummary
|
|
if json.Unmarshal([]byte(artifact.CapabilitiesSummaryJSON), &summary) != nil || summary.Minecraft == nil {
|
|
return nil
|
|
}
|
|
return summary.Minecraft
|
|
}
|
|
|
|
func activeProxyConnections(plugin pluginmanager.PluginRecord) int64 {
|
|
var summary map[string]any
|
|
if json.Unmarshal([]byte(plugin.RuntimeSummaryJSON), &summary) != nil {
|
|
return 0
|
|
}
|
|
switch value := summary["active_proxy_connections"].(type) {
|
|
case float64:
|
|
return int64(value)
|
|
case int64:
|
|
return value
|
|
default:
|
|
return 0
|
|
}
|
|
}
|
|
|
|
func jsonObjectString(raw string) any {
|
|
var value any
|
|
if raw == "" || json.Unmarshal([]byte(raw), &value) != nil {
|
|
return map[string]any{}
|
|
}
|
|
return value
|
|
}
|
|
|
|
func jsonArrayString(raw string) any {
|
|
var value any
|
|
if raw == "" || json.Unmarshal([]byte(raw), &value) != nil {
|
|
return []any{}
|
|
}
|
|
return value
|
|
}
|
|
|
|
func writePluginManagerError(w http.ResponseWriter, err error) {
|
|
switch {
|
|
case errors.Is(err, pluginmanager.ErrArtifactNotFound), errors.Is(err, pluginmanager.ErrPluginNotFound):
|
|
adminhttp.WriteAPIError(w, http.StatusNotFound, err.Error())
|
|
default:
|
|
adminhttp.WriteAPIError(w, http.StatusBadRequest, err.Error())
|
|
}
|
|
}
|
|
|
|
func handleAdminPluginService(w http.ResponseWriter, r *http.Request) {
|
|
session, ok := requireRole(w, r, adminRoleMember)
|
|
if !ok {
|
|
return
|
|
}
|
|
if pluginsManager == nil {
|
|
adminhttp.WriteAPIError(w, http.StatusServiceUnavailable, "plugin manager is not initialized")
|
|
return
|
|
}
|
|
switch r.Method {
|
|
case http.MethodGet:
|
|
status, err := pluginsManager.PluginServiceStatus(r.Context())
|
|
if err != nil {
|
|
adminhttp.WriteAPIError(w, http.StatusInternalServerError, err.Error())
|
|
return
|
|
}
|
|
adminhttp.WriteJSON(w, http.StatusOK, map[string]any{"plugin_service": status})
|
|
case http.MethodPut:
|
|
if session.Role != adminRoleAdmin {
|
|
adminhttp.WriteAPIError(w, http.StatusForbidden, "forbidden")
|
|
return
|
|
}
|
|
var req adminhttp.PluginServiceRequest
|
|
if !adminhttp.DecodeJSONRequest(w, r, &req) {
|
|
return
|
|
}
|
|
state, err := pluginsManager.SetPluginServiceDesired(r.Context(), session.Username, req.DesiredMode)
|
|
if err != nil {
|
|
recordAudit(r.Context(), session.Username, adminhttp.RequestSourceIP(r), "plugin_service_mode_update", "plugin_service", "", false, err.Error())
|
|
adminhttp.WriteAPIError(w, http.StatusBadRequest, err.Error())
|
|
return
|
|
}
|
|
recordAuditMetadata(r.Context(), session.Username, adminhttp.RequestSourceIP(r), "plugin_service_mode_update", "plugin_service", "", true, "plugin service mode desired state updated", map[string]any{
|
|
"desired_mode": state.DesiredMode,
|
|
"active_mode": state.ActiveMode,
|
|
"restart_required": state.RestartRequired,
|
|
})
|
|
adminhttp.WriteJSON(w, http.StatusOK, map[string]any{"plugin_service": state})
|
|
case http.MethodPost:
|
|
if session.Role != adminRoleAdmin {
|
|
adminhttp.WriteAPIError(w, http.StatusForbidden, "forbidden")
|
|
return
|
|
}
|
|
if err := pluginsManager.ApplyPluginServiceMode(r.Context()); err != nil {
|
|
adminhttp.WriteAPIError(w, http.StatusBadRequest, err.Error())
|
|
return
|
|
}
|
|
status, err := pluginsManager.PluginServiceStatus(r.Context())
|
|
if err != nil {
|
|
adminhttp.WriteAPIError(w, http.StatusInternalServerError, err.Error())
|
|
return
|
|
}
|
|
adminhttp.WriteJSON(w, http.StatusOK, map[string]any{"plugin_service": status})
|
|
default:
|
|
adminhttp.WriteAPIError(w, http.StatusMethodNotAllowed, "method not allowed")
|
|
}
|
|
}
|
|
|
|
func handleAdminPluginRepositories(w http.ResponseWriter, r *http.Request) {
|
|
session, ok := requireRole(w, r, adminRoleMember)
|
|
if !ok {
|
|
return
|
|
}
|
|
if pluginsManager == nil {
|
|
adminhttp.WriteAPIError(w, http.StatusServiceUnavailable, "plugin manager is not initialized")
|
|
return
|
|
}
|
|
switch r.Method {
|
|
case http.MethodGet:
|
|
imports, err := pluginsManager.ListRepositoryImports(r.Context())
|
|
if err != nil {
|
|
adminhttp.WriteAPIError(w, http.StatusInternalServerError, err.Error())
|
|
return
|
|
}
|
|
adminhttp.WriteJSON(w, http.StatusOK, map[string]any{"imports": imports})
|
|
case http.MethodPost:
|
|
if session.Role != adminRoleAdmin {
|
|
adminhttp.WriteAPIError(w, http.StatusForbidden, "forbidden")
|
|
return
|
|
}
|
|
var req adminhttp.PluginRepositoryImportRequest
|
|
if !adminhttp.DecodeJSONRequest(w, r, &req) {
|
|
return
|
|
}
|
|
record, artifact, err := pluginsManager.ImportRepositoryArtifact(r.Context(), session.Username, pluginmanager.RepositoryImportRequest{
|
|
RepositoryType: req.RepositoryType,
|
|
IndexPath: req.IndexPath,
|
|
ArtifactID: req.ArtifactID,
|
|
PluginID: req.PluginID,
|
|
Version: req.Version,
|
|
TrustPolicy: req.TrustPolicy,
|
|
})
|
|
if err != nil {
|
|
recordAudit(r.Context(), session.Username, adminhttp.RequestSourceIP(r), "plugin_repository_import", "plugin_repository", req.IndexPath, false, err.Error())
|
|
adminhttp.WriteAPIError(w, http.StatusBadRequest, err.Error())
|
|
return
|
|
}
|
|
recordAuditMetadata(r.Context(), session.Username, adminhttp.RequestSourceIP(r), "plugin_repository_import", "plugin_artifact", artifact.ID, true, "repository artifact imported locally", map[string]any{
|
|
"plugin_id": artifact.PluginID,
|
|
"version": artifact.Version,
|
|
"auto_enable": false,
|
|
"import_id": record.ID,
|
|
})
|
|
adminhttp.WriteJSON(w, http.StatusCreated, map[string]any{"import": record, "artifact": artifact})
|
|
default:
|
|
adminhttp.WriteAPIError(w, http.StatusMethodNotAllowed, "method not allowed")
|
|
}
|
|
}
|
|
|
|
func handleAdminPluginSupplyChain(w http.ResponseWriter, r *http.Request) {
|
|
session, ok := requireRole(w, r, adminRoleMember)
|
|
if !ok {
|
|
return
|
|
}
|
|
if pluginsManager == nil {
|
|
adminhttp.WriteAPIError(w, http.StatusServiceUnavailable, "plugin manager is not initialized")
|
|
return
|
|
}
|
|
switch r.Method {
|
|
case http.MethodGet:
|
|
assessments, err := pluginsManager.ListSupplyChainAssessments(r.Context(), r.URL.Query().Get("plugin_id"), r.URL.Query().Get("artifact_id"))
|
|
if err != nil {
|
|
adminhttp.WriteAPIError(w, http.StatusInternalServerError, err.Error())
|
|
return
|
|
}
|
|
adminhttp.WriteJSON(w, http.StatusOK, map[string]any{"assessments": assessments})
|
|
case http.MethodPost:
|
|
if session.Role != adminRoleAdmin {
|
|
adminhttp.WriteAPIError(w, http.StatusForbidden, "forbidden")
|
|
return
|
|
}
|
|
var req adminhttp.PluginSupplyChainRequest
|
|
if !adminhttp.DecodeJSONRequest(w, r, &req) {
|
|
return
|
|
}
|
|
assessment, err := pluginsManager.AssessSupplyChain(r.Context(), session.Username, req.PluginID, req.ArtifactID, req.Metadata)
|
|
if err != nil {
|
|
adminhttp.WriteAPIError(w, http.StatusBadRequest, err.Error())
|
|
return
|
|
}
|
|
adminhttp.WriteJSON(w, http.StatusCreated, map[string]any{"assessment": assessment})
|
|
default:
|
|
adminhttp.WriteAPIError(w, http.StatusMethodNotAllowed, "method not allowed")
|
|
}
|
|
}
|
|
|
|
func handleAdminPluginInstrumentation(w http.ResponseWriter, r *http.Request) {
|
|
session, ok := requireRole(w, r, adminRoleMember)
|
|
if !ok {
|
|
return
|
|
}
|
|
if pluginsManager == nil {
|
|
adminhttp.WriteAPIError(w, http.StatusServiceUnavailable, "plugin manager is not initialized")
|
|
return
|
|
}
|
|
switch r.Method {
|
|
case http.MethodGet:
|
|
records, err := pluginsManager.ListInstrumentation(r.Context())
|
|
if err != nil {
|
|
adminhttp.WriteAPIError(w, http.StatusInternalServerError, err.Error())
|
|
return
|
|
}
|
|
adminhttp.WriteJSON(w, http.StatusOK, map[string]any{"instrumentation": records})
|
|
case http.MethodPost:
|
|
if session.Role != adminRoleAdmin {
|
|
adminhttp.WriteAPIError(w, http.StatusForbidden, "forbidden")
|
|
return
|
|
}
|
|
var req pluginmanager.InstrumentationRequest
|
|
if !adminhttp.DecodeJSONRequest(w, r, &req) {
|
|
return
|
|
}
|
|
record, err := pluginsManager.SaveInstrumentation(r.Context(), session.Username, req)
|
|
if err != nil {
|
|
adminhttp.WriteAPIError(w, http.StatusBadRequest, err.Error())
|
|
return
|
|
}
|
|
adminhttp.WriteJSON(w, http.StatusCreated, map[string]any{"instrumentation": record})
|
|
default:
|
|
adminhttp.WriteAPIError(w, http.StatusMethodNotAllowed, "method not allowed")
|
|
}
|
|
}
|