fix(source): add /services/{name}/source endpoint; fix 404 for service code view in funcs-console

This commit is contained in:
Naeel
2026-03-21 07:20:03 +03:00
parent 09b35888d4
commit 50f24565ec
6 changed files with 51 additions and 7 deletions
+1 -1
View File
@@ -29,7 +29,7 @@ spec:
- name: sless-registry-auth
containers:
- name: funcs
image: pearlharbor.registryk8s.services.ngcloud.ru/naeel/sless-funcs-service:v0.2.1
image: pearlharbor.registryk8s.services.ngcloud.ru/naeel/sless-funcs-service:v0.2.2
ports:
- containerPort: 8090
env:
+1 -1
View File
@@ -74,7 +74,7 @@ spec:
containers:
- name: operator
# При обновлении версии оператора — менять тег здесь (не latest!)
image: pearlharbor.registryk8s.services.ngcloud.ru/naeel/sless-operator:v0.1.44
image: pearlharbor.registryk8s.services.ngcloud.ru/naeel/sless-operator:v0.1.45
# Always — чтобы всегда тянуть по точному тегу (не кешировать старый)
imagePullPolicy: Always
ports:
+37
View File
@@ -66,6 +66,43 @@ func (h *Handler) GetSource(w http.ResponseWriter, r *http.Request) {
writeJSON(w, http.StatusOK, files)
}
// GetServiceSource — GET /v1/namespaces/{namespace}/services/{name}/source
// Аналог GetSource для sless_service: скачивает tar.gz из S3 и возвращает пользовательские файлы.
func (h *Handler) GetServiceSource(w http.ResponseWriter, r *http.Request) {
ns := namespace(r)
name := pathVar(r, "name")
svc := &slessv1alpha1.Service{}
if err := h.K8s.Get(r.Context(), client.ObjectKey{Name: name, Namespace: ns}, svc); err != nil {
if errors.IsNotFound(err) {
writeJSON(w, http.StatusNotFound, errResp("service not found"))
return
}
writeJSON(w, http.StatusInternalServerError, errResp(err.Error()))
return
}
if svc.Spec.S3Key == "" {
writeJSON(w, http.StatusOK, []sourceFileEntry{})
return
}
rc, err := h.S3.Download(r.Context(), svc.Spec.S3Key)
if err != nil {
writeJSON(w, http.StatusInternalServerError, errResp("download from S3: "+err.Error()))
return
}
defer rc.Close()
files, err := extractSourceFilesFromTarGz(rc)
if err != nil {
writeJSON(w, http.StatusInternalServerError, errResp("extract source: "+err.Error()))
return
}
writeJSON(w, http.StatusOK, files)
}
// extractSourceFilesFromTarGz читает tar.gz и возвращает пользовательские файлы.
// Dockerfile пропускается — он генерируется builder-ом автоматически.
// Бинарные файлы (содержат null bytes) возвращаются в base64 с binary=true.
+1
View File
@@ -55,6 +55,7 @@ func NewRouter(h *handler.Handler, log *slog.Logger) http.Handler {
v1.HandleFunc("/namespaces/{namespace}/services/{name}", h.UpdateService).Methods(http.MethodPut)
v1.HandleFunc("/namespaces/{namespace}/services/{name}", h.DeleteService).Methods(http.MethodDelete)
v1.HandleFunc("/namespaces/{namespace}/services/{name}/upload", h.UploadServiceCode).Methods(http.MethodPost)
v1.HandleFunc("/namespaces/{namespace}/services/{name}/source", h.GetServiceSource).Methods(http.MethodGet)
// Triggers CRUD
v1.HandleFunc("/namespaces/{namespace}/triggers", h.ListTriggers).Methods(http.MethodGet)
+4 -3
View File
@@ -416,7 +416,7 @@
icon.style.transform = 'rotate(90deg)';
if (!loaded) {
loaded = true;
loadSource(body, ns, fn.name);
loadSource(body, ns, fn.name, fn.kind);
}
} else {
body.style.display = 'none';
@@ -458,12 +458,13 @@
return btn;
}
function loadSource(body, ns, fnName) {
function loadSource(body, ns, fnName, fnKind) {
var msg = $el('div', 'src-msg');
msg.textContent = 'Загрузка кода…';
body.appendChild(msg);
fetch('/funcs/' + ns + '/source/' + fnName)
var kindParam = fnKind === 'service' ? '?kind=service' : '';
fetch('/funcs/' + ns + '/source/' + fnName + kindParam)
.then(function (r) {
body.removeChild(msg);
if (!r.ok) {
+7 -2
View File
@@ -347,14 +347,19 @@ func servePlainText(w http.ResponseWriter, ns, externalURL string, fns []fnRespo
fmt.Fprint(w, sb.String())
}
// proxySourceGet проксирует GET /funcs/{ns}/source/{fn} → оператор GET /v1/.../source.
// proxySourceGet проксирует GET /funcs/{ns}/source/{fn}?kind={function|service} → оператор.
// Авторизация — сервисный токен (хранится на сервере, не открывается клиенту).
// kind=service → /v1/.../services/{fn}/source; иначе → /v1/.../functions/{fn}/source
func proxySourceGet(w http.ResponseWriter, r *http.Request, operatorURL, serviceToken, ns, funcName string) {
if !isValidK8sName(funcName) {
http.Error(w, "invalid function name\n", http.StatusBadRequest)
return
}
target := operatorURL + "/v1/namespaces/" + ns + "/functions/" + funcName + "/source"
resourceType := "functions"
if r.URL.Query().Get("kind") == "service" {
resourceType = "services"
}
target := operatorURL + "/v1/namespaces/" + ns + "/" + resourceType + "/" + funcName + "/source"
req, err := http.NewRequestWithContext(r.Context(), http.MethodGet, target, nil)
if err != nil {
http.Error(w, "internal error\n", http.StatusInternalServerError)