WeKnora deployment source with the reviewed wiki identity, summary and recovery fixes
Anydoc / anydoc parser engine (push) Canceled after 0s
App / Format, vet, test, and build (push) Canceled after 0s
CLI / test (macos-latest / go 1.26) (push) Canceled after 0s
CLI / test (ubuntu-latest / go 1.26) (push) Canceled after 0s
CLI / test (windows-latest / go 1.26) (push) Canceled after 0s
Build and Push Docker Image / build-ui (push) Canceled after 0s
Build and Push Docker Image / build-docreader (push) Canceled after 0s
Build and Push Docker Image / build-sandbox (push) Canceled after 0s
Build and Push Docker Image / build-app (amd64, linux/amd64, ubuntu-latest) (push) Canceled after 0s
Build and Push Docker Image / build-app (arm64, linux/arm64, ubuntu-24.04-arm) (push) Canceled after 0s
DocReader / Python tests and Go client tests (push) Canceled after 0s
Build & Publish (dsh-weknora) / Typecheck, test, pack (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (pinned) (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (latest, advisory) (push) Canceled after 0s
Frontend / Test, type-check, and build (push) Canceled after 0s
Go Lint Cache / warm lint cache (.) (push) Canceled after 0s
Go Lint Cache / warm lint cache (cli) (push) Canceled after 0s
Go Lint Cache / warm lint cache (client) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.10) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.11) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.12) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.13) (push) Canceled after 0s
Build and Push Docker Image / merge (push) Canceled after 0s
Build & Publish (dsh-weknora) / Publish to npm (push) Canceled after 0s
Build & Publish (mcp-server) / Build sdist + wheel (push) Canceled after 0s
Build & Publish (mcp-server) / Publish to PyPI (push) Canceled after 0s
Anydoc / anydoc parser engine (push) Canceled after 0s
App / Format, vet, test, and build (push) Canceled after 0s
CLI / test (macos-latest / go 1.26) (push) Canceled after 0s
CLI / test (ubuntu-latest / go 1.26) (push) Canceled after 0s
CLI / test (windows-latest / go 1.26) (push) Canceled after 0s
Build and Push Docker Image / build-ui (push) Canceled after 0s
Build and Push Docker Image / build-docreader (push) Canceled after 0s
Build and Push Docker Image / build-sandbox (push) Canceled after 0s
Build and Push Docker Image / build-app (amd64, linux/amd64, ubuntu-latest) (push) Canceled after 0s
Build and Push Docker Image / build-app (arm64, linux/arm64, ubuntu-24.04-arm) (push) Canceled after 0s
DocReader / Python tests and Go client tests (push) Canceled after 0s
Build & Publish (dsh-weknora) / Typecheck, test, pack (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (pinned) (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (latest, advisory) (push) Canceled after 0s
Frontend / Test, type-check, and build (push) Canceled after 0s
Go Lint Cache / warm lint cache (.) (push) Canceled after 0s
Go Lint Cache / warm lint cache (cli) (push) Canceled after 0s
Go Lint Cache / warm lint cache (client) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.10) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.11) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.12) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.13) (push) Canceled after 0s
Build and Push Docker Image / merge (push) Canceled after 0s
Build & Publish (dsh-weknora) / Publish to npm (push) Canceled after 0s
Build & Publish (mcp-server) / Build sdist + wheel (push) Canceled after 0s
Build & Publish (mcp-server) / Publish to PyPI (push) Canceled after 0s
This commit is contained in:
@@ -0,0 +1,129 @@
|
||||
package sandbox
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const StandardTemplateName = "weknora"
|
||||
|
||||
// DefaultE2BTemplateTag is the tag E2B resolves when a sandbox is created from a
|
||||
// bare template name or ID. Builds must carry it to be spawnable at all.
|
||||
const DefaultE2BTemplateTag = "default"
|
||||
|
||||
// TemplateStatusUntagged marks a template whose builds finished but which has no
|
||||
// build under the tag sandbox creation resolves. It looks identical to "still
|
||||
// building" in the provider's template list, yet waiting will never help: the
|
||||
// template needs a new build carrying the default tag.
|
||||
const TemplateStatusUntagged = "untagged"
|
||||
|
||||
// RemoteTemplate is the provider-neutral template projection returned to the
|
||||
// settings UI. IDs remain opaque; users choose a readable name and status.
|
||||
type RemoteTemplate struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Status string `json:"status,omitempty"`
|
||||
Version string `json:"version,omitempty"`
|
||||
Image string `json:"image,omitempty"`
|
||||
CreatedAt string `json:"created_at,omitempty"`
|
||||
UpdatedAt string `json:"updated_at,omitempty"`
|
||||
Standard bool `json:"standard"`
|
||||
// Error carries the provider's own explanation for a failed build. Without
|
||||
// it a failed template is a red badge with no way to tell a registry
|
||||
// credential problem from an out-of-disk node.
|
||||
Error string `json:"error,omitempty"`
|
||||
|
||||
// Cube reports these on GET /templates; other backends leave them empty
|
||||
// and the settings list simply omits the corresponding rows.
|
||||
InstanceType string `json:"instance_type,omitempty"`
|
||||
NetworkType string `json:"network_type,omitempty"`
|
||||
AllowInternetAccess *bool `json:"allow_internet_access,omitempty"`
|
||||
}
|
||||
|
||||
// RemoteTemplateCatalog is an optional provider capability used by the
|
||||
// configuration flow. It stays separate from RemoteSandboxClient because the
|
||||
// session lifecycle never needs template administration.
|
||||
type RemoteTemplateCatalog interface {
|
||||
ListTemplates(ctx context.Context) ([]RemoteTemplate, error)
|
||||
EnsureStandardTemplate(ctx context.Context) (*RemoteTemplate, error)
|
||||
// ReplaceStandardTemplate applies the current spec to the cluster's
|
||||
// WeKnora template (DNS, image). A READY template cannot pick those up
|
||||
// any other way. It must not delete a usable template: callers persist
|
||||
// the replacement ID first, then DeleteSupersededStandardTemplates.
|
||||
ReplaceStandardTemplate(ctx context.Context) (*RemoteTemplate, error)
|
||||
// DeleteSupersededStandardTemplates removes WeKnora templates other than
|
||||
// keepID. Call only after keepID is spawnable and has been written onto
|
||||
// every config that still pointed at the previous standard template.
|
||||
DeleteSupersededStandardTemplates(ctx context.Context, keepID string) error
|
||||
}
|
||||
|
||||
func isStandardTemplate(name string) bool {
|
||||
trimmed := strings.Trim(strings.TrimSpace(name), "/")
|
||||
if strings.EqualFold(trimmed, StandardTemplateName) {
|
||||
return true
|
||||
}
|
||||
parts := strings.Split(trimmed, "/")
|
||||
return len(parts) > 1 && strings.EqualFold(parts[len(parts)-1], StandardTemplateName)
|
||||
}
|
||||
|
||||
// isStandardTemplateImage recognises our template by the image it was built
|
||||
// from. Names are the primary key, but a provider that drops them — Cube omits
|
||||
// the field entirely when a template carries no alias — would otherwise make
|
||||
// every catalog refresh look at a cluster with no standard template and build
|
||||
// yet another one.
|
||||
func isStandardTemplateImage(image string) bool {
|
||||
candidate := normalizeImageRepository(image)
|
||||
return candidate != "" && candidate == normalizeImageRepository(DefaultDockerImage)
|
||||
}
|
||||
|
||||
// normalizeImageRepository reduces an image reference to its repository path so
|
||||
// that "docker.io/wechatopenai/weknora-sandbox:latest",
|
||||
// "wechatopenai/weknora-sandbox@sha256:…" and the bare name all compare equal.
|
||||
func normalizeImageRepository(image string) string {
|
||||
ref := strings.TrimSpace(image)
|
||||
if ref == "" {
|
||||
return ""
|
||||
}
|
||||
if at := strings.Index(ref, "@"); at >= 0 {
|
||||
ref = ref[:at]
|
||||
}
|
||||
// A colon before the last slash belongs to a registry port, not a tag.
|
||||
if colon := strings.LastIndex(ref, ":"); colon > strings.LastIndex(ref, "/") {
|
||||
ref = ref[:colon]
|
||||
}
|
||||
ref = strings.Trim(ref, "/")
|
||||
parts := strings.Split(ref, "/")
|
||||
// Registry hosts are recognisable by a dot, a port, or being "localhost";
|
||||
// anything else at the head is a namespace we must keep.
|
||||
if len(parts) > 1 && (strings.ContainsAny(parts[0], ".:") || parts[0] == "localhost") {
|
||||
parts = parts[1:]
|
||||
}
|
||||
if len(parts) > 1 && strings.EqualFold(parts[0], "library") {
|
||||
parts = parts[1:]
|
||||
}
|
||||
return strings.ToLower(strings.Join(parts, "/"))
|
||||
}
|
||||
|
||||
// IsTemplateBuildFailed reports whether a template's build ended in a state no
|
||||
// amount of waiting will improve. Such a template must be rebuilt rather than
|
||||
// treated as an existing standard template.
|
||||
func IsTemplateBuildFailed(status string) bool {
|
||||
switch strings.ToLower(strings.TrimSpace(status)) {
|
||||
case "failed", "failure", "error", "cancelled", "canceled", TemplateStatusUntagged:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// IsTemplateReady reports whether a template can spawn a sandbox. Rebuild
|
||||
// replacements stay listed alongside the previous READY template until this
|
||||
// is true, so sessions never lose a spawnable ID mid-build.
|
||||
func IsTemplateReady(status string) bool {
|
||||
switch strings.ToLower(strings.TrimSpace(status)) {
|
||||
case "ready", "available", "complete", "completed", "success", "succeeded":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user