304 lines
9.1 KiB
Go
304 lines
9.1 KiB
Go
package httpapp
|
|
|
|
import (
|
|
"net/http"
|
|
"net/url"
|
|
"strings"
|
|
|
|
"gitea.avt.data-center.id/othman.suseno/atlas/internal/errors"
|
|
)
|
|
|
|
// methodHandler routes requests based on HTTP method
|
|
func methodHandler(get, post, put, delete, patch http.HandlerFunc) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
switch r.Method {
|
|
case http.MethodGet:
|
|
if get != nil {
|
|
get(w, r)
|
|
} else {
|
|
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
|
|
}
|
|
case http.MethodPost:
|
|
if post != nil {
|
|
post(w, r)
|
|
} else {
|
|
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
|
|
}
|
|
case http.MethodPut:
|
|
if put != nil {
|
|
put(w, r)
|
|
} else {
|
|
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
|
|
}
|
|
case http.MethodDelete:
|
|
if delete != nil {
|
|
delete(w, r)
|
|
} else {
|
|
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
|
|
}
|
|
case http.MethodPatch:
|
|
if patch != nil {
|
|
patch(w, r)
|
|
} else {
|
|
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
|
|
}
|
|
default:
|
|
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
|
|
}
|
|
}
|
|
}
|
|
|
|
// pathParam extracts the last segment from a path
|
|
func pathParam(r *http.Request, prefix string) string {
|
|
path := strings.TrimPrefix(r.URL.Path, prefix)
|
|
path = strings.Trim(path, "/")
|
|
parts := strings.Split(path, "/")
|
|
if len(parts) > 0 {
|
|
return parts[len(parts)-1]
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// pathParamFull extracts the full path after prefix (for dataset/zvol names that may contain slashes)
|
|
func pathParamFull(r *http.Request, prefix string) string {
|
|
path := strings.TrimPrefix(r.URL.Path, prefix)
|
|
path = strings.Trim(path, "/")
|
|
if path == "" {
|
|
return ""
|
|
}
|
|
// URL decode the path
|
|
decoded, err := url.PathUnescape(path)
|
|
if err != nil {
|
|
// If decoding fails, return original path
|
|
return path
|
|
}
|
|
return decoded
|
|
}
|
|
|
|
// handlePoolOps routes pool operations by method
|
|
func (a *App) handlePoolOps(w http.ResponseWriter, r *http.Request) {
|
|
// Extract pool name from path like /api/v1/pools/tank
|
|
name := pathParam(r, "/api/v1/pools/")
|
|
if name == "" {
|
|
writeError(w, errors.ErrBadRequest("pool name required"))
|
|
return
|
|
}
|
|
|
|
if strings.HasSuffix(r.URL.Path, "/scrub") {
|
|
if r.Method == http.MethodPost {
|
|
a.handleScrubPool(w, r)
|
|
} else if r.Method == http.MethodGet {
|
|
a.handleGetScrubStatus(w, r)
|
|
} else {
|
|
writeError(w, errors.NewAPIError(errors.ErrCodeBadRequest, "method not allowed", http.StatusMethodNotAllowed))
|
|
}
|
|
return
|
|
}
|
|
|
|
if strings.HasSuffix(r.URL.Path, "/export") {
|
|
if r.Method == http.MethodPost {
|
|
a.handleExportPool(w, r)
|
|
} else {
|
|
writeError(w, errors.NewAPIError(errors.ErrCodeBadRequest, "method not allowed", http.StatusMethodNotAllowed))
|
|
}
|
|
return
|
|
}
|
|
|
|
if strings.HasSuffix(r.URL.Path, "/spare") {
|
|
if r.Method == http.MethodPost {
|
|
a.handleAddSpareDisk(w, r)
|
|
} else {
|
|
writeError(w, errors.NewAPIError(errors.ErrCodeBadRequest, "method not allowed", http.StatusMethodNotAllowed))
|
|
}
|
|
return
|
|
}
|
|
|
|
methodHandler(
|
|
func(w http.ResponseWriter, r *http.Request) { a.handleGetPool(w, r) },
|
|
nil,
|
|
nil,
|
|
func(w http.ResponseWriter, r *http.Request) { a.handleDeletePool(w, r) },
|
|
nil,
|
|
)(w, r)
|
|
}
|
|
|
|
// handleDatasetOps routes dataset operations by method
|
|
func (a *App) handleDatasetOps(w http.ResponseWriter, r *http.Request) {
|
|
methodHandler(
|
|
func(w http.ResponseWriter, r *http.Request) { a.handleGetDataset(w, r) },
|
|
func(w http.ResponseWriter, r *http.Request) { a.handleCreateDataset(w, r) },
|
|
func(w http.ResponseWriter, r *http.Request) { a.handleUpdateDataset(w, r) },
|
|
func(w http.ResponseWriter, r *http.Request) { a.handleDeleteDataset(w, r) },
|
|
nil,
|
|
)(w, r)
|
|
}
|
|
|
|
// handleZVOLOps routes ZVOL operations by method
|
|
func (a *App) handleZVOLOps(w http.ResponseWriter, r *http.Request) {
|
|
methodHandler(
|
|
func(w http.ResponseWriter, r *http.Request) { a.handleGetZVOL(w, r) },
|
|
func(w http.ResponseWriter, r *http.Request) { a.handleCreateZVOL(w, r) },
|
|
nil,
|
|
func(w http.ResponseWriter, r *http.Request) { a.handleDeleteZVOL(w, r) },
|
|
nil,
|
|
)(w, r)
|
|
}
|
|
|
|
// handleSnapshotOps routes snapshot operations by method
|
|
func (a *App) handleSnapshotOps(w http.ResponseWriter, r *http.Request) {
|
|
// Check if it's a restore operation
|
|
if strings.HasSuffix(r.URL.Path, "/restore") {
|
|
if r.Method == http.MethodPost {
|
|
a.handleRestoreSnapshot(w, r)
|
|
} else {
|
|
writeError(w, errors.ErrBadRequest("method not allowed"))
|
|
}
|
|
return
|
|
}
|
|
|
|
methodHandler(
|
|
func(w http.ResponseWriter, r *http.Request) { a.handleGetSnapshot(w, r) },
|
|
func(w http.ResponseWriter, r *http.Request) { a.handleCreateSnapshot(w, r) },
|
|
nil,
|
|
func(w http.ResponseWriter, r *http.Request) { a.handleDeleteSnapshot(w, r) },
|
|
nil,
|
|
)(w, r)
|
|
}
|
|
|
|
// handleSnapshotPolicyOps routes snapshot policy operations by method
|
|
func (a *App) handleSnapshotPolicyOps(w http.ResponseWriter, r *http.Request) {
|
|
methodHandler(
|
|
func(w http.ResponseWriter, r *http.Request) { a.handleGetSnapshotPolicy(w, r) },
|
|
func(w http.ResponseWriter, r *http.Request) { a.handleCreateSnapshotPolicy(w, r) },
|
|
func(w http.ResponseWriter, r *http.Request) { a.handleUpdateSnapshotPolicy(w, r) },
|
|
func(w http.ResponseWriter, r *http.Request) { a.handleDeleteSnapshotPolicy(w, r) },
|
|
nil,
|
|
)(w, r)
|
|
}
|
|
|
|
// handleSMBShareOps routes SMB share operations by method
|
|
func (a *App) handleSMBShareOps(w http.ResponseWriter, r *http.Request) {
|
|
methodHandler(
|
|
func(w http.ResponseWriter, r *http.Request) { a.handleGetSMBShare(w, r) },
|
|
func(w http.ResponseWriter, r *http.Request) { a.handleCreateSMBShare(w, r) },
|
|
func(w http.ResponseWriter, r *http.Request) { a.handleUpdateSMBShare(w, r) },
|
|
func(w http.ResponseWriter, r *http.Request) { a.handleDeleteSMBShare(w, r) },
|
|
nil,
|
|
)(w, r)
|
|
}
|
|
|
|
// handleNFSExportOps routes NFS export operations by method
|
|
func (a *App) handleNFSExportOps(w http.ResponseWriter, r *http.Request) {
|
|
methodHandler(
|
|
func(w http.ResponseWriter, r *http.Request) { a.handleGetNFSExport(w, r) },
|
|
func(w http.ResponseWriter, r *http.Request) { a.handleCreateNFSExport(w, r) },
|
|
func(w http.ResponseWriter, r *http.Request) { a.handleUpdateNFSExport(w, r) },
|
|
func(w http.ResponseWriter, r *http.Request) { a.handleDeleteNFSExport(w, r) },
|
|
nil,
|
|
)(w, r)
|
|
}
|
|
|
|
// handleISCSITargetOps routes iSCSI target operations by method
|
|
func (a *App) handleBackupOps(w http.ResponseWriter, r *http.Request) {
|
|
backupID := pathParam(r, "/api/v1/backups/")
|
|
if backupID == "" {
|
|
writeError(w, errors.ErrBadRequest("backup id required"))
|
|
return
|
|
}
|
|
|
|
switch r.Method {
|
|
case http.MethodGet:
|
|
// Check if it's a verify request
|
|
if r.URL.Query().Get("verify") == "true" {
|
|
a.handleVerifyBackup(w, r)
|
|
} else {
|
|
a.handleGetBackup(w, r)
|
|
}
|
|
case http.MethodPost:
|
|
// Restore backup (POST /api/v1/backups/{id}/restore)
|
|
if strings.HasSuffix(r.URL.Path, "/restore") {
|
|
a.handleRestoreBackup(w, r)
|
|
} else {
|
|
writeError(w, errors.ErrBadRequest("invalid backup operation"))
|
|
}
|
|
case http.MethodDelete:
|
|
a.handleDeleteBackup(w, r)
|
|
default:
|
|
writeError(w, errors.ErrBadRequest("method not allowed"))
|
|
}
|
|
}
|
|
|
|
func (a *App) handleISCSITargetOps(w http.ResponseWriter, r *http.Request) {
|
|
id := pathParam(r, "/api/v1/iscsi/targets/")
|
|
if id == "" {
|
|
writeError(w, errors.ErrBadRequest("target id required"))
|
|
return
|
|
}
|
|
|
|
if strings.HasSuffix(r.URL.Path, "/connection") {
|
|
if r.Method == http.MethodGet {
|
|
a.handleGetISCSIConnectionInstructions(w, r)
|
|
} else {
|
|
writeError(w, errors.NewAPIError(errors.ErrCodeBadRequest, "method not allowed", http.StatusMethodNotAllowed))
|
|
}
|
|
return
|
|
}
|
|
|
|
if strings.HasSuffix(r.URL.Path, "/luns") {
|
|
if r.Method == http.MethodPost {
|
|
a.handleAddLUN(w, r)
|
|
return
|
|
}
|
|
writeError(w, errors.NewAPIError(errors.ErrCodeBadRequest, "method not allowed", http.StatusMethodNotAllowed))
|
|
return
|
|
}
|
|
|
|
if strings.HasSuffix(r.URL.Path, "/luns/remove") {
|
|
if r.Method == http.MethodPost {
|
|
a.handleRemoveLUN(w, r)
|
|
return
|
|
}
|
|
writeError(w, errors.NewAPIError(errors.ErrCodeBadRequest, "method not allowed", http.StatusMethodNotAllowed))
|
|
return
|
|
}
|
|
|
|
methodHandler(
|
|
func(w http.ResponseWriter, r *http.Request) { a.handleGetISCSITarget(w, r) },
|
|
nil,
|
|
func(w http.ResponseWriter, r *http.Request) { a.handleUpdateISCSITarget(w, r) },
|
|
func(w http.ResponseWriter, r *http.Request) { a.handleDeleteISCSITarget(w, r) },
|
|
nil,
|
|
)(w, r)
|
|
}
|
|
|
|
// handleJobOps routes job operations by method
|
|
func (a *App) handleJobOps(w http.ResponseWriter, r *http.Request) {
|
|
if strings.HasSuffix(r.URL.Path, "/cancel") {
|
|
if r.Method == http.MethodPost {
|
|
a.handleCancelJob(w, r)
|
|
return
|
|
}
|
|
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
|
|
return
|
|
}
|
|
|
|
methodHandler(
|
|
func(w http.ResponseWriter, r *http.Request) { a.handleGetJob(w, r) },
|
|
nil,
|
|
nil,
|
|
nil,
|
|
nil,
|
|
)(w, r)
|
|
}
|
|
|
|
// handleUserOps routes user operations by method
|
|
func (a *App) handleUserOps(w http.ResponseWriter, r *http.Request) {
|
|
methodHandler(
|
|
func(w http.ResponseWriter, r *http.Request) { a.handleGetUser(w, r) },
|
|
func(w http.ResponseWriter, r *http.Request) { a.handleCreateUser(w, r) },
|
|
func(w http.ResponseWriter, r *http.Request) { a.handleUpdateUser(w, r) },
|
|
func(w http.ResponseWriter, r *http.Request) { a.handleDeleteUser(w, r) },
|
|
nil,
|
|
)(w, r)
|
|
}
|