feat: NodeJS pg-info function; funcs endpoint: filter + created_at/last_built_at; operator v0.1.32
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
// Изменено: 2026-03-11
|
||||
// Изменено: 2026-03-18 (добавлены created_at, last_built_at в functionResponse и fnToResponse)
|
||||
// functions.go — CRUD handlers для Function CRD.
|
||||
// Принимает JSON, создаёт/обновляет/удаляет k8s ресурсы Function.
|
||||
// Namespace берётся из URL: /v1/namespaces/{namespace}/functions/{name}
|
||||
@@ -30,23 +30,29 @@ type functionRequest struct {
|
||||
|
||||
// functionResponse — ответ при чтении функции.
|
||||
type functionResponse struct {
|
||||
Name string `json:"name"`
|
||||
Namespace string `json:"namespace"`
|
||||
Runtime string `json:"runtime"`
|
||||
Entrypoint string `json:"entrypoint"`
|
||||
MemoryMB int32 `json:"memory_mb"`
|
||||
TimeoutSec int32 `json:"timeout_sec"`
|
||||
Env map[string]string `json:"env_vars"`
|
||||
S3Bucket string `json:"s3_bucket"`
|
||||
S3Key string `json:"s3_key"`
|
||||
Phase slessv1alpha1.FunctionPhase `json:"phase"`
|
||||
ImageRef string `json:"image_ref"`
|
||||
Message string `json:"message,omitempty"`
|
||||
Name string `json:"name"`
|
||||
Namespace string `json:"namespace"`
|
||||
Runtime string `json:"runtime"`
|
||||
Entrypoint string `json:"entrypoint"`
|
||||
MemoryMB int32 `json:"memory_mb"`
|
||||
TimeoutSec int32 `json:"timeout_sec"`
|
||||
Env map[string]string `json:"env_vars"`
|
||||
S3Bucket string `json:"s3_bucket"`
|
||||
S3Key string `json:"s3_key"`
|
||||
Phase slessv1alpha1.FunctionPhase `json:"phase"`
|
||||
ImageRef string `json:"image_ref"`
|
||||
Message string `json:"message,omitempty"`
|
||||
// CreatedAt — время создания CRD объекта (metadata.creationTimestamp).
|
||||
// Пустое значение = "0001-01-01T00:00:00Z" сериализуется в "", опускаем через omitempty.
|
||||
CreatedAt string `json:"created_at,omitempty"`
|
||||
// LastBuiltAt — время последней успешной сборки образа (status.lastBuiltAt).
|
||||
// nil если сборки ещё не было.
|
||||
LastBuiltAt string `json:"last_built_at,omitempty"`
|
||||
}
|
||||
|
||||
// fnToResponse конвертирует CRD в ответ API.
|
||||
func fnToResponse(fn *slessv1alpha1.Function) functionResponse {
|
||||
return functionResponse{
|
||||
resp := functionResponse{
|
||||
Name: fn.Name,
|
||||
Namespace: fn.Namespace,
|
||||
Runtime: fn.Spec.Runtime,
|
||||
@@ -60,6 +66,14 @@ func fnToResponse(fn *slessv1alpha1.Function) functionResponse {
|
||||
ImageRef: fn.Status.ImageRef,
|
||||
Message: fn.Status.Message,
|
||||
}
|
||||
// creationTimestamp — всегда заполнен k8s, но zero value опускаем.
|
||||
if !fn.CreationTimestamp.IsZero() {
|
||||
resp.CreatedAt = fn.CreationTimestamp.UTC().Format("2006-01-02 15:04:05 UTC")
|
||||
}
|
||||
if fn.Status.LastBuiltAt != nil && !fn.Status.LastBuiltAt.IsZero() {
|
||||
resp.LastBuiltAt = fn.Status.LastBuiltAt.UTC().Format("2006-01-02 15:04:05 UTC")
|
||||
}
|
||||
return resp
|
||||
}
|
||||
|
||||
// ListFunctions — GET /v1/namespaces/{namespace}/functions
|
||||
@@ -115,13 +129,20 @@ func (h *Handler) CreateFunction(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
if err := h.K8s.Create(r.Context(), fn); err != nil {
|
||||
if errors.IsAlreadyExists(err) {
|
||||
// Если существующая функция в статусе Failed (build провалился, terraform не
|
||||
// добавил её в state) — удаляем её и пересоздаём, иначе клиент получит 409 навсегда.
|
||||
// IsAlreadyExists может прийти из кеша controller-runtime (split-brain):
|
||||
// объект удалён из etcd, но кеш informer ещё южив. Делаем uncached Get:
|
||||
// если реально NotFound — кеш устарел, пересоздаём.
|
||||
// если существует и фаза Failed — тоже пересоздаём (build провалился, терраформ не добавил в state).
|
||||
// если существует и фаза Ready/Building — возвращаем 409 (функция реально есть).
|
||||
existing := &slessv1alpha1.Function{}
|
||||
if getErr := h.K8s.Get(r.Context(), client.ObjectKey{Name: req.Name, Namespace: ns}, existing); getErr == nil &&
|
||||
existing.Status.Phase == slessv1alpha1.FunctionPhaseFailed {
|
||||
_ = h.K8s.Delete(r.Context(), existing)
|
||||
// Создаём заново с теми же параметрами
|
||||
getErr := h.K8s.Get(r.Context(), client.ObjectKey{Name: req.Name, Namespace: ns}, existing)
|
||||
shouldRecreate := errors.IsNotFound(getErr) ||
|
||||
(getErr == nil && existing.Status.Phase == slessv1alpha1.FunctionPhaseFailed)
|
||||
if shouldRecreate {
|
||||
if getErr == nil {
|
||||
_ = h.K8s.Delete(r.Context(), existing)
|
||||
}
|
||||
// Сбрасываем ResourceVersion — при split-brain etcd считает объект новым
|
||||
fn.ResourceVersion = ""
|
||||
if createErr := h.K8s.Create(r.Context(), fn); createErr != nil {
|
||||
writeJSON(w, http.StatusInternalServerError, errResp(createErr.Error()))
|
||||
|
||||
Reference in New Issue
Block a user