mirror of
https://github.com/go-sigma/sigma.git
synced 2026-10-28 15:34:34 +08:00
Sigma shipped three overlapping ways to operate a remote server: the embedded MCP server, sigma-cli, and the sigma-api-operator skill. All three wrapped the same HTTP API, yet each covered a different subset: user CRUD existed only in the CLI, system-* only in MCP, and builders, daemons, webhooks, validators, code repositories and tokens were reachable only through raw HTTP. MCP was disabled by default and still carried 1246 lines of production code plus 606 lines of tests to maintain that partially overlapping surface. Keep two surfaces instead: sigma-cli for execution and the skill for API discovery. - delete pkg/server/mcp and its mcp-go dependency, which also drops the jsonschema-go, jsonschema/v6 and uritemplate/v3 transitives - drop ConfigurationMCP, its defaults and the authz/audit skipper bypasses that existed only to let the MCP endpoint bypass both - remove the mcp section from conf/config*.yaml, docs/configuration.md, and the Helm chart values and configmap - replace docs/mcp.md with docs/cli.md covering sigma-cli login, global flags, per-resource commands, jq composition and agent usage - make sigma-cli the preferred path in the sigma-api-operator skill and rebuild its examples around the resources the CLI does not wrap yet, using the paths and request schemas from swagger.yaml Lint was already red on dev for an unrelated reason: golangci-lint v2.13.0 vendors golang.org/x/tools v0.49.0, which only decodes export data version 4, so every import of a Go 1.27.2 standard library package failed typecheck with "export data version 5 is greater than maximum supported version 4". Bump to v2.15.0 (x/tools v0.52.0) and fix the two modernize findings it reports for Go 1.27's strings.CutLast. - .github/workflows/lint.yml: golangci-lint v2.13.0 -> v2.15.0, and document the export data constraint next to the pin - pkg/authz/resolver.go, pkg/logger/logger.go: replace LastIndex slicing with strings.CutLast - bump github.com/docker/cli v29.9.0 -> v29.9.1 BREAKING CHANGE: the `mcp` configuration section and the /api/v1/mcp endpoint are gone. Use sigma-cli or the REST API for automation. Verification: - go build -tags "netgo,timetzdata,exclude_graphdriver_btrfs,containers_image_openpgp" ./... - go vet -tags "netgo,timetzdata,exclude_graphdriver_btrfs,containers_image_openpgp" ./... - golangci-lint v2.15.0 run --build-tags "netgo,timetzdata,exclude_graphdriver_btrfs,containers_image_openpgp" ./... (0 issues) - golangci-lint config verify - go test -tags "netgo,timetzdata,exclude_graphdriver_btrfs,containers_image_openpgp" ./pkg/authz/... ./pkg/logger/... ./cmd/cli/... ./pkg/app/... ./pkg/background/build/... - go test -run TestConfiguration -tags "netgo,timetzdata,exclude_graphdriver_btrfs,containers_image_openpgp" ./pkg/config/... - go mod tidy - make swagen - helm template sigma ./deploy/sigma - bun run docs:build - make build - make build-cli
187 lines
5.5 KiB
Go
187 lines
5.5 KiB
Go
// Copyright 2023 sigma
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package server
|
|
|
|
import (
|
|
"context"
|
|
"crypto/tls"
|
|
"fmt"
|
|
"log/slog"
|
|
"net/http"
|
|
"os"
|
|
"os/signal"
|
|
"time"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"go.uber.org/dig"
|
|
|
|
"github.com/go-sigma/sigma/pkg/app"
|
|
"github.com/go-sigma/sigma/pkg/background/build/runtime"
|
|
"github.com/go-sigma/sigma/pkg/background/cronjob"
|
|
"github.com/go-sigma/sigma/pkg/background/daemon"
|
|
"github.com/go-sigma/sigma/pkg/config"
|
|
"github.com/go-sigma/sigma/pkg/consts"
|
|
"github.com/go-sigma/sigma/pkg/graceful"
|
|
"github.com/go-sigma/sigma/pkg/infra/workq"
|
|
"github.com/go-sigma/sigma/pkg/logger"
|
|
"github.com/go-sigma/sigma/pkg/server/handlers"
|
|
)
|
|
|
|
// ServerConfig selects which optional subsystems Serve initializes; each
|
|
// Without* field disables the corresponding component.
|
|
type ServerConfig struct {
|
|
WithoutDistribution bool
|
|
WithoutWorker bool
|
|
WithoutWeb bool
|
|
}
|
|
|
|
// Serve starts the server
|
|
func Serve(digCon *dig.Container) error {
|
|
var ginServer *gin.Engine
|
|
var serverParams app.GinServerParams
|
|
err := digCon.Invoke(func(params app.GinServerParams) error {
|
|
var invokeErr error
|
|
serverParams = params
|
|
ginServer, invokeErr = app.NewGinServer(params)
|
|
return invokeErr
|
|
})
|
|
if err != nil {
|
|
return fmt.Errorf("failed to new gin server: %v", err)
|
|
}
|
|
|
|
var serverConfig ServerConfig
|
|
err = digCon.Invoke(func(config ServerConfig) { serverConfig = config })
|
|
if err != nil {
|
|
return fmt.Errorf("failed to invoke server config: %v", err)
|
|
}
|
|
|
|
err = digCon.Provide(func() *gin.Engine { return ginServer })
|
|
if err != nil {
|
|
return fmt.Errorf("failed to provide gin engine: %v", err)
|
|
}
|
|
|
|
var cfg *config.Configuration
|
|
err = digCon.Invoke(func(c *config.Configuration) { cfg = c })
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if !serverConfig.WithoutDistribution {
|
|
err = handlers.InitializeDistribution(digCon)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to initialize distribution handlers: %v", err)
|
|
}
|
|
}
|
|
if !serverConfig.WithoutWorker {
|
|
err = runtime.Initialize(cfg)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = daemon.Initialize(digCon)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to initialize daemon: %v", err)
|
|
}
|
|
|
|
err = digCon.Invoke(workq.InitConsumer)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to initialize work queue consumer: %v", err)
|
|
}
|
|
if err = cronjob.Initialize(digCon); err != nil {
|
|
return fmt.Errorf("failed to initialize cronjob: %v", err)
|
|
}
|
|
graceful.RunAtShutdown("cronjob", 40, cronjob.DeInitialize)
|
|
}
|
|
|
|
err = handlers.Initialize(digCon)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
httpServer := &http.Server{
|
|
Addr: consts.ServerPort,
|
|
Handler: ginServer,
|
|
ReadHeaderTimeout: cfg.HTTP.Timeout.ReadHeaderTimeout(),
|
|
ReadTimeout: cfg.HTTP.Timeout.ReadTimeout(),
|
|
WriteTimeout: cfg.HTTP.Timeout.WriteTimeout(),
|
|
IdleTimeout: cfg.HTTP.Timeout.IdleTimeout(),
|
|
}
|
|
|
|
// Verify all required dependencies are reachable before accepting traffic.
|
|
// If this fails, the process exits without ever calling ListenAndServe.
|
|
startupCtx, startupCancel := context.WithTimeout(context.Background(), app.StartupTimeout)
|
|
defer startupCancel()
|
|
err = app.ValidateOnStartup(startupCtx, app.NewReadinessResources(serverParams))
|
|
if err != nil {
|
|
return fmt.Errorf("startup dependency check failed: %w", err)
|
|
}
|
|
app.PrewarmMetadataCache(startupCtx, digCon)
|
|
|
|
go func() {
|
|
if cfg.HTTP.TLS.Enabled {
|
|
crtBytes, e := os.ReadFile(cfg.HTTP.TLS.Certificate)
|
|
if e != nil {
|
|
logger.Fatal("read certificate failed", "err", e, "certificate", cfg.HTTP.TLS.Certificate)
|
|
return
|
|
}
|
|
keyBytes, e := os.ReadFile(cfg.HTTP.TLS.Key)
|
|
if e != nil {
|
|
logger.Fatal("read key failed", "err", e, "key", cfg.HTTP.TLS.Key)
|
|
return
|
|
}
|
|
cert, e := tls.X509KeyPair(crtBytes, keyBytes)
|
|
if e != nil {
|
|
logger.Fatal("parse X509 key pair failed", "err", e)
|
|
return
|
|
}
|
|
httpServer.TLSConfig = &tls.Config{Certificates: []tls.Certificate{cert}}
|
|
e = httpServer.ListenAndServeTLS("", "")
|
|
if e != nil && e != http.ErrServerClosed {
|
|
logger.Fatal("listening on interface failed", "err", e)
|
|
}
|
|
} else {
|
|
e := httpServer.ListenAndServe()
|
|
if e != nil && e != http.ErrServerClosed {
|
|
logger.Fatal("listening on interface failed", "err", e)
|
|
}
|
|
}
|
|
}()
|
|
|
|
// Wait for interrupt signal to gracefully shutdown the server with a
|
|
// single shared 30s budget covering HTTP drain + shutdown hooks.
|
|
// Use a buffered channel to avoid missing signals as recommended for signal.Notify
|
|
quit := make(chan os.Signal, 1)
|
|
signal.Notify(quit, os.Interrupt)
|
|
<-quit
|
|
|
|
// Flip readiness to 503 and give the load balancer a moment to stop
|
|
// routing new connections before we start rejecting in-flight requests.
|
|
app.SetDraining(true)
|
|
slog.Info("draining traffic before shutdown")
|
|
time.Sleep(app.DrainDelay)
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
|
defer cancel()
|
|
err = httpServer.Shutdown(ctx)
|
|
if err != nil {
|
|
slog.Error("server shutdown failed", "err", err)
|
|
}
|
|
|
|
if err := graceful.Shutdown(ctx); err != nil {
|
|
slog.Error("graceful shutdown failed", "err", err)
|
|
}
|
|
|
|
return nil
|
|
}
|