Slice 2: real auth, agent-mediated repo/branch listing, deployment list from SQLite
- protocol: add RepoInfo, RouteOverride; add HostPort, SandboxID to DeployRequest.
- ws hub: add CallAgent for sync request/response RPCs over the agent WS,
and DeliverAgentReply to route {op:reply} frames back to the caller.
UnregisterAgent now also fails any pending RPCs so callers don't hang.
- agent-micro: new op handlers list_repos, list_branches, probe.
Wire protocol.Event frames use json.RawMessage so each op decodes
its own data shape.
- agent-gateway: same op handlers (list_repos/list_branches/probe) plus
push_routes, which the gateway uses to rewrite the api-gateway
config.php. Detailed in a later commit.
- control-plane login: validateViaAgent now calls CallAgent('probe')
against the gateway agent (git ls-remote), replacing the
accept-any-creds stub.
- control-plane repos: handleListRepos and handleListBranches forward
to the agents via list_repos / list_branches RPCs, replacing the
hardcoded fixtures.
- control-plane deployments: split into its own file. handleListDeployments
reads from SQLite (was hardcoded []). handleCreateDeployment now
supports sandbox-scoped deploys with a host port + env merge.
handleStopDeployment looks up the node from the deployment row.
- store: split into store.go + deployments.go. The Deployment type
adds sandboxId, containerId, hostPort. StartDeploymentInSandbox,
SetContainerID, ListDeployments, GetDeployment, LatestDeploymentBySandboxService
are new.
- store_test.go: round-trips every Slice-2 path (env, sandbox,
template, clone, routes, deployment).
- .gitignore: track bin/ — the build runs on a separate Linux box
with the golang:1.24 toolchain, and the binaries are SCPed from
there to the company VMs (92 / 186). The VMs have no internet.
- Tracked bin/{control-plane,agent-micro,agent-gateway}.
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
// Command agent-micro runs on the microservices VM (172.18.136.92). It
|
||||
// maintains a WebSocket to the control plane, accepts deploy/stop frames,
|
||||
// and runs the build+container pipeline locally for Go microservices.
|
||||
// maintains a WebSocket to the control plane, accepts deploy/stop/RPC
|
||||
// frames, and runs the build+container pipeline locally for Go
|
||||
// microservices.
|
||||
package main
|
||||
|
||||
import (
|
||||
@@ -13,8 +14,8 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
docker "github.com/moby/moby/client"
|
||||
"github.com/gorilla/websocket"
|
||||
docker "github.com/moby/moby/client"
|
||||
|
||||
"github.com/sdp/agentlib/deployer"
|
||||
"github.com/sdp/agentlib/gitutil"
|
||||
@@ -31,7 +32,7 @@ var repos = map[string]string{
|
||||
}
|
||||
|
||||
func main() {
|
||||
cpURL := flag.String("cp", envOr("SDP_CP_URL", "ws://localhost:8080/ws/agent"), "control plane WS URL")
|
||||
cpURL := flag.String("cp", envOr("SDP_CP_URL", "ws://localhost:3452/ws/agent"), "control plane WS URL")
|
||||
nodeID := flag.String("node", envOr("SDP_NODE_ID", "micro"), "node id sent in WS query")
|
||||
flag.Parse()
|
||||
|
||||
@@ -110,9 +111,9 @@ func readLoop(c *websocket.Conn, cli *docker.Client, out chan<- []byte, mu *sync
|
||||
}
|
||||
// Inbound frame: {op, data, id}. Op is the verb. data is op-specific.
|
||||
var frame struct {
|
||||
Op string `json:"op"`
|
||||
Data protocol.DeployRequest `json:"data"`
|
||||
ID string `json:"id"`
|
||||
Op string `json:"op"`
|
||||
Data json.RawMessage `json:"data"`
|
||||
ID string `json:"id"`
|
||||
}
|
||||
if err := json.Unmarshal(raw, &frame); err != nil {
|
||||
log.Printf("bad frame: %v", err)
|
||||
@@ -120,33 +121,96 @@ func readLoop(c *websocket.Conn, cli *docker.Client, out chan<- []byte, mu *sync
|
||||
}
|
||||
switch frame.Op {
|
||||
case "deploy":
|
||||
repoPath, ok := repos[frame.Data.Repository]
|
||||
var req protocol.DeployRequest
|
||||
if err := json.Unmarshal(frame.Data, &req); err != nil {
|
||||
log.Printf("bad deploy data: %v", err)
|
||||
continue
|
||||
}
|
||||
repoPath, ok := repos[req.Repository]
|
||||
if !ok {
|
||||
emit(out, protocol.Event{
|
||||
DeploymentID: frame.Data.DeploymentID,
|
||||
DeploymentID: req.DeploymentID,
|
||||
Kind: "status",
|
||||
State: "FAILED",
|
||||
At: time.Now().UnixMilli(),
|
||||
})
|
||||
continue
|
||||
}
|
||||
d := deployer.NewGo(cli, frame.Data.DeploymentID,
|
||||
frame.Data.Repository, repoPath,
|
||||
frame.Data.Branch, frame.Data.Env,
|
||||
gitutil.Creds{Username: frame.Data.Username, Password: frame.Data.Password},
|
||||
d := deployer.NewGo(cli, req.DeploymentID,
|
||||
req.Repository, repoPath,
|
||||
req.Branch, req.HostPort, req.Env,
|
||||
gitutil.Creds{Username: req.Username, Password: req.Password},
|
||||
)
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
inflight[frame.Data.DeploymentID] = &runState{deployer: d, cancel: cancel}
|
||||
inflight[req.DeploymentID] = &runState{deployer: d, cancel: cancel}
|
||||
go runDeploy(d, ctx, out)
|
||||
case "stop":
|
||||
if rs, ok := inflight[frame.ID]; ok {
|
||||
_ = rs.deployer.Stop(context.Background())
|
||||
rs.cancel() // unblock StreamLogs
|
||||
}
|
||||
case "list_repos":
|
||||
handleListRepos(out, frame.ID)
|
||||
case "list_branches":
|
||||
var body struct {
|
||||
Repo string `json:"repo"`
|
||||
}
|
||||
_ = json.Unmarshal(frame.Data, &body)
|
||||
handleListBranches(out, frame.ID, body.Repo)
|
||||
case "probe":
|
||||
var body struct {
|
||||
Repo string `json:"repo"`
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
}
|
||||
_ = json.Unmarshal(frame.Data, &body)
|
||||
handleProbe(out, frame.ID, body.Repo, body.Username, body.Password)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func handleListRepos(out chan<- []byte, id string) {
|
||||
// Micro agent serves all repos except the api-gateway. The control
|
||||
// plane calls both nodes for the union; the gateway will return
|
||||
// only its own repo.
|
||||
list := make([]protocol.RepoInfo, 0, len(repos))
|
||||
for name, path := range repos {
|
||||
list = append(list, protocol.RepoInfo{Name: name, Path: path})
|
||||
}
|
||||
replyOK(out, id, map[string]any{"repos": list})
|
||||
}
|
||||
|
||||
func handleListBranches(out chan<- []byte, id, repo string) {
|
||||
path, ok := repos[repo]
|
||||
if !ok {
|
||||
replyErr(out, id, "unknown repo: "+repo)
|
||||
return
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
branches, err := gitutil.ListBranches(ctx, path)
|
||||
if err != nil {
|
||||
replyErr(out, id, err.Error())
|
||||
return
|
||||
}
|
||||
replyOK(out, id, map[string]any{"ok": true, "branches": branches})
|
||||
}
|
||||
|
||||
func handleProbe(out chan<- []byte, id, repo, user, pass string) {
|
||||
path, ok := repos[repo]
|
||||
if !ok {
|
||||
replyErr(out, id, "unknown repo: "+repo)
|
||||
return
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
if err := gitutil.Probe(ctx, path, gitutil.Creds{Username: user, Password: pass}); err != nil {
|
||||
replyErr(out, id, err.Error())
|
||||
return
|
||||
}
|
||||
replyOK(out, id, map[string]any{"ok": true})
|
||||
}
|
||||
|
||||
func runDeploy(d *deployer.Deployer, ctx context.Context, out chan<- []byte) {
|
||||
events := make(chan protocol.Event, 64)
|
||||
// producer: Run pipelines, then StreamLogs tails the container. Both
|
||||
@@ -174,6 +238,31 @@ func emit(out chan<- []byte, e protocol.Event) {
|
||||
}
|
||||
}
|
||||
|
||||
func replyOK(out chan<- []byte, id string, data interface{}) {
|
||||
reply(out, id, true, "", data)
|
||||
}
|
||||
|
||||
func replyErr(out chan<- []byte, id, errMsg string) {
|
||||
reply(out, id, false, errMsg, nil)
|
||||
}
|
||||
|
||||
func reply(out chan<- []byte, id string, ok bool, errMsg string, data interface{}) {
|
||||
frame := map[string]any{
|
||||
"op": "reply",
|
||||
"id": id,
|
||||
"ok": ok,
|
||||
"data": data,
|
||||
}
|
||||
if errMsg != "" {
|
||||
frame["error"] = errMsg
|
||||
}
|
||||
b, _ := json.Marshal(frame)
|
||||
select {
|
||||
case out <- b:
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
func envOr(k, def string) string {
|
||||
if v := os.Getenv(k); v != "" {
|
||||
return v
|
||||
|
||||
Reference in New Issue
Block a user