diff --git a/internal/server/backup_handlers.go b/internal/server/backup_handlers.go new file mode 100644 index 00000000..6eb8217c --- /dev/null +++ b/internal/server/backup_handlers.go @@ -0,0 +1,65 @@ +package server + +import ( + "encoding/json" + "fmt" + "net/http" + + "log/slog" +) + +// @Summary Get available full backups. +// @Tags Backup +// @Produce plain +// @Param name query string true "Backup policy name" +// @Router /backup/full/list [get] +// @Success 200 {array} model.BackupDetails "Full backups" +// @Failure 404 {string} string "" +func (ws *HTTPServer) getAvailableFullBackups(w http.ResponseWriter, r *http.Request) { + policyName := r.URL.Query().Get("name") + if policyName == "" { + http.Error(w, "Invalid/undefined policy name", http.StatusBadRequest) + } else { + list, err := ws.backupBackends[policyName].FullBackupList() + if err != nil { + slog.Error("Get full backup list", "err", err) + http.Error(w, "", http.StatusNotFound) + } else { + response, err := json.Marshal(list) + if err != nil { + slog.Error("Failed to parse full backup list", "err", err) + http.Error(w, "", http.StatusInternalServerError) + } else { + fmt.Fprint(w, string(response)) + } + } + } +} + +// @Summary Get available incremental backups. +// @Tags Backup +// @Produce plain +// @Param name query string true "Backup policy name" +// @Router /backup/incremental/list [get] +// @Success 200 {array} model.BackupDetails "Incremental backups" +// @Failure 404 {string} string "" +func (ws *HTTPServer) getAvailableIncrBackups(w http.ResponseWriter, r *http.Request) { + policyName := r.URL.Query().Get("name") + if policyName == "" { + http.Error(w, "Invalid/undefined policy name", http.StatusBadRequest) + } else { + list, err := ws.backupBackends[policyName].IncrementalBackupList() + if err != nil { + slog.Error("Get incremental backup list", "err", err) + http.Error(w, "", http.StatusNotFound) + } else { + response, err := json.Marshal(list) + if err != nil { + slog.Error("Failed to parse incremental backup list", "err", err) + http.Error(w, "", http.StatusInternalServerError) + } else { + fmt.Fprint(w, string(response)) + } + } + } +} diff --git a/internal/server/config_handlers.go b/internal/server/config_handlers.go index 79f101f2..2d8620bf 100644 --- a/internal/server/config_handlers.go +++ b/internal/server/config_handlers.go @@ -11,10 +11,11 @@ import ( var ConfigurationManager service.ConfigurationManager // readConfig -// @Summary Returns the configuration for the service. -// @Router /config [get] -// @Produce json -// @Success 200 {array} model.Config +// @Summary Returns the configuration for the service. +// @Tags Configuration +// @Router /config [get] +// @Produce json +// @Success 200 {array} model.Config func (ws *HTTPServer) readConfig(w http.ResponseWriter) { configuration, err := json.MarshalIndent(ws.config, "", " ") // pretty print if err != nil { @@ -27,11 +28,12 @@ func (ws *HTTPServer) readConfig(w http.ResponseWriter) { } // updateConfig -// @Summary Updates the configuration for the service. -// @Router /config [post] -// @Accept json -// @Param storage body model.Config true "config" -// @Success 200 "" +// @Summary Updates the configuration for the service. +// @Tags Configuration +// @Router /config [post] +// @Accept json +// @Param storage body model.Config true "config" +// @Success 200 "" func (ws *HTTPServer) updateConfig(w http.ResponseWriter, r *http.Request) { var newConfig model.Config @@ -49,11 +51,12 @@ func (ws *HTTPServer) updateConfig(w http.ResponseWriter, r *http.Request) { } // addAerospikeCluster -// @Summary adds an Aerospike cluster to the config. -// @Router /config/cluster [post] -// @Accept json -// @Param cluster body model.AerospikeCluster true "cluster info" -// @Success 200 "" +// @Summary Adds an Aerospike cluster to the config. +// @Tags Configuration +// @Router /config/cluster [post] +// @Accept json +// @Param cluster body model.AerospikeCluster true "cluster info" +// @Success 200 "" func (ws *HTTPServer) addAerospikeCluster(w http.ResponseWriter, r *http.Request) { var newCluster model.AerospikeCluster err := json.NewDecoder(r.Body).Decode(&newCluster) @@ -73,10 +76,11 @@ func (ws *HTTPServer) addAerospikeCluster(w http.ResponseWriter, r *http.Request } // readAerospikeClusters reads all Aerospike clusters from the configuration. -// @Summary Reads all Aerospike clusters from the configuration. -// @Router /config/cluster [get] -// @Produce json -// @Success 200 {array} model.AerospikeCluster +// @Summary Reads all Aerospike clusters from the configuration. +// @Tags Configuration +// @Router /config/cluster [get] +// @Produce json +// @Success 200 {array} model.AerospikeCluster func (ws *HTTPServer) readAerospikeClusters(w http.ResponseWriter) { clusters := ws.config.AerospikeClusters jsonResponse, err := json.Marshal(clusters) @@ -89,12 +93,13 @@ func (ws *HTTPServer) readAerospikeClusters(w http.ResponseWriter) { } // updateAerospikeCluster updates an existing Aerospike cluster in the configuration. -// @Summary Updates an existing Aerospike cluster in the configuration. -// @Router /config/cluster [put] -// @Accept json -// @Param storage body model.AerospikeCluster true "aerospike cluster" -// @Success 200 {string} string "OK" -// @Failure 400 {string} string "Bad Request" +// @Summary Updates an existing Aerospike cluster in the configuration. +// @Tags Configuration +// @Router /config/cluster [put] +// @Accept json +// @Param storage body model.AerospikeCluster true "aerospike cluster" +// @Success 200 {string} string "OK" +// @Failure 400 {string} string "Bad Request" func (ws *HTTPServer) updateAerospikeCluster(w http.ResponseWriter, r *http.Request) { var updatedCluster model.AerospikeCluster err := json.NewDecoder(r.Body).Decode(&updatedCluster) @@ -114,11 +119,12 @@ func (ws *HTTPServer) updateAerospikeCluster(w http.ResponseWriter, r *http.Requ } // deleteAerospikeCluster -// @Summary Deletes a cluster from the configuration by name. -// @Router /config/cluster [delete] -// @Param name query string true "Cluster Name" -// @Success 200 {string} string "OK" -// @Failure 400 {string} string "Bad Request" +// @Summary Deletes a cluster from the configuration by name. +// @Tags Configuration +// @Router /config/cluster [delete] +// @Param name query string true "Cluster Name" +// @Success 200 {string} string "OK" +// @Failure 400 {string} string "Bad Request" func (ws *HTTPServer) deleteAerospikeCluster(w http.ResponseWriter, r *http.Request) { clusterName := r.URL.Query().Get("name") if clusterName == "" { @@ -139,11 +145,12 @@ func (ws *HTTPServer) deleteAerospikeCluster(w http.ResponseWriter, r *http.Requ } // addStorage -// @Summary adds a storage cluster to the config. -// @Router /config/storage [post] -// @Accept json -// @Param storage body model.BackupStorage true "backup storage" -// @Success 200 "" +// @Summary Adds a storage cluster to the config. +// @Tags Configuration +// @Router /config/storage [post] +// @Accept json +// @Param storage body model.BackupStorage true "backup storage" +// @Success 200 "" func (ws *HTTPServer) addStorage(w http.ResponseWriter, r *http.Request) { var newStorage model.BackupStorage err := json.NewDecoder(r.Body).Decode(&newStorage) @@ -163,10 +170,11 @@ func (ws *HTTPServer) addStorage(w http.ResponseWriter, r *http.Request) { } // readStorages reads all storages from the configuration. -// @Summary Reads all storages from the configuration. -// @Router /config/storage [get] -// @Produce json -// @Success 200 {array} model.BackupStorage +// @Summary Reads all storages from the configuration. +// @Tags Configuration +// @Router /config/storage [get] +// @Produce json +// @Success 200 {array} model.BackupStorage func (ws *HTTPServer) readStorages(w http.ResponseWriter) { storage := ws.config.BackupStorage jsonResponse, err := json.Marshal(storage) @@ -179,12 +187,13 @@ func (ws *HTTPServer) readStorages(w http.ResponseWriter) { } // updateStorage updates an existing storage in the configuration. -// @Summary Updates an existing storage in the configuration. -// @Router /config/storage [put] -// @Accept json -// @Param storage body model.BackupStorage true "backup storage" -// @Success 200 {string} string "OK" -// @Failure 400 {string} string "Bad Request" +// @Summary Updates an existing storage in the configuration. +// @Tags Configuration +// @Router /config/storage [put] +// @Accept json +// @Param storage body model.BackupStorage true "backup storage" +// @Success 200 {string} string "OK" +// @Failure 400 {string} string "Bad Request" func (ws *HTTPServer) updateStorage(w http.ResponseWriter, r *http.Request) { var updatedStorage model.BackupStorage err := json.NewDecoder(r.Body).Decode(&updatedStorage) @@ -204,11 +213,12 @@ func (ws *HTTPServer) updateStorage(w http.ResponseWriter, r *http.Request) { } // deleteStorage -// @Summary Deletes a storage from the configuration by name. -// @Router /config/storage [delete] -// @Param name query string true "Storage Name" -// @Success 200 {string} string "OK" -// @Failure 400 {string} string "Bad Request" +// @Summary Deletes a storage from the configuration by name. +// @Tags Configuration +// @Router /config/storage [delete] +// @Param name query string true "Storage Name" +// @Success 200 {string} string "OK" +// @Failure 400 {string} string "Bad Request" func (ws *HTTPServer) deleteStorage(w http.ResponseWriter, r *http.Request) { storageName := r.URL.Query().Get("name") if storageName == "" { @@ -229,11 +239,12 @@ func (ws *HTTPServer) deleteStorage(w http.ResponseWriter, r *http.Request) { } // addPolicy -// @Summary adds a policy to the config. -// @Router /config/policy [post] -// @Accept json -// @Param storage body model.BackupPolicy true "backup policy" -// @Success 200 "" +// @Summary Adds a policy to the config. +// @Tags Configuration +// @Router /config/policy [post] +// @Accept json +// @Param storage body model.BackupPolicy true "backup policy" +// @Success 200 "" func (ws *HTTPServer) addPolicy(w http.ResponseWriter, r *http.Request) { var newPolicy model.BackupPolicy err := json.NewDecoder(r.Body).Decode(&newPolicy) @@ -253,10 +264,11 @@ func (ws *HTTPServer) addPolicy(w http.ResponseWriter, r *http.Request) { } // readPolicies reads all backup policies from the configuration. -// @Summary Reads all policies from the configuration. -// @Router /config/policy [get] -// @Produce json -// @Success 200 {array} model.BackupPolicy +// @Summary Reads all policies from the configuration. +// @Tags Configuration +// @Router /config/policy [get] +// @Produce json +// @Success 200 {array} model.BackupPolicy func (ws *HTTPServer) readPolicies(w http.ResponseWriter) { policies := ws.config.BackupPolicy jsonResponse, err := json.Marshal(policies) @@ -269,12 +281,13 @@ func (ws *HTTPServer) readPolicies(w http.ResponseWriter) { } // updatePolicy updates an existing policy in the configuration. -// @Summary Updates an existing policy in the configuration. -// @Router /config/policy [put] -// @Accept json -// @Param storage body model.BackupPolicy true "backup policy" -// @Success 200 {string} string "OK" -// @Failure 400 {string} string "Bad Request" +// @Summary Updates an existing policy in the configuration. +// @Tags Configuration +// @Router /config/policy [put] +// @Accept json +// @Param storage body model.BackupPolicy true "backup policy" +// @Success 200 {string} string "OK" +// @Failure 400 {string} string "Bad Request" func (ws *HTTPServer) updatePolicy(w http.ResponseWriter, r *http.Request) { var updatedPolicy model.BackupPolicy err := json.NewDecoder(r.Body).Decode(&updatedPolicy) @@ -294,11 +307,12 @@ func (ws *HTTPServer) updatePolicy(w http.ResponseWriter, r *http.Request) { } // deletePolicy -// @Summary Deletes a policy from the configuration by name. -// @Router /config/policy [delete] -// @Param name query string true "Policy Name" -// @Success 200 {string} string "OK" -// @Failure 400 {string} string "Bad Request" +// @Summary Deletes a policy from the configuration by name. +// @Tags Configuration +// @Router /config/policy [delete] +// @Param name query string true "Policy Name" +// @Success 200 {string} string "OK" +// @Failure 400 {string} string "Bad Request" func (ws *HTTPServer) deletePolicy(w http.ResponseWriter, r *http.Request) { policyName := r.URL.Query().Get("name") if policyName == "" { diff --git a/internal/server/restore_handlers.go b/internal/server/restore_handlers.go new file mode 100644 index 00000000..d476de96 --- /dev/null +++ b/internal/server/restore_handlers.go @@ -0,0 +1,56 @@ +package server + +import ( + "encoding/json" + "fmt" + "net/http" + "strconv" + + "log/slog" + + "github.com/aerospike/backup/pkg/model" +) + +// @Summary Trigger an asynchronous restore operation. +// @Description Specify the directory parameter for the full backup restore. +// @Description Use the file parameter to restore from an incremental backup file. +// @Tags Restore +// @Router /restore [post] +// @Param request body model.RestoreRequest true "query params" +// @Success 200 {integer} int "Job ID (int64)" +func (ws *HTTPServer) restoreHandler(w http.ResponseWriter, r *http.Request) { + if r.Method == http.MethodPost { + var request model.RestoreRequest + + err := json.NewDecoder(r.Body).Decode(&request) + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + if err = request.Validate(); err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + jobID := ws.restoreService.Restore(&request) + slog.Info("Restore action", "jobID", jobID, "request", request) + fmt.Fprint(w, strconv.Itoa(jobID)) + } else { + http.Error(w, "", http.StatusNotFound) + } +} + +// @Summary Retrieve status for a restore job. +// @Tags Restore +// @Produce plain +// @Param jobId query int true "Job ID to retrieve the status" +// @Router /restore/status [get] +// @Success 200 {string} string "Job status" +func (ws *HTTPServer) restoreStatusHandler(w http.ResponseWriter, r *http.Request) { + jobIDParam := r.URL.Query().Get("jobId") + jobID, err := strconv.Atoi(jobIDParam) + if err != nil { + http.Error(w, "Invalid job id", http.StatusBadRequest) + } else { + fmt.Fprint(w, ws.restoreService.JobStatus(jobID)) + } +} diff --git a/internal/server/server.go b/internal/server/server.go index 8848723d..13ce9e3e 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -2,9 +2,6 @@ package server import ( "context" - "encoding/json" - "fmt" - "log/slog" "net" "net/http" "strconv" @@ -133,16 +130,6 @@ func (ws *HTTPServer) Shutdown() error { return ws.server.Shutdown(context.Background()) } -// @Summary Root endpoint -// @Router / [get] -// @Success 200 "" -func rootActionHandler(w http.ResponseWriter, r *http.Request) { - if r.URL.Path != "/" { - w.WriteHeader(http.StatusNotFound) - } - fmt.Fprintf(w, "") -} - func (ws *HTTPServer) configActionHandler(w http.ResponseWriter, r *http.Request) { switch r.Method { case http.MethodGet: @@ -198,120 +185,3 @@ func (ws *HTTPServer) configPolicyActionHandler(w http.ResponseWriter, r *http.R http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) } } - -// @Summary Health endpoint. -// @Router /health [get] -// @Success 200 "Ok" -func healthActionHandler(w http.ResponseWriter, _ *http.Request) { - fmt.Fprintf(w, "Ok") -} - -// @Summary Readiness endpoint. -// @Router /ready [get] -// @Success 200 "Ok" -func readyActionHandler(w http.ResponseWriter, _ *http.Request) { - fmt.Fprintf(w, "Ok") -} - -// @Summary Returns application version. -// @Router /version [get] -// @Success 200 {string} string "version" -func versionActionHandler(w http.ResponseWriter, _ *http.Request) { - fmt.Fprint(w, util.Version) -} - -// @Summary Trigger an asynchronous restore operation. -// @Description Specify the directory parameter for the full backup restore. -// @Description Use the file parameter to restore from an incremental backup file. -// @Router /restore [post] -// @Param request body model.RestoreRequest true "query params" -// @Success 200 {integer} int "Job ID (int64)" -func (ws *HTTPServer) restoreHandler(w http.ResponseWriter, r *http.Request) { - if r.Method == http.MethodPost { - var request model.RestoreRequest - - err := json.NewDecoder(r.Body).Decode(&request) - if err != nil { - http.Error(w, err.Error(), http.StatusBadRequest) - return - } - if err = request.Validate(); err != nil { - http.Error(w, err.Error(), http.StatusBadRequest) - return - } - jobID := ws.restoreService.Restore(&request) - slog.Info("Restore action", "jobID", jobID, "request", request) - fmt.Fprint(w, strconv.Itoa(jobID)) - } else { - http.Error(w, "", http.StatusNotFound) - } -} - -// @Summary Retrieve status for a restore job. -// @Produce plain -// @Param jobId query int true "Job ID to retrieve the status" -// @Router /restore/status [get] -// @Success 200 {string} string "Job status" -func (ws *HTTPServer) restoreStatusHandler(w http.ResponseWriter, r *http.Request) { - jobIDParam := r.URL.Query().Get("jobId") - jobID, err := strconv.Atoi(jobIDParam) - if err != nil { - http.Error(w, "Invalid job id", http.StatusBadRequest) - } else { - fmt.Fprint(w, ws.restoreService.JobStatus(jobID)) - } -} - -// @Summary Get available full backups. -// @Produce plain -// @Param name query string true "Backup policy name" -// @Router /backup/full/list [get] -// @Success 200 {array} model.BackupDetails "Full backups" -// @Failure 404 {string} string "" -func (ws *HTTPServer) getAvailableFullBackups(w http.ResponseWriter, r *http.Request) { - policyName := r.URL.Query().Get("name") - if policyName == "" { - http.Error(w, "Invalid/undefined policy name", http.StatusBadRequest) - } else { - list, err := ws.backupBackends[policyName].FullBackupList() - if err != nil { - slog.Error("Get full backup list", "err", err) - http.Error(w, "", http.StatusNotFound) - } else { - response, err := json.Marshal(list) - if err != nil { - slog.Error("Failed to parse full backup list", "err", err) - http.Error(w, "", http.StatusInternalServerError) - } else { - fmt.Fprint(w, string(response)) - } - } - } -} - -// @Summary Get available incremental backups. -// @Produce plain -// @Param name query string true "Backup policy name" -// @Router /backup/incremental/list [get] -// @Success 200 {array} model.BackupDetails "Incremental backups" -// @Failure 404 {string} string "" -func (ws *HTTPServer) getAvailableIncrBackups(w http.ResponseWriter, r *http.Request) { - policyName := r.URL.Query().Get("name") - if policyName == "" { - http.Error(w, "Invalid/undefined policy name", http.StatusBadRequest) - } else { - list, err := ws.backupBackends[policyName].IncrementalBackupList() - if err != nil { - slog.Error("Get incremental backup list", "err", err) - http.Error(w, "", http.StatusNotFound) - } else { - response, err := json.Marshal(list) - if err != nil { - slog.Error("Failed to parse incremental backup list", "err", err) - http.Error(w, "", http.StatusInternalServerError) - } else { - fmt.Fprint(w, string(response)) - } - } - } -} diff --git a/internal/server/system_handlers.go b/internal/server/system_handlers.go new file mode 100644 index 00000000..acfed629 --- /dev/null +++ b/internal/server/system_handlers.go @@ -0,0 +1,43 @@ +package server + +import ( + "fmt" + "net/http" + + "github.com/aerospike/backup/internal/util" +) + +// @Summary Root endpoint. +// @Tags System +// @Router / [get] +// @Success 200 "" +func rootActionHandler(w http.ResponseWriter, r *http.Request) { + if r.URL.Path != "/" { + w.WriteHeader(http.StatusNotFound) + } + fmt.Fprintf(w, "") +} + +// @Summary Health endpoint. +// @Tags System +// @Router /health [get] +// @Success 200 "Ok" +func healthActionHandler(w http.ResponseWriter, _ *http.Request) { + fmt.Fprintf(w, "Ok") +} + +// @Summary Readiness endpoint. +// @Tags System +// @Router /ready [get] +// @Success 200 "Ok" +func readyActionHandler(w http.ResponseWriter, _ *http.Request) { + fmt.Fprintf(w, "Ok") +} + +// @Summary Returns application version. +// @Tags System +// @Router /version [get] +// @Success 200 {string} string "version" +func versionActionHandler(w http.ResponseWriter, _ *http.Request) { + fmt.Fprint(w, util.Version) +}