fix(source): add /services/{name}/source endpoint; fix 404 for service code view in funcs-console
This commit is contained in:
@@ -29,7 +29,7 @@ spec:
|
|||||||
- name: sless-registry-auth
|
- name: sless-registry-auth
|
||||||
containers:
|
containers:
|
||||||
- name: funcs
|
- 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:
|
ports:
|
||||||
- containerPort: 8090
|
- containerPort: 8090
|
||||||
env:
|
env:
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ spec:
|
|||||||
containers:
|
containers:
|
||||||
- name: operator
|
- name: operator
|
||||||
# При обновлении версии оператора — менять тег здесь (не latest!)
|
# При обновлении версии оператора — менять тег здесь (не 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 — чтобы всегда тянуть по точному тегу (не кешировать старый)
|
# Always — чтобы всегда тянуть по точному тегу (не кешировать старый)
|
||||||
imagePullPolicy: Always
|
imagePullPolicy: Always
|
||||||
ports:
|
ports:
|
||||||
|
|||||||
@@ -66,6 +66,43 @@ func (h *Handler) GetSource(w http.ResponseWriter, r *http.Request) {
|
|||||||
writeJSON(w, http.StatusOK, files)
|
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 и возвращает пользовательские файлы.
|
// extractSourceFilesFromTarGz читает tar.gz и возвращает пользовательские файлы.
|
||||||
// Dockerfile пропускается — он генерируется builder-ом автоматически.
|
// Dockerfile пропускается — он генерируется builder-ом автоматически.
|
||||||
// Бинарные файлы (содержат null bytes) возвращаются в base64 с binary=true.
|
// Бинарные файлы (содержат null bytes) возвращаются в base64 с binary=true.
|
||||||
|
|||||||
@@ -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.UpdateService).Methods(http.MethodPut)
|
||||||
v1.HandleFunc("/namespaces/{namespace}/services/{name}", h.DeleteService).Methods(http.MethodDelete)
|
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}/upload", h.UploadServiceCode).Methods(http.MethodPost)
|
||||||
|
v1.HandleFunc("/namespaces/{namespace}/services/{name}/source", h.GetServiceSource).Methods(http.MethodGet)
|
||||||
|
|
||||||
// Triggers CRUD
|
// Triggers CRUD
|
||||||
v1.HandleFunc("/namespaces/{namespace}/triggers", h.ListTriggers).Methods(http.MethodGet)
|
v1.HandleFunc("/namespaces/{namespace}/triggers", h.ListTriggers).Methods(http.MethodGet)
|
||||||
|
|||||||
@@ -416,7 +416,7 @@
|
|||||||
icon.style.transform = 'rotate(90deg)';
|
icon.style.transform = 'rotate(90deg)';
|
||||||
if (!loaded) {
|
if (!loaded) {
|
||||||
loaded = true;
|
loaded = true;
|
||||||
loadSource(body, ns, fn.name);
|
loadSource(body, ns, fn.name, fn.kind);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
body.style.display = 'none';
|
body.style.display = 'none';
|
||||||
@@ -458,12 +458,13 @@
|
|||||||
return btn;
|
return btn;
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadSource(body, ns, fnName) {
|
function loadSource(body, ns, fnName, fnKind) {
|
||||||
var msg = $el('div', 'src-msg');
|
var msg = $el('div', 'src-msg');
|
||||||
msg.textContent = 'Загрузка кода…';
|
msg.textContent = 'Загрузка кода…';
|
||||||
body.appendChild(msg);
|
body.appendChild(msg);
|
||||||
|
|
||||||
fetch('/funcs/' + ns + '/source/' + fnName)
|
var kindParam = fnKind === 'service' ? '?kind=service' : '';
|
||||||
|
fetch('/funcs/' + ns + '/source/' + fnName + kindParam)
|
||||||
.then(function (r) {
|
.then(function (r) {
|
||||||
body.removeChild(msg);
|
body.removeChild(msg);
|
||||||
if (!r.ok) {
|
if (!r.ok) {
|
||||||
|
|||||||
@@ -347,14 +347,19 @@ func servePlainText(w http.ResponseWriter, ns, externalURL string, fns []fnRespo
|
|||||||
fmt.Fprint(w, sb.String())
|
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) {
|
func proxySourceGet(w http.ResponseWriter, r *http.Request, operatorURL, serviceToken, ns, funcName string) {
|
||||||
if !isValidK8sName(funcName) {
|
if !isValidK8sName(funcName) {
|
||||||
http.Error(w, "invalid function name\n", http.StatusBadRequest)
|
http.Error(w, "invalid function name\n", http.StatusBadRequest)
|
||||||
return
|
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)
|
req, err := http.NewRequestWithContext(r.Context(), http.MethodGet, target, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, "internal error\n", http.StatusInternalServerError)
|
http.Error(w, "internal error\n", http.StatusInternalServerError)
|
||||||
|
|||||||
Reference in New Issue
Block a user