diff --git a/deployments/k8s/funcs-service.yaml b/deployments/k8s/funcs-service.yaml index d752ae7..6505c99 100644 --- a/deployments/k8s/funcs-service.yaml +++ b/deployments/k8s/funcs-service.yaml @@ -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: diff --git a/deployments/k8s/operator.yaml b/deployments/k8s/operator.yaml index e0d3490..78eba8d 100644 --- a/deployments/k8s/operator.yaml +++ b/deployments/k8s/operator.yaml @@ -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: diff --git a/internal/api/handler/source.go b/internal/api/handler/source.go index 44b83b4..8e7aeaa 100644 --- a/internal/api/handler/source.go +++ b/internal/api/handler/source.go @@ -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. diff --git a/internal/api/router.go b/internal/api/router.go index 17d9bd1..d7af48d 100644 --- a/internal/api/router.go +++ b/internal/api/router.go @@ -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) diff --git a/services/funcs/index.html b/services/funcs/index.html index 3aaab6f..229289e 100644 --- a/services/funcs/index.html +++ b/services/funcs/index.html @@ -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) { diff --git a/services/funcs/main.go b/services/funcs/main.go index 1af680d..9d17f8b 100644 --- a/services/funcs/main.go +++ b/services/funcs/main.go @@ -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)