mirror of
https://github.com/GlueOps/autoglue.git
synced 2026-02-13 04:40:05 +01:00
Refactor routing logic (Chi can be a pain when you're managing large sets of routes, but its one of the better options when considering a potential gRPC future)
Upgrade API Generation to fully support OAS3.1
Update swagger interface to RapiDoc - the old swagger interface doesnt support OAS3.1 yet
Docs are now embedded as part of the UI - once logged in they pick up the cookies and org id from what gets set by the UI, but you can override it
Other updates include better portability of the db-studio
Signed-off-by: allanice001 <allanice001@gmail.com>
1260 lines
41 KiB
Go
1260 lines
41 KiB
Go
package handlers
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"net/http"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/glueops/autoglue/internal/api/httpmiddleware"
|
|
"github.com/glueops/autoglue/internal/common"
|
|
"github.com/glueops/autoglue/internal/handlers/dto"
|
|
"github.com/glueops/autoglue/internal/models"
|
|
"github.com/glueops/autoglue/internal/utils"
|
|
"github.com/go-chi/chi/v5"
|
|
"github.com/google/uuid"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
// -- Node Pools Core
|
|
|
|
// ListNodePools godoc
|
|
//
|
|
// @ID ListNodePools
|
|
// @Summary List node pools (org scoped)
|
|
// @Description Returns node pools for the organization in X-Org-ID.
|
|
// @Tags NodePools
|
|
// @Produce json
|
|
// @Param X-Org-ID header string false "Organization UUID"
|
|
// @Param q query string false "Name contains (case-insensitive)"
|
|
// @Success 200 {array} dto.NodePoolResponse
|
|
// @Failure 401 {string} string "Unauthorized"
|
|
// @Failure 403 {string} string "organization required"
|
|
// @Failure 500 {string} string "failed to list node pools"
|
|
// @Router /node-pools [get]
|
|
// @Security BearerAuth
|
|
// @Security OrgKeyAuth
|
|
// @Security OrgSecretAuth
|
|
func ListNodePools(db *gorm.DB) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
orgID, ok := httpmiddleware.OrgIDFrom(r.Context())
|
|
if !ok {
|
|
utils.WriteError(w, http.StatusForbidden, "org_required", "specify X-Org-ID")
|
|
return
|
|
}
|
|
|
|
q := db.Where("organization_id = ?", orgID)
|
|
if needle := strings.TrimSpace(r.URL.Query().Get("q")); needle != "" {
|
|
q = q.Where("name LIKE ?", "%"+needle+"%")
|
|
}
|
|
|
|
var pools []models.NodePool
|
|
if err := q.
|
|
Preload("Servers").
|
|
Preload("Labels").
|
|
Preload("Taints").
|
|
Preload("Annotations").
|
|
Order("created_at DESC").
|
|
Find(&pools).Error; err != nil {
|
|
utils.WriteError(w, http.StatusInternalServerError, "db_error", "db error")
|
|
return
|
|
}
|
|
|
|
out := make([]dto.NodePoolResponse, 0, len(pools))
|
|
for _, p := range pools {
|
|
npr := dto.NodePoolResponse{
|
|
AuditFields: p.AuditFields,
|
|
Name: p.Name,
|
|
Role: dto.NodeRole(p.Role),
|
|
Servers: make([]dto.ServerResponse, 0, len(p.Servers)),
|
|
Labels: make([]dto.LabelResponse, 0, len(p.Labels)),
|
|
Taints: make([]dto.TaintResponse, 0, len(p.Taints)),
|
|
Annotations: make([]dto.AnnotationResponse, 0, len(p.Annotations)),
|
|
}
|
|
//Servers
|
|
for _, s := range p.Servers {
|
|
outSrv := dto.ServerResponse{
|
|
ID: s.ID,
|
|
Hostname: s.Hostname,
|
|
PublicIPAddress: s.PublicIPAddress,
|
|
PrivateIPAddress: s.PrivateIPAddress,
|
|
OrganizationID: s.OrganizationID,
|
|
SshKeyID: s.SshKeyID,
|
|
SSHUser: s.SSHUser,
|
|
Role: s.Role,
|
|
Status: s.Status,
|
|
CreatedAt: s.CreatedAt.UTC().Format(time.RFC3339),
|
|
UpdatedAt: s.UpdatedAt.UTC().Format(time.RFC3339),
|
|
// add more fields as needed
|
|
}
|
|
npr.Servers = append(npr.Servers, outSrv)
|
|
}
|
|
//Labels
|
|
for _, l := range p.Labels {
|
|
outL := dto.LabelResponse{
|
|
AuditFields: common.AuditFields{
|
|
ID: l.ID,
|
|
OrganizationID: l.OrganizationID,
|
|
CreatedAt: l.CreatedAt,
|
|
UpdatedAt: l.UpdatedAt,
|
|
},
|
|
Key: l.Key,
|
|
Value: l.Value,
|
|
}
|
|
npr.Labels = append(npr.Labels, outL)
|
|
}
|
|
// Taints
|
|
for _, t := range p.Taints {
|
|
outT := dto.TaintResponse{
|
|
ID: t.ID,
|
|
OrganizationID: t.OrganizationID,
|
|
CreatedAt: t.CreatedAt.UTC().Format(time.RFC3339),
|
|
UpdatedAt: t.UpdatedAt.UTC().Format(time.RFC3339),
|
|
Key: t.Key,
|
|
Value: t.Value,
|
|
Effect: t.Effect,
|
|
}
|
|
npr.Taints = append(npr.Taints, outT)
|
|
}
|
|
// Annotations
|
|
for _, a := range p.Annotations {
|
|
outA := dto.AnnotationResponse{
|
|
AuditFields: common.AuditFields{
|
|
ID: a.ID,
|
|
OrganizationID: a.OrganizationID,
|
|
CreatedAt: a.CreatedAt,
|
|
UpdatedAt: a.UpdatedAt,
|
|
},
|
|
Key: a.Key,
|
|
Value: a.Value,
|
|
}
|
|
npr.Annotations = append(npr.Annotations, outA)
|
|
}
|
|
|
|
out = append(out, npr)
|
|
}
|
|
utils.WriteJSON(w, http.StatusOK, out)
|
|
}
|
|
}
|
|
|
|
// GetNodePool godoc
|
|
//
|
|
// @ID GetNodePool
|
|
// @Summary Get node pool by ID (org scoped)
|
|
// @Description Returns one node pool. Add `include=servers` to include servers.
|
|
// @Tags NodePools
|
|
// @Produce json
|
|
// @Param X-Org-ID header string false "Organization UUID"
|
|
// @Param id path string true "Node Pool ID (UUID)"
|
|
// @Success 200 {object} dto.NodePoolResponse
|
|
// @Failure 400 {string} string "invalid id"
|
|
// @Failure 401 {string} string "Unauthorized"
|
|
// @Failure 403 {string} string "organization required"
|
|
// @Failure 404 {string} string "not found"
|
|
// @Failure 500 {string} string "fetch failed"
|
|
// @Router /node-pools/{id} [get]
|
|
// @Security BearerAuth
|
|
// @Security OrgKeyAuth
|
|
// @Security OrgSecretAuth
|
|
func GetNodePool(db *gorm.DB) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
orgID, ok := httpmiddleware.OrgIDFrom(r.Context())
|
|
if !ok {
|
|
utils.WriteError(w, http.StatusForbidden, "org_required", "specify X-Org-ID")
|
|
return
|
|
}
|
|
|
|
id, err := uuid.Parse(chi.URLParam(r, "id"))
|
|
if err != nil {
|
|
utils.WriteError(w, http.StatusBadRequest, "id_required", "id required")
|
|
return
|
|
}
|
|
|
|
var out dto.NodePoolResponse
|
|
if err := db.Model(&models.NodePool{}).Preload("Servers").Where("id = ? AND organization_id = ?", id, orgID).First(&out, id).Error; err != nil {
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
utils.WriteError(w, http.StatusNotFound, "node_pool_not_found", "node pool not found")
|
|
return
|
|
}
|
|
utils.WriteError(w, http.StatusInternalServerError, "db_error", "db error")
|
|
return
|
|
}
|
|
utils.WriteJSON(w, http.StatusOK, out)
|
|
}
|
|
}
|
|
|
|
// CreateNodePool godoc
|
|
//
|
|
// @ID CreateNodePool
|
|
// @Summary Create node pool (org scoped)
|
|
// @Description Creates a node pool. Optionally attach initial servers.
|
|
// @Tags NodePools
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param X-Org-ID header string false "Organization UUID"
|
|
// @Param body body dto.CreateNodePoolRequest true "NodePool payload"
|
|
// @Success 201 {object} dto.NodePoolResponse
|
|
// @Failure 400 {string} string "invalid json / missing fields / invalid server_ids"
|
|
// @Failure 401 {string} string "Unauthorized"
|
|
// @Failure 403 {string} string "organization required"
|
|
// @Failure 500 {string} string "create failed"
|
|
// @Router /node-pools [post]
|
|
// @Security BearerAuth
|
|
// @Security OrgKeyAuth
|
|
// @Security OrgSecretAuth
|
|
func CreateNodePool(db *gorm.DB) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
orgID, ok := httpmiddleware.OrgIDFrom(r.Context())
|
|
if !ok {
|
|
utils.WriteError(w, http.StatusForbidden, "org_required", "specify X-Org-ID")
|
|
return
|
|
}
|
|
|
|
var req dto.CreateNodePoolRequest
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
utils.WriteError(w, http.StatusBadRequest, "bad_request", "bad request")
|
|
return
|
|
}
|
|
|
|
req.Name = strings.TrimSpace(req.Name)
|
|
req.Role = dto.NodeRole(strings.TrimSpace(string(req.Role)))
|
|
|
|
if req.Name == "" || req.Role == "" {
|
|
utils.WriteError(w, http.StatusBadRequest, "bad_request", "missing name/role")
|
|
return
|
|
}
|
|
|
|
n := models.NodePool{
|
|
AuditFields: common.AuditFields{
|
|
OrganizationID: orgID,
|
|
},
|
|
Name: req.Name,
|
|
Role: string(req.Role),
|
|
}
|
|
|
|
if err := db.Create(&n).Error; err != nil {
|
|
utils.WriteError(w, http.StatusInternalServerError, "db_error", "db error")
|
|
return
|
|
}
|
|
|
|
out := dto.NodePoolResponse{
|
|
AuditFields: n.AuditFields,
|
|
Name: n.Name,
|
|
Role: dto.NodeRole(n.Role),
|
|
}
|
|
utils.WriteJSON(w, http.StatusCreated, out)
|
|
}
|
|
}
|
|
|
|
// UpdateNodePool godoc
|
|
//
|
|
// @ID UpdateNodePool
|
|
// @Summary Update node pool (org scoped)
|
|
// @Description Partially update node pool fields.
|
|
// @Tags NodePools
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param X-Org-ID header string false "Organization UUID"
|
|
// @Param id path string true "Node Pool ID (UUID)"
|
|
// @Param body body dto.UpdateNodePoolRequest true "Fields to update"
|
|
// @Success 200 {object} dto.NodePoolResponse
|
|
// @Failure 400 {string} string "invalid id / invalid json"
|
|
// @Failure 401 {string} string "Unauthorized"
|
|
// @Failure 403 {string} string "organization required"
|
|
// @Failure 404 {string} string "not found"
|
|
// @Failure 500 {string} string "update failed"
|
|
// @Router /node-pools/{id} [patch]
|
|
// @Security BearerAuth
|
|
// @Security OrgKeyAuth
|
|
// @Security OrgSecretAuth
|
|
func UpdateNodePool(db *gorm.DB) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
orgID, ok := httpmiddleware.OrgIDFrom(r.Context())
|
|
if !ok {
|
|
utils.WriteError(w, http.StatusForbidden, "org_required", "specify X-Org-ID")
|
|
return
|
|
}
|
|
|
|
id, err := uuid.Parse(chi.URLParam(r, "id"))
|
|
if err != nil {
|
|
utils.WriteError(w, http.StatusBadRequest, "id_required", "id required")
|
|
return
|
|
}
|
|
|
|
var n models.NodePool
|
|
if err := db.Where("id = ? AND organization_id = ?", id, orgID).First(&n).Error; err != nil {
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
utils.WriteError(w, http.StatusNotFound, "node_pool_not_found", "node pool not found")
|
|
return
|
|
}
|
|
utils.WriteError(w, http.StatusInternalServerError, "db_error", "db error")
|
|
return
|
|
}
|
|
|
|
var req dto.UpdateNodePoolRequest
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
utils.WriteError(w, http.StatusBadRequest, "bad_request", "bad request")
|
|
return
|
|
}
|
|
|
|
if req.Name != nil {
|
|
n.Name = strings.TrimSpace(*req.Name)
|
|
}
|
|
if req.Role != nil {
|
|
v := dto.NodeRole(strings.TrimSpace(string(*req.Role)))
|
|
n.Role = string(v)
|
|
}
|
|
|
|
if err := db.Save(&n).Error; err != nil {
|
|
utils.WriteError(w, http.StatusInternalServerError, "db_error", "db error")
|
|
return
|
|
}
|
|
out := dto.NodePoolResponse{
|
|
AuditFields: n.AuditFields,
|
|
Name: n.Name,
|
|
Role: dto.NodeRole(n.Role),
|
|
}
|
|
utils.WriteJSON(w, http.StatusOK, out)
|
|
}
|
|
}
|
|
|
|
// DeleteNodePool godoc
|
|
//
|
|
// @ID DeleteNodePool
|
|
// @Summary Delete node pool (org scoped)
|
|
// @Description Permanently deletes the node pool.
|
|
// @Tags NodePools
|
|
// @Produce json
|
|
// @Param X-Org-ID header string false "Organization UUID"
|
|
// @Param id path string true "Node Pool ID (UUID)"
|
|
// @Success 204 "No Content"
|
|
// @Failure 400 {string} string "invalid id"
|
|
// @Failure 401 {string} string "Unauthorized"
|
|
// @Failure 403 {string} string "organization required"
|
|
// @Failure 500 {string} string "delete failed"
|
|
// @Router /node-pools/{id} [delete]
|
|
// @Security BearerAuth
|
|
// @Security OrgKeyAuth
|
|
// @Security OrgSecretAuth
|
|
func DeleteNodePool(db *gorm.DB) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
orgID, ok := httpmiddleware.OrgIDFrom(r.Context())
|
|
if !ok {
|
|
utils.WriteError(w, http.StatusForbidden, "org_required", "specify X-Org-ID")
|
|
return
|
|
}
|
|
|
|
id, err := uuid.Parse(chi.URLParam(r, "id"))
|
|
if err != nil {
|
|
utils.WriteError(w, http.StatusBadRequest, "id_required", "id required")
|
|
return
|
|
}
|
|
|
|
if err := db.Where("id = ? AND organization_id = ?", id, orgID).Delete(&models.NodePool{}).Error; err != nil {
|
|
utils.WriteError(w, http.StatusInternalServerError, "db_error", "db error")
|
|
return
|
|
}
|
|
w.WriteHeader(http.StatusNoContent)
|
|
}
|
|
}
|
|
|
|
// -- Node Pools Servers
|
|
|
|
// ListNodePoolServers godoc
|
|
//
|
|
// @ID ListNodePoolServers
|
|
// @Summary List servers attached to a node pool (org scoped)
|
|
// @Tags NodePools
|
|
// @Produce json
|
|
// @Param X-Org-ID header string false "Organization UUID"
|
|
// @Param id path string true "Node Pool ID (UUID)"
|
|
// @Success 200 {array} dto.ServerResponse
|
|
// @Failure 400 {string} string "invalid id"
|
|
// @Failure 401 {string} string "Unauthorized"
|
|
// @Failure 403 {string} string "organization required"
|
|
// @Failure 404 {string} string "not found"
|
|
// @Failure 500 {string} string "fetch failed"
|
|
// @Router /node-pools/{id}/servers [get]
|
|
// @Security BearerAuth
|
|
// @Security OrgKeyAuth
|
|
// @Security OrgSecretAuth
|
|
func ListNodePoolServers(db *gorm.DB) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
orgID, ok := httpmiddleware.OrgIDFrom(r.Context())
|
|
if !ok {
|
|
utils.WriteError(w, http.StatusForbidden, "org_required", "specify X-Org-ID")
|
|
return
|
|
}
|
|
|
|
id, err := uuid.Parse(chi.URLParam(r, "id"))
|
|
if err != nil {
|
|
utils.WriteError(w, http.StatusBadRequest, "id_required", "id required")
|
|
return
|
|
}
|
|
|
|
var np models.NodePool
|
|
if err := db.Where("id = ? AND organization_id = ?", id, orgID).Preload("Servers").First(&np).Error; err != nil {
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
utils.WriteError(w, http.StatusNotFound, "node_pool_not_found", "node pool not found")
|
|
return
|
|
}
|
|
utils.WriteError(w, http.StatusInternalServerError, "db_error", "db error")
|
|
return
|
|
}
|
|
|
|
out := make([]dto.ServerResponse, 0, len(np.Servers))
|
|
for _, server := range np.Servers {
|
|
out = append(out, dto.ServerResponse{
|
|
ID: server.ID,
|
|
OrganizationID: server.OrganizationID,
|
|
Hostname: server.Hostname,
|
|
PrivateIPAddress: server.PrivateIPAddress,
|
|
PublicIPAddress: server.PublicIPAddress,
|
|
Role: server.Role,
|
|
SshKeyID: server.SshKeyID,
|
|
SSHUser: server.SSHUser,
|
|
Status: server.Status,
|
|
CreatedAt: server.CreatedAt.UTC().Format(time.RFC3339),
|
|
UpdatedAt: server.UpdatedAt.UTC().Format(time.RFC3339),
|
|
})
|
|
}
|
|
utils.WriteJSON(w, http.StatusOK, out)
|
|
}
|
|
}
|
|
|
|
// AttachNodePoolServers godoc
|
|
//
|
|
// @ID AttachNodePoolServers
|
|
// @Summary Attach servers to a node pool (org scoped)
|
|
// @Tags NodePools
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param X-Org-ID header string false "Organization UUID"
|
|
// @Param id path string true "Node Pool ID (UUID)"
|
|
// @Param body body dto.AttachServersRequest true "Server IDs to attach"
|
|
// @Success 204 {string} string "No Content"
|
|
// @Failure 400 {string} string "invalid id / invalid server_ids"
|
|
// @Failure 401 {string} string "Unauthorized"
|
|
// @Failure 403 {string} string "organization required"
|
|
// @Failure 404 {string} string "not found"
|
|
// @Failure 500 {string} string "attach failed"
|
|
// @Router /node-pools/{id}/servers [post]
|
|
// @Security BearerAuth
|
|
// @Security OrgKeyAuth
|
|
// @Security OrgSecretAuth
|
|
func AttachNodePoolServers(db *gorm.DB) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
orgID, ok := httpmiddleware.OrgIDFrom(r.Context())
|
|
if !ok {
|
|
utils.WriteError(w, http.StatusForbidden, "org_required", "specify X-Org-ID")
|
|
return
|
|
}
|
|
|
|
id, err := uuid.Parse(chi.URLParam(r, "id"))
|
|
if err != nil {
|
|
utils.WriteError(w, http.StatusBadRequest, "id_required", "id required")
|
|
return
|
|
}
|
|
|
|
var np models.NodePool
|
|
if err := db.Where("id = ? AND organization_id = ?", id, orgID).First(&np).Error; err != nil {
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
utils.WriteError(w, http.StatusNotFound, "node_pool_not_found", "node pool not found")
|
|
return
|
|
}
|
|
utils.WriteError(w, http.StatusInternalServerError, "db_error", "db error")
|
|
return
|
|
}
|
|
|
|
var req dto.AttachServersRequest
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
utils.WriteError(w, http.StatusBadRequest, "bad_request", "bad request")
|
|
return
|
|
}
|
|
|
|
ids, err := parseUUIDs(req.ServerIDs)
|
|
if err != nil {
|
|
utils.WriteError(w, http.StatusBadRequest, "bad_request", "invalid server_ids")
|
|
return
|
|
}
|
|
|
|
if len(ids) == 0 {
|
|
// nothing to attach
|
|
utils.WriteError(w, http.StatusBadRequest, "bad_request", "nothing to attach")
|
|
return
|
|
}
|
|
|
|
// validate IDs belong to org
|
|
if err := ensureServersBelongToOrg(orgID, ids, db); err != nil {
|
|
utils.WriteError(w, http.StatusBadRequest, "bad_request", "invalid server_ids for this organization")
|
|
return
|
|
}
|
|
|
|
// fetch only the requested servers
|
|
var servers []models.Server
|
|
if err := db.Where("organization_id = ? AND id IN ?", orgID, ids).Find(&servers).Error; err != nil {
|
|
utils.WriteError(w, http.StatusInternalServerError, "db_error", "attach db error")
|
|
return
|
|
}
|
|
|
|
if len(servers) == 0 {
|
|
w.WriteHeader(http.StatusNoContent)
|
|
return
|
|
}
|
|
|
|
if err := db.Model(&np).Association("Servers").Append(&servers); err != nil {
|
|
utils.WriteError(w, http.StatusInternalServerError, "db_error", "attach failed")
|
|
return
|
|
}
|
|
|
|
w.WriteHeader(http.StatusNoContent)
|
|
}
|
|
}
|
|
|
|
// DetachNodePoolServer godoc
|
|
//
|
|
// @ID DetachNodePoolServer
|
|
// @Summary Detach one server from a node pool (org scoped)
|
|
// @Tags NodePools
|
|
// @Produce json
|
|
// @Param X-Org-ID header string false "Organization UUID"
|
|
// @Param id path string true "Node Pool ID (UUID)"
|
|
// @Param serverId path string true "Server ID (UUID)"
|
|
// @Success 204 {string} string "No Content"
|
|
// @Failure 400 {string} string "invalid id"
|
|
// @Failure 401 {string} string "Unauthorized"
|
|
// @Failure 403 {string} string "organization required"
|
|
// @Failure 404 {string} string "not found"
|
|
// @Failure 500 {string} string "detach failed"
|
|
// @Router /node-pools/{id}/servers/{serverId} [delete]
|
|
// @Security BearerAuth
|
|
// @Security OrgKeyAuth
|
|
// @Security OrgSecretAuth
|
|
func DetachNodePoolServer(db *gorm.DB) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
orgID, ok := httpmiddleware.OrgIDFrom(r.Context())
|
|
if !ok {
|
|
utils.WriteError(w, http.StatusForbidden, "org_required", "specify X-Org-ID")
|
|
return
|
|
}
|
|
id, err := uuid.Parse(chi.URLParam(r, "id"))
|
|
if err != nil {
|
|
utils.WriteError(w, http.StatusBadRequest, "id_required", "node pool id required")
|
|
return
|
|
}
|
|
serverId, err := uuid.Parse(chi.URLParam(r, "serverId"))
|
|
if err != nil {
|
|
utils.WriteError(w, http.StatusBadRequest, "id_required", "server id required")
|
|
return
|
|
}
|
|
var np models.NodePool
|
|
if err := db.Where("id = ? AND organization_id = ?", id, orgID).First(&np).Error; err != nil {
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
utils.WriteError(w, http.StatusNotFound, "node_pool_not_found", "node pool not found")
|
|
return
|
|
}
|
|
utils.WriteError(w, http.StatusInternalServerError, "db_error", "db error")
|
|
return
|
|
}
|
|
|
|
var s models.Server
|
|
if err := db.Where("id = ? AND organization_id = ?", serverId, orgID).First(&s).Error; err != nil {
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
utils.WriteError(w, http.StatusNotFound, "server_not_found", "server not found")
|
|
return
|
|
}
|
|
utils.WriteError(w, http.StatusInternalServerError, "db_error", "db error")
|
|
return
|
|
}
|
|
|
|
if err := db.Model(&np).Association("Servers").Delete(&s); err != nil {
|
|
utils.WriteError(w, http.StatusInternalServerError, "db_error", "detach error")
|
|
return
|
|
}
|
|
w.WriteHeader(http.StatusNoContent)
|
|
}
|
|
}
|
|
|
|
// -- Node Pools Taints
|
|
|
|
// ListNodePoolTaints godoc
|
|
//
|
|
// @ID ListNodePoolTaints
|
|
// @Summary List taints attached to a node pool (org scoped)
|
|
// @Tags NodePools
|
|
// @Produce json
|
|
// @Param X-Org-ID header string false "Organization UUID"
|
|
// @Param id path string true "Node Pool ID (UUID)"
|
|
// @Success 200 {array} dto.TaintResponse
|
|
// @Failure 400 {string} string "invalid id"
|
|
// @Failure 401 {string} string "Unauthorized"
|
|
// @Failure 403 {string} string "organization required"
|
|
// @Failure 404 {string} string "not found"
|
|
// @Failure 500 {string} string "fetch failed"
|
|
// @Router /node-pools/{id}/taints [get]
|
|
// @Security BearerAuth
|
|
// @Security OrgKeyAuth
|
|
// @Security OrgSecretAuth
|
|
func ListNodePoolTaints(db *gorm.DB) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
orgID, ok := httpmiddleware.OrgIDFrom(r.Context())
|
|
if !ok {
|
|
utils.WriteError(w, http.StatusForbidden, "org_required", "specify X-Org-ID")
|
|
return
|
|
}
|
|
|
|
id, err := uuid.Parse(chi.URLParam(r, "id"))
|
|
if err != nil {
|
|
utils.WriteError(w, http.StatusBadRequest, "id_required", "node pool id required")
|
|
return
|
|
}
|
|
|
|
var np models.NodePool
|
|
if err := db.Where("id = ? AND organization_id = ?", id, orgID).Preload("Taints").First(&np).Error; err != nil {
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
utils.WriteError(w, http.StatusNotFound, "node_pool_not_found", "node pool not found")
|
|
return
|
|
}
|
|
utils.WriteError(w, http.StatusInternalServerError, "db_error", "db error")
|
|
return
|
|
}
|
|
|
|
out := make([]dto.TaintResponse, 0, len(np.Taints))
|
|
for _, t := range np.Taints {
|
|
out = append(out, dto.TaintResponse{
|
|
ID: t.ID,
|
|
Key: t.Key,
|
|
Value: t.Value,
|
|
Effect: t.Effect,
|
|
})
|
|
}
|
|
utils.WriteJSON(w, http.StatusOK, out)
|
|
}
|
|
}
|
|
|
|
// AttachNodePoolTaints godoc
|
|
//
|
|
// @ID AttachNodePoolTaints
|
|
// @Summary Attach taints to a node pool (org scoped)
|
|
// @Tags NodePools
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param X-Org-ID header string false "Organization UUID"
|
|
// @Param id path string true "Node Pool ID (UUID)"
|
|
// @Param body body dto.AttachTaintsRequest true "Taint IDs to attach"
|
|
// @Success 204 {string} string "No Content"
|
|
// @Failure 400 {string} string "invalid id / invalid taint_ids"
|
|
// @Failure 401 {string} string "Unauthorized"
|
|
// @Failure 403 {string} string "organization required"
|
|
// @Failure 404 {string} string "not found"
|
|
// @Failure 500 {string} string "attach failed"
|
|
// @Router /node-pools/{id}/taints [post]
|
|
// @Security BearerAuth
|
|
// @Security OrgKeyAuth
|
|
// @Security OrgSecretAuth
|
|
func AttachNodePoolTaints(db *gorm.DB) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
orgID, ok := httpmiddleware.OrgIDFrom(r.Context())
|
|
if !ok {
|
|
utils.WriteError(w, http.StatusForbidden, "org_required", "specify X-Org-ID")
|
|
return
|
|
}
|
|
|
|
id, err := uuid.Parse(chi.URLParam(r, "id"))
|
|
if err != nil {
|
|
utils.WriteError(w, http.StatusBadRequest, "id_required", "node pool id required")
|
|
return
|
|
}
|
|
|
|
var np models.NodePool
|
|
if err := db.Where("id = ? AND organization_id = ?", id, orgID).First(&np).Error; err != nil {
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
utils.WriteError(w, http.StatusNotFound, "node_pool_not_found", "node pool not found")
|
|
return
|
|
}
|
|
utils.WriteError(w, http.StatusInternalServerError, "db_error", "db error")
|
|
return
|
|
}
|
|
|
|
var req dto.AttachTaintsRequest
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
utils.WriteError(w, http.StatusBadRequest, "bad_request", "bad request")
|
|
return
|
|
}
|
|
|
|
ids, err := parseUUIDs(req.TaintIDs)
|
|
if err != nil {
|
|
utils.WriteError(w, http.StatusBadRequest, "bad_request", "invalid taint_ids")
|
|
return
|
|
}
|
|
|
|
if len(ids) == 0 {
|
|
utils.WriteError(w, http.StatusBadRequest, "bad_request", "nothing to attach")
|
|
return
|
|
}
|
|
|
|
// validate IDs belong to org
|
|
if err := ensureTaintsBelongToOrg(orgID, ids, db); err != nil {
|
|
utils.WriteError(w, http.StatusBadRequest, "bad_request", "invalid taint_ids for this organization")
|
|
return
|
|
}
|
|
|
|
var taints []models.Taint
|
|
if err := db.Where("organization_id = ? AND id IN ?", orgID, ids).Find(&taints).Error; err != nil {
|
|
utils.WriteError(w, http.StatusInternalServerError, "db_error", "attach db error")
|
|
return
|
|
}
|
|
|
|
if len(taints) == 0 {
|
|
w.WriteHeader(http.StatusNoContent)
|
|
return
|
|
}
|
|
|
|
if err := db.Model(&np).Association("Taints").Append(&taints); err != nil {
|
|
utils.WriteError(w, http.StatusInternalServerError, "db_error", "attach db error")
|
|
return
|
|
}
|
|
w.WriteHeader(http.StatusNoContent)
|
|
}
|
|
}
|
|
|
|
// DetachNodePoolTaint godoc
|
|
//
|
|
// @ID DetachNodePoolTaint
|
|
// @Summary Detach one taint from a node pool (org scoped)
|
|
// @Tags NodePools
|
|
// @Produce json
|
|
// @Param X-Org-ID header string false "Organization UUID"
|
|
// @Param id path string true "Node Pool ID (UUID)"
|
|
// @Param taintId path string true "Taint ID (UUID)"
|
|
// @Success 204 {string} string "No Content"
|
|
// @Failure 400 {string} string "invalid id"
|
|
// @Failure 401 {string} string "Unauthorized"
|
|
// @Failure 403 {string} string "organization required"
|
|
// @Failure 404 {string} string "not found"
|
|
// @Failure 500 {string} string "detach failed"
|
|
// @Router /node-pools/{id}/taints/{taintId} [delete]
|
|
// @Security BearerAuth
|
|
// @Security OrgKeyAuth
|
|
// @Security OrgSecretAuth
|
|
func DetachNodePoolTaint(db *gorm.DB) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
orgID, ok := httpmiddleware.OrgIDFrom(r.Context())
|
|
if !ok {
|
|
utils.WriteError(w, http.StatusForbidden, "org_required", "specify X-Org-ID")
|
|
return
|
|
}
|
|
id, err := uuid.Parse(chi.URLParam(r, "id"))
|
|
if err != nil {
|
|
utils.WriteError(w, http.StatusBadRequest, "id_required", "node pool id required")
|
|
return
|
|
}
|
|
taintId, err := uuid.Parse(chi.URLParam(r, "taintId"))
|
|
if err != nil {
|
|
utils.WriteError(w, http.StatusBadRequest, "taintId_required", "taint id required")
|
|
return
|
|
}
|
|
|
|
var np models.NodePool
|
|
if err := db.Where("id = ? AND organization_id = ?", id, orgID).First(&np).Error; err != nil {
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
utils.WriteError(w, http.StatusNotFound, "node_pool_not_found", "node pool not found")
|
|
return
|
|
}
|
|
utils.WriteError(w, http.StatusInternalServerError, "db_error", "db error")
|
|
return
|
|
}
|
|
|
|
var t models.Taint
|
|
if err := db.Where("id = ? AND organization_id = ?", taintId, orgID).First(&t).Error; err != nil {
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
utils.WriteError(w, http.StatusNotFound, "taint_not_found", "taint not found")
|
|
return
|
|
}
|
|
utils.WriteError(w, http.StatusInternalServerError, "db_error", "db error")
|
|
return
|
|
}
|
|
|
|
if err := db.Model(&np).Association("Taints").Delete(&t); err != nil {
|
|
utils.WriteError(w, http.StatusInternalServerError, "db_error", "db error")
|
|
return
|
|
}
|
|
w.WriteHeader(http.StatusNoContent)
|
|
}
|
|
}
|
|
|
|
// -- Node Pools Labels
|
|
|
|
// ListNodePoolLabels godoc
|
|
//
|
|
// @ID ListNodePoolLabels
|
|
// @Summary List labels attached to a node pool (org scoped)
|
|
// @Tags NodePools
|
|
// @Produce json
|
|
// @Param X-Org-ID header string false "Organization UUID"
|
|
// @Param id path string true "Label Pool ID (UUID)"
|
|
// @Success 200 {array} dto.LabelResponse
|
|
// @Failure 400 {string} string "invalid id"
|
|
// @Failure 401 {string} string "Unauthorized"
|
|
// @Failure 403 {string} string "organization required"
|
|
// @Failure 404 {string} string "not found"
|
|
// @Failure 500 {string} string "fetch failed"
|
|
// @Router /node-pools/{id}/labels [get]
|
|
// @Security BearerAuth
|
|
// @Security OrgKeyAuth
|
|
// @Security OrgSecretAuth
|
|
func ListNodePoolLabels(db *gorm.DB) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
orgID, ok := httpmiddleware.OrgIDFrom(r.Context())
|
|
if !ok {
|
|
utils.WriteError(w, http.StatusForbidden, "org_required", "specify X-Org-ID")
|
|
return
|
|
}
|
|
id, err := uuid.Parse(chi.URLParam(r, "id"))
|
|
if err != nil {
|
|
utils.WriteError(w, http.StatusBadRequest, "id_required", "node pool id required")
|
|
return
|
|
}
|
|
|
|
var np models.NodePool
|
|
if err := db.Where("id = ? AND organization_id = ?", id, orgID).Preload("Labels").First(&np).Error; err != nil {
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
utils.WriteError(w, http.StatusNotFound, "node_pool_not_found", "node pool not found")
|
|
return
|
|
}
|
|
utils.WriteError(w, http.StatusInternalServerError, "db_error", "db error")
|
|
return
|
|
}
|
|
|
|
out := make([]dto.LabelResponse, 0, len(np.Taints))
|
|
for _, taint := range np.Taints {
|
|
out = append(out, dto.LabelResponse{
|
|
AuditFields: common.AuditFields{
|
|
ID: taint.ID,
|
|
OrganizationID: taint.OrganizationID,
|
|
CreatedAt: taint.CreatedAt,
|
|
UpdatedAt: taint.UpdatedAt,
|
|
},
|
|
Key: taint.Key,
|
|
Value: taint.Value,
|
|
})
|
|
}
|
|
utils.WriteJSON(w, http.StatusOK, out)
|
|
}
|
|
}
|
|
|
|
// AttachNodePoolLabels godoc
|
|
//
|
|
// @ID AttachNodePoolLabels
|
|
// @Summary Attach labels to a node pool (org scoped)
|
|
// @Tags NodePools
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param X-Org-ID header string false "Organization UUID"
|
|
// @Param id path string true "Node Pool ID (UUID)"
|
|
// @Param body body dto.AttachLabelsRequest true "Label IDs to attach"
|
|
// @Success 204 {string} string "No Content"
|
|
// @Failure 400 {string} string "invalid id / invalid server_ids"
|
|
// @Failure 401 {string} string "Unauthorized"
|
|
// @Failure 403 {string} string "organization required"
|
|
// @Failure 404 {string} string "not found"
|
|
// @Failure 500 {string} string "attach failed"
|
|
// @Router /node-pools/{id}/labels [post]
|
|
// @Security BearerAuth
|
|
// @Security OrgKeyAuth
|
|
// @Security OrgSecretAuth
|
|
func AttachNodePoolLabels(db *gorm.DB) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
orgID, ok := httpmiddleware.OrgIDFrom(r.Context())
|
|
if !ok {
|
|
utils.WriteError(w, http.StatusForbidden, "org_required", "specify X-Org-ID")
|
|
return
|
|
}
|
|
id, err := uuid.Parse(chi.URLParam(r, "id"))
|
|
if err != nil {
|
|
utils.WriteError(w, http.StatusBadRequest, "id_required", "node pool id required")
|
|
return
|
|
}
|
|
|
|
var np models.NodePool
|
|
if err := db.Where("id = ? AND organization_id = ?", id, orgID).First(&np).Error; err != nil {
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
utils.WriteError(w, http.StatusNotFound, "node_pool_not_found", "node pool not found")
|
|
return
|
|
}
|
|
utils.WriteError(w, http.StatusInternalServerError, "db_error", "db error")
|
|
return
|
|
}
|
|
|
|
var req dto.AttachLabelsRequest
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
utils.WriteError(w, http.StatusBadRequest, "bad_request", "bad request")
|
|
return
|
|
}
|
|
|
|
ids, err := parseUUIDs(req.LabelIDs)
|
|
if err != nil {
|
|
utils.WriteError(w, http.StatusBadRequest, "bad_request", "invalid label_ids")
|
|
return
|
|
}
|
|
|
|
if len(ids) == 0 {
|
|
utils.WriteError(w, http.StatusBadRequest, "bad_request", "nothing to attach")
|
|
return
|
|
}
|
|
|
|
if err := ensureLabelsBelongToOrg(orgID, ids, db); err != nil {
|
|
utils.WriteError(w, http.StatusBadRequest, "bad_request", "invalid label_ids for this organization")
|
|
}
|
|
|
|
var labels []models.Label
|
|
if err := db.Where("organization_id = ? AND id IN ?", orgID, ids).Find(&labels).Error; err != nil {
|
|
utils.WriteError(w, http.StatusInternalServerError, "db_error", "attach db error")
|
|
return
|
|
}
|
|
|
|
if len(labels) == 0 {
|
|
w.WriteHeader(http.StatusNoContent)
|
|
return
|
|
}
|
|
|
|
if err := db.Model(&np).Association("Labels").Append(&labels); err != nil {
|
|
utils.WriteError(w, http.StatusInternalServerError, "db_error", "attach failed")
|
|
return
|
|
}
|
|
w.WriteHeader(http.StatusNoContent)
|
|
}
|
|
}
|
|
|
|
// DetachNodePoolLabel godoc
|
|
//
|
|
// @ID DetachNodePoolLabel
|
|
// @Summary Detach one label from a node pool (org scoped)
|
|
// @Tags NodePools
|
|
// @Produce json
|
|
// @Param X-Org-ID header string false "Organization UUID"
|
|
// @Param id path string true "Node Pool ID (UUID)"
|
|
// @Param labelId path string true "Label ID (UUID)"
|
|
// @Success 204 {string} string "No Content"
|
|
// @Failure 400 {string} string "invalid id"
|
|
// @Failure 401 {string} string "Unauthorized"
|
|
// @Failure 403 {string} string "organization required"
|
|
// @Failure 404 {string} string "not found"
|
|
// @Failure 500 {string} string "detach failed"
|
|
// @Router /node-pools/{id}/labels/{labelId} [delete]
|
|
// @Security BearerAuth
|
|
// @Security OrgKeyAuth
|
|
// @Security OrgSecretAuth
|
|
func DetachNodePoolLabel(db *gorm.DB) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
orgID, ok := httpmiddleware.OrgIDFrom(r.Context())
|
|
if !ok {
|
|
utils.WriteError(w, http.StatusForbidden, "org_required", "specify X-Org-ID")
|
|
return
|
|
}
|
|
id, err := uuid.Parse(chi.URLParam(r, "id"))
|
|
if err != nil {
|
|
utils.WriteError(w, http.StatusBadRequest, "id_required", "node pool id required")
|
|
return
|
|
}
|
|
labelId, err := uuid.Parse(chi.URLParam(r, "labelId"))
|
|
if err != nil {
|
|
utils.WriteError(w, http.StatusBadRequest, "id_required", "labelId required")
|
|
return
|
|
}
|
|
|
|
var np models.NodePool
|
|
if err := db.Where("id = ? AND organization_id = ?", id, orgID).First(&np).Error; err != nil {
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
utils.WriteError(w, http.StatusNotFound, "node_pool_not_found", "node pool not found")
|
|
return
|
|
}
|
|
utils.WriteError(w, http.StatusInternalServerError, "db_error", "db error")
|
|
return
|
|
}
|
|
|
|
var l models.Label
|
|
if err := db.Where("id = ? AND organization_id = ?", labelId, orgID).First(&l).Error; err != nil {
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
utils.WriteError(w, http.StatusNotFound, "label_not_found", "label not found")
|
|
return
|
|
}
|
|
utils.WriteError(w, http.StatusInternalServerError, "db_error", "db error")
|
|
return
|
|
}
|
|
|
|
if err := db.Model(&np).Association("Labels").Delete(&l); err != nil {
|
|
utils.WriteError(w, http.StatusInternalServerError, "db_error", "detach error")
|
|
return
|
|
}
|
|
w.WriteHeader(http.StatusNoContent)
|
|
}
|
|
}
|
|
|
|
// -- Node Pools Annotations
|
|
|
|
// ListNodePoolAnnotations godoc
|
|
//
|
|
// @ID ListNodePoolAnnotations
|
|
// @Summary List annotations attached to a node pool (org scoped)
|
|
// @Tags NodePools
|
|
// @Produce json
|
|
// @Param X-Org-ID header string false "Organization UUID"
|
|
// @Param id path string true "Node Pool ID (UUID)"
|
|
// @Success 200 {array} dto.AnnotationResponse
|
|
// @Failure 400 {string} string "invalid id"
|
|
// @Failure 401 {string} string "Unauthorized"
|
|
// @Failure 403 {string} string "organization required"
|
|
// @Failure 404 {string} string "not found"
|
|
// @Failure 500 {string} string "fetch failed"
|
|
// @Router /node-pools/{id}/annotations [get]
|
|
// @Security BearerAuth
|
|
// @Security OrgKeyAuth
|
|
// @Security OrgSecretAuth
|
|
func ListNodePoolAnnotations(db *gorm.DB) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
orgID, ok := httpmiddleware.OrgIDFrom(r.Context())
|
|
if !ok {
|
|
utils.WriteError(w, http.StatusForbidden, "org_required", "specify X-Org-ID")
|
|
return
|
|
}
|
|
id, err := uuid.Parse(chi.URLParam(r, "id"))
|
|
if err != nil {
|
|
utils.WriteError(w, http.StatusBadRequest, "id_required", "node pool id required")
|
|
return
|
|
}
|
|
|
|
var np models.NodePool
|
|
if err := db.Where("id = ? AND organization_id = ?", id, orgID).Preload("Labels").First(&np).Error; err != nil {
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
utils.WriteError(w, http.StatusNotFound, "node_pool_not_found", "node pool not found")
|
|
return
|
|
}
|
|
utils.WriteError(w, http.StatusInternalServerError, "db_error", "db error")
|
|
return
|
|
}
|
|
|
|
out := make([]dto.AnnotationResponse, 0, len(np.Annotations))
|
|
for _, ann := range np.Annotations {
|
|
out = append(out, dto.AnnotationResponse{
|
|
AuditFields: common.AuditFields{
|
|
ID: ann.ID,
|
|
OrganizationID: ann.OrganizationID,
|
|
CreatedAt: ann.CreatedAt,
|
|
UpdatedAt: ann.UpdatedAt,
|
|
},
|
|
Key: ann.Key,
|
|
Value: ann.Value,
|
|
})
|
|
}
|
|
utils.WriteJSON(w, http.StatusOK, out)
|
|
}
|
|
}
|
|
|
|
// AttachNodePoolAnnotations godoc
|
|
//
|
|
// @ID AttachNodePoolAnnotations
|
|
// @Summary Attach annotation to a node pool (org scoped)
|
|
// @Tags NodePools
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param X-Org-ID header string false "Organization UUID"
|
|
// @Param id path string true "Node Group ID (UUID)"
|
|
// @Param body body dto.AttachAnnotationsRequest true "Annotation IDs to attach"
|
|
// @Success 204 {string} string "No Content"
|
|
// @Failure 400 {string} string "invalid id / invalid server_ids"
|
|
// @Failure 401 {string} string "Unauthorized"
|
|
// @Failure 403 {string} string "organization required"
|
|
// @Failure 404 {string} string "not found"
|
|
// @Failure 500 {string} string "attach failed"
|
|
// @Router /node-pools/{id}/annotations [post]
|
|
// @Security BearerAuth
|
|
// @Security OrgKeyAuth
|
|
// @Security OrgSecretAuth
|
|
func AttachNodePoolAnnotations(db *gorm.DB) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
orgID, ok := httpmiddleware.OrgIDFrom(r.Context())
|
|
if !ok {
|
|
utils.WriteError(w, http.StatusForbidden, "org_required", "specify X-Org-ID")
|
|
return
|
|
}
|
|
id, err := uuid.Parse(chi.URLParam(r, "id"))
|
|
if err != nil {
|
|
utils.WriteError(w, http.StatusBadRequest, "id_required", "node pool id required")
|
|
return
|
|
}
|
|
|
|
var np models.NodePool
|
|
if err := db.Where("id = ? AND organization_id = ?", id, orgID).First(&np).Error; err != nil {
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
utils.WriteError(w, http.StatusNotFound, "node_pool_not_found", "node pool not found")
|
|
return
|
|
}
|
|
utils.WriteError(w, http.StatusInternalServerError, "db_error", "db error")
|
|
return
|
|
}
|
|
|
|
var req dto.AttachAnnotationsRequest
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
utils.WriteError(w, http.StatusBadRequest, "bad_request", "bad request")
|
|
return
|
|
}
|
|
|
|
ids, err := parseUUIDs(req.AnnotationIDs)
|
|
if err != nil {
|
|
utils.WriteError(w, http.StatusBadRequest, "bad_request", "invalid annotation ids")
|
|
return
|
|
}
|
|
|
|
if len(ids) == 0 {
|
|
utils.WriteError(w, http.StatusBadRequest, "bad_request", "nothing to attach")
|
|
return
|
|
}
|
|
|
|
if err := ensureAnnotaionsBelongToOrg(orgID, ids, db); err != nil {
|
|
utils.WriteError(w, http.StatusBadRequest, "bad_request", "invalid annotation ids for this organization")
|
|
return
|
|
}
|
|
|
|
var ann []models.Annotation
|
|
if err := db.Where("organization_id = ? AND id IN ?", orgID, ids).Find(&ann).Error; err != nil {
|
|
utils.WriteError(w, http.StatusInternalServerError, "db_error", "db error")
|
|
return
|
|
}
|
|
|
|
if len(ann) == 0 {
|
|
w.WriteHeader(http.StatusNoContent)
|
|
return
|
|
}
|
|
|
|
if err := db.Model(&np).Association("Annotations").Append(&ann); err != nil {
|
|
utils.WriteError(w, http.StatusInternalServerError, "db_error", "attach failed")
|
|
return
|
|
}
|
|
w.WriteHeader(http.StatusNoContent)
|
|
}
|
|
}
|
|
|
|
// DetachNodePoolAnnotation godoc
|
|
//
|
|
// @ID DetachNodePoolAnnotation
|
|
// @Summary Detach one annotation from a node pool (org scoped)
|
|
// @Tags NodePools
|
|
// @Produce json
|
|
// @Param X-Org-ID header string false "Organization UUID"
|
|
// @Param id path string true "Node Pool ID (UUID)"
|
|
// @Param annotationId path string true "Annotation ID (UUID)"
|
|
// @Success 204 {string} string "No Content"
|
|
// @Failure 400 {string} string "invalid id"
|
|
// @Failure 401 {string} string "Unauthorized"
|
|
// @Failure 403 {string} string "organization required"
|
|
// @Failure 404 {string} string "not found"
|
|
// @Failure 500 {string} string "detach failed"
|
|
// @Router /node-pools/{id}/annotations/{annotationId} [delete]
|
|
// @Security BearerAuth
|
|
// @Security OrgKeyAuth
|
|
// @Security OrgSecretAuth
|
|
func DetachNodePoolAnnotation(db *gorm.DB) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
orgID, ok := httpmiddleware.OrgIDFrom(r.Context())
|
|
if !ok {
|
|
utils.WriteError(w, http.StatusForbidden, "org_required", "specify X-Org-ID")
|
|
return
|
|
}
|
|
id, err := uuid.Parse(chi.URLParam(r, "id"))
|
|
if err != nil {
|
|
utils.WriteError(w, http.StatusBadRequest, "id_required", "node pool id required")
|
|
return
|
|
}
|
|
annotationId, err := uuid.Parse(chi.URLParam(r, "annotationId"))
|
|
if err != nil {
|
|
utils.WriteError(w, http.StatusBadRequest, "id_required", "node pool annotation id required")
|
|
return
|
|
}
|
|
|
|
var np models.NodePool
|
|
if err := db.Where("id = ? AND organization_id = ?", id, orgID).First(&np).Error; err != nil {
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
utils.WriteError(w, http.StatusNotFound, "node_pool_not_found", "node pool not found")
|
|
return
|
|
}
|
|
utils.WriteError(w, http.StatusInternalServerError, "db_error", "db error")
|
|
return
|
|
}
|
|
|
|
var ann []models.Annotation
|
|
if err := db.Where("id = ? AND organization_id = ?", annotationId, orgID).First(&ann).Error; err != nil {
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
utils.WriteError(w, http.StatusNotFound, "annotation_not_found", "annotation not found")
|
|
return
|
|
}
|
|
utils.WriteError(w, http.StatusInternalServerError, "db_error", "db error")
|
|
return
|
|
}
|
|
|
|
if err := db.Model(&np).Association("Annotations").Delete(&ann); err != nil {
|
|
utils.WriteError(w, http.StatusInternalServerError, "db_error", "db error")
|
|
return
|
|
}
|
|
w.WriteHeader(http.StatusNoContent)
|
|
}
|
|
}
|
|
|
|
// -- Helpers
|
|
func parseUUIDs(ids []string) ([]uuid.UUID, error) {
|
|
out := make([]uuid.UUID, 0, len(ids))
|
|
for _, id := range ids {
|
|
u, err := uuid.Parse(id)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
out = append(out, u)
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
func ensureServersBelongToOrg(orgID uuid.UUID, ids []uuid.UUID, db *gorm.DB) error {
|
|
var count int64
|
|
if err := db.Model(&models.Server{}).Where("organization_id = ? AND id IN ?", orgID, ids).Count(&count).Error; err != nil {
|
|
return err
|
|
}
|
|
if count != int64(len(ids)) {
|
|
return errors.New("some servers do not belong to this org")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func ensureTaintsBelongToOrg(orgID uuid.UUID, ids []uuid.UUID, db *gorm.DB) error {
|
|
var count int64
|
|
if err := db.Model(&models.Taint{}).Where("organization_id = ? AND id IN ?", orgID, ids).Count(&count).Error; err != nil {
|
|
return err
|
|
}
|
|
if count != int64(len(ids)) {
|
|
return errors.New("some taints do not belong to this org")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func ensureLabelsBelongToOrg(orgID uuid.UUID, ids []uuid.UUID, db *gorm.DB) error {
|
|
var count int64
|
|
if err := db.Model(&models.Label{}).Where("organization_id = ? AND id IN ?", orgID, ids).Count(&count).Error; err != nil {
|
|
return err
|
|
}
|
|
if count != int64(len(ids)) {
|
|
return errors.New("some labels do not belong to this org")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func ensureAnnotaionsBelongToOrg(orgID uuid.UUID, ids []uuid.UUID, db *gorm.DB) error {
|
|
var count int64
|
|
if err := db.Model(&models.Annotation{}).Where("organization_id = ? AND id IN ?", orgID, ids).Count(&count).Error; err != nil {
|
|
return err
|
|
}
|
|
if count != int64(len(ids)) {
|
|
return errors.New("some annotations do not belong to this org")
|
|
}
|
|
return nil
|
|
}
|