fix(api): DELETE несуществующего ресурса — 404 вместо 204 (function/service/trigger/job)

This commit is contained in:
Naeel
2026-03-21 06:45:55 +03:00
parent 146d3b5d5d
commit e8d0d78310
5 changed files with 24 additions and 20 deletions
+2 -2
View File
@@ -3,7 +3,7 @@
# Состав: # Состав:
# - ConfigMap: не-секретные env vars (S3_ENDPOINT, REGISTRY_HOST и т.д.) # - ConfigMap: не-секретные env vars (S3_ENDPOINT, REGISTRY_HOST и т.д.)
# - Secret: секретные данные (S3 keys, postgres DSN, API token, Harbor pass) # - Secret: секретные данные (S3 keys, postgres DSN, API token, Harbor pass)
# - Deployment: оператор naeel/sless-operator:v0.1.33 в namespace sless # - Deployment: оператор naeel/sless-operator:v0.1.44 в namespace sless
# - Service: ClusterIP :9090 (REST API) # - Service: ClusterIP :9090 (REST API)
# - Ingress: sless.kube5s.ru → :9090 (внешний доступ с TLS) # - Ingress: sless.kube5s.ru → :9090 (внешний доступ с TLS)
# #
@@ -74,7 +74,7 @@ spec:
containers: containers:
- name: operator - name: operator
# При обновлении версии оператора — менять тег здесь (не latest!) # При обновлении версии оператора — менять тег здесь (не latest!)
image: pearlharbor.registryk8s.services.ngcloud.ru/naeel/sless-operator:v0.1.43 image: pearlharbor.registryk8s.services.ngcloud.ru/naeel/sless-operator:v0.1.44
# Always — чтобы всегда тянуть по точному тегу (не кешировать старый) # Always — чтобы всегда тянуть по точному тегу (не кешировать старый)
imagePullPolicy: Always imagePullPolicy: Always
ports: ports:
+16 -15
View File
@@ -1,4 +1,5 @@
// Изменено: 2026-03-18 (добавлены created_at, last_built_at в functionResponse и fnToResponse) // Изменено: 2026-03-18 (добавлены created_at, last_built_at в functionResponse и fnToResponse)
// Изменено: 2026-03-21 (fix: DeleteFunction возвращает 404 вместо 204 при отсутствующем объекте)
// functions.go — CRUD handlers для Function CRD. // functions.go — CRUD handlers для Function CRD.
// Принимает JSON, создаёт/обновляет/удаляет k8s ресурсы Function. // Принимает JSON, создаёт/обновляет/удаляет k8s ресурсы Function.
// Namespace берётся из URL: /v1/namespaces/{namespace}/functions/{name} // Namespace берётся из URL: /v1/namespaces/{namespace}/functions/{name}
@@ -30,24 +31,24 @@ type functionRequest struct {
// functionResponse — ответ при чтении функции. // functionResponse — ответ при чтении функции.
type functionResponse struct { type functionResponse struct {
Name string `json:"name"` Name string `json:"name"`
Namespace string `json:"namespace"` Namespace string `json:"namespace"`
Runtime string `json:"runtime"` Runtime string `json:"runtime"`
Entrypoint string `json:"entrypoint"` Entrypoint string `json:"entrypoint"`
MemoryMB int32 `json:"memory_mb"` MemoryMB int32 `json:"memory_mb"`
TimeoutSec int32 `json:"timeout_sec"` TimeoutSec int32 `json:"timeout_sec"`
Env map[string]string `json:"env_vars"` Env map[string]string `json:"env_vars"`
S3Bucket string `json:"s3_bucket"` S3Bucket string `json:"s3_bucket"`
S3Key string `json:"s3_key"` S3Key string `json:"s3_key"`
Phase slessv1alpha1.FunctionPhase `json:"phase"` Phase slessv1alpha1.FunctionPhase `json:"phase"`
ImageRef string `json:"image_ref"` ImageRef string `json:"image_ref"`
Message string `json:"message,omitempty"` Message string `json:"message,omitempty"`
// CreatedAt — время создания CRD объекта (metadata.creationTimestamp). // CreatedAt — время создания CRD объекта (metadata.creationTimestamp).
// Пустое значение = "0001-01-01T00:00:00Z" сериализуется в "", опускаем через omitempty. // Пустое значение = "0001-01-01T00:00:00Z" сериализуется в "", опускаем через omitempty.
CreatedAt string `json:"created_at,omitempty"` CreatedAt string `json:"created_at,omitempty"`
// LastBuiltAt — время последней успешной сборки образа (status.lastBuiltAt). // LastBuiltAt — время последней успешной сборки образа (status.lastBuiltAt).
// nil если сборки ещё не было. // nil если сборки ещё не было.
LastBuiltAt string `json:"last_built_at,omitempty"` LastBuiltAt string `json:"last_built_at,omitempty"`
} }
// fnToResponse конвертирует CRD в ответ API. // fnToResponse конвертирует CRD в ответ API.
@@ -233,7 +234,7 @@ func (h *Handler) DeleteFunction(w http.ResponseWriter, r *http.Request) {
fn := &slessv1alpha1.Function{} fn := &slessv1alpha1.Function{}
if err := h.K8s.Get(r.Context(), client.ObjectKey{Name: name, Namespace: ns}, fn); err != nil { if err := h.K8s.Get(r.Context(), client.ObjectKey{Name: name, Namespace: ns}, fn); err != nil {
if errors.IsNotFound(err) { if errors.IsNotFound(err) {
w.WriteHeader(http.StatusNoContent) writeJSON(w, http.StatusNotFound, errResp("function not found"))
return return
} }
writeJSON(w, http.StatusInternalServerError, errResp(err.Error())) writeJSON(w, http.StatusInternalServerError, errResp(err.Error()))
+2 -1
View File
@@ -1,4 +1,5 @@
// Изменено: 2026-03-20 (merge: FunctionJob теперь самодостаточен — убран FunctionRef, добавлены Runtime/Entrypoint/Env) // Изменено: 2026-03-20 (merge: FunctionJob теперь самодостаточен — убран FunctionRef, добавлены Runtime/Entrypoint/Env)
// Изменено: 2026-03-21 (fix: DeleteJob возвращает 404 вместо 204 при отсутствующем объекте)
// jobs.go — CRUD handlers для FunctionJob CRD. // jobs.go — CRUD handlers для FunctionJob CRD.
// Создаёт/читает/удаляет k8s FunctionJob ресурсы. // Создаёт/читает/удаляет k8s FunctionJob ресурсы.
// Namespace берётся из URL: /v1/namespaces/{namespace}/jobs/{name} // Namespace берётся из URL: /v1/namespaces/{namespace}/jobs/{name}
@@ -159,7 +160,7 @@ func (h *Handler) DeleteJob(w http.ResponseWriter, r *http.Request) {
var job slessv1alpha1.FunctionJob var job slessv1alpha1.FunctionJob
if err := h.K8s.Get(r.Context(), client.ObjectKey{Namespace: ns, Name: name}, &job); err != nil { if err := h.K8s.Get(r.Context(), client.ObjectKey{Namespace: ns, Name: name}, &job); err != nil {
if errors.IsNotFound(err) { if errors.IsNotFound(err) {
w.WriteHeader(http.StatusNoContent) writeJSON(w, http.StatusNotFound, errResp("job not found"))
return return
} }
h.Log.Error("get FunctionJob for delete", "err", err) h.Log.Error("get FunctionJob for delete", "err", err)
+2 -1
View File
@@ -1,4 +1,5 @@
// Создано: 2026-03-20 (function-service-split) // Создано: 2026-03-20 (function-service-split)
// Изменено: 2026-03-21 (fix: DeleteService возвращает 404 вместо 204 при отсутствующем объекте)
// services.go — CRUD handlers для Service CRD (sless_service). // services.go — CRUD handlers для Service CRD (sless_service).
// sless_service = long-running Deployment + URL. Каждый вызов проксируется к поду. // sless_service = long-running Deployment + URL. Каждый вызов проксируется к поду.
// Namespace берётся из URL: /v1/namespaces/{namespace}/services/{name} // Namespace берётся из URL: /v1/namespaces/{namespace}/services/{name}
@@ -228,7 +229,7 @@ func (h *Handler) DeleteService(w http.ResponseWriter, r *http.Request) {
svc := &slessv1alpha1.Service{} svc := &slessv1alpha1.Service{}
if err := h.K8s.Get(r.Context(), client.ObjectKey{Name: name, Namespace: ns}, svc); err != nil { if err := h.K8s.Get(r.Context(), client.ObjectKey{Name: name, Namespace: ns}, svc); err != nil {
if errors.IsNotFound(err) { if errors.IsNotFound(err) {
w.WriteHeader(http.StatusNoContent) writeJSON(w, http.StatusNotFound, errResp("service not found"))
return return
} }
writeJSON(w, http.StatusInternalServerError, errResp(err.Error())) writeJSON(w, http.StatusInternalServerError, errResp(err.Error()))
+2 -1
View File
@@ -1,4 +1,5 @@
// Изменено: 2026-03-08 // Изменено: 2026-03-08
// Изменено: 2026-03-21 (fix: DeleteTrigger возвращает 404 вместо 204 при отсутствующем объекте)
// triggers.go — CRUD handlers для Trigger CRD. // triggers.go — CRUD handlers для Trigger CRD.
// Триггеры привязаны к Function через FunctionRef. // Триггеры привязаны к Function через FunctionRef.
// Namespace берётся из URL: /v1/namespaces/{namespace}/triggers/{name} // Namespace берётся из URL: /v1/namespaces/{namespace}/triggers/{name}
@@ -140,7 +141,7 @@ func (h *Handler) DeleteTrigger(w http.ResponseWriter, r *http.Request) {
tr := &slessv1alpha1.Trigger{} tr := &slessv1alpha1.Trigger{}
if err := h.K8s.Get(r.Context(), client.ObjectKey{Name: name, Namespace: ns}, tr); err != nil { if err := h.K8s.Get(r.Context(), client.ObjectKey{Name: name, Namespace: ns}, tr); err != nil {
if errors.IsNotFound(err) { if errors.IsNotFound(err) {
w.WriteHeader(http.StatusNoContent) writeJSON(w, http.StatusNotFound, errResp("trigger not found"))
return return
} }
writeJSON(w, http.StatusInternalServerError, errResp(err.Error())) writeJSON(w, http.StatusInternalServerError, errResp(err.Error()))