Skip to content

Commit

Permalink
APPS-916 REST API for configuration (#20)
Browse files Browse the repository at this point in the history
* add config manager

* cluster CRUD

* extract business logic

* add tests

* clean code

* add storage service

* add storage endpoints

* add policy endpoints

* add extra validation and tests

* clean code

* fix error formatting

* fix tests

* fix error messages

* fix linter warnings

* add nolint:typecheck

---------

Co-authored-by: yrizhkov <[email protected]>
  • Loading branch information
korotkov-aerospike and reugn authored Oct 17, 2023
1 parent f1e63b6 commit 70f0720
Show file tree
Hide file tree
Showing 15 changed files with 919 additions and 32 deletions.
7 changes: 4 additions & 3 deletions cmd/backup/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ func run() int {
// set default logger
slog.SetDefault(slog.New(util.LogHandler(logLevel)))
// read configuration file
config, err := readConfiguration(configFile)
server.ConfigurationManager = service.NewConfigurationManager(configFile)
config, err := readConfiguration()
if err != nil {
return err
}
Expand Down Expand Up @@ -73,8 +74,8 @@ func systemCtx() context.Context {
return ctx
}

func readConfiguration(configFile string) (*model.Config, error) {
config, err := model.ReadConfiguration(configFile)
func readConfiguration() (*model.Config, error) {
config, err := server.ConfigurationManager.ReadConfiguration()
if err != nil {
slog.Error("failed to read configuration file", "error", err)
return nil, err
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ require (
github.com/aws/aws-sdk-go-v2 v1.21.0
github.com/aws/aws-sdk-go-v2/config v1.18.42
github.com/aws/aws-sdk-go-v2/service/s3 v1.39.0
github.com/aws/smithy-go v1.14.2
github.com/prometheus/client_golang v1.16.0
github.com/spf13/cobra v1.7.0
golang.org/x/time v0.3.0
Expand All @@ -27,7 +28,6 @@ require (
github.com/aws/aws-sdk-go-v2/service/sso v1.14.1 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.17.1 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.22.0 // indirect
github.com/aws/smithy-go v1.14.2 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/golang/protobuf v1.5.3 // indirect
Expand Down
319 changes: 319 additions & 0 deletions internal/server/config_handlers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,319 @@
package server

import (
"encoding/json"
"net/http"

"github.com/aerospike/backup/pkg/model"
"github.com/aerospike/backup/pkg/service"
)

var ConfigurationManager service.ConfigurationManager

// readConfig
// @Summary Returns the configuration for the service.
// @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 {
http.Error(w, "Failed to parse service configuration", http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
_, _ = w.Write(configuration)
}

// updateConfig
// @Summary Updates the configuration for the service.
// @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

err := json.NewDecoder(r.Body).Decode(&newConfig)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
ws.config = &newConfig
err = ConfigurationManager.WriteConfiguration(&newConfig)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
}

// 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 ""
func (ws *HTTPServer) addAerospikeCluster(w http.ResponseWriter, r *http.Request) {
var newCluster model.AerospikeCluster
err := json.NewDecoder(r.Body).Decode(&newCluster)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
err = service.AddCluster(ws.config, &newCluster)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
err = ConfigurationManager.WriteConfiguration(ws.config)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
}
}

// 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
func (ws *HTTPServer) readAerospikeClusters(w http.ResponseWriter) {
clusters := ws.config.AerospikeClusters
jsonResponse, err := json.Marshal(clusters)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write(jsonResponse)
}

// 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"
func (ws *HTTPServer) updateAerospikeCluster(w http.ResponseWriter, r *http.Request) {
var updatedCluster model.AerospikeCluster
err := json.NewDecoder(r.Body).Decode(&updatedCluster)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
err = service.UpdateCluster(ws.config, &updatedCluster)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
err = ConfigurationManager.WriteConfiguration(ws.config)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
}
}

// 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"
func (ws *HTTPServer) deleteAerospikeCluster(w http.ResponseWriter, r *http.Request) {
clusterName := r.URL.Query().Get("name")
if clusterName == "" {
http.Error(w, "Cluster name is required", http.StatusBadRequest)
return
}

err := service.DeleteCluster(ws.config, &clusterName)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}

err = ConfigurationManager.WriteConfiguration(ws.config)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
}
}

// 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 ""
func (ws *HTTPServer) addStorage(w http.ResponseWriter, r *http.Request) {
var newStorage model.BackupStorage
err := json.NewDecoder(r.Body).Decode(&newStorage)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
err = service.AddStorage(ws.config, &newStorage)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
err = ConfigurationManager.WriteConfiguration(ws.config)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
}
}

// 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
func (ws *HTTPServer) readStorages(w http.ResponseWriter) {
storage := ws.config.BackupStorage
jsonResponse, err := json.Marshal(storage)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write(jsonResponse)
}

// 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"
func (ws *HTTPServer) updateStorage(w http.ResponseWriter, r *http.Request) {
var updatedStorage model.BackupStorage
err := json.NewDecoder(r.Body).Decode(&updatedStorage)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
err = service.UpdateStorage(ws.config, &updatedStorage)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
err = ConfigurationManager.WriteConfiguration(ws.config)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
}
}

// 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"
func (ws *HTTPServer) deleteStorage(w http.ResponseWriter, r *http.Request) {
storageName := r.URL.Query().Get("name")
if storageName == "" {
http.Error(w, "Storage name is required", http.StatusBadRequest)
return
}

err := service.DeleteStorage(ws.config, &storageName)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}

err = ConfigurationManager.WriteConfiguration(ws.config)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
}
}

// addPolicy
// @Summary adds a policy to the config.
// @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)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
err = service.AddPolicy(ws.config, &newPolicy)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
err = ConfigurationManager.WriteConfiguration(ws.config)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
}
}

// 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
func (ws *HTTPServer) readPolicies(w http.ResponseWriter) {
policies := ws.config.BackupPolicy
jsonResponse, err := json.Marshal(policies)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write(jsonResponse)
}

// 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"
func (ws *HTTPServer) updatePolicy(w http.ResponseWriter, r *http.Request) {
var updatedPolicy model.BackupPolicy
err := json.NewDecoder(r.Body).Decode(&updatedPolicy)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
err = service.UpdatePolicy(ws.config, &updatedPolicy)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
err = ConfigurationManager.WriteConfiguration(ws.config)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
}
}

// 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"
func (ws *HTTPServer) deletePolicy(w http.ResponseWriter, r *http.Request) {
policyName := r.URL.Query().Get("name")
if policyName == "" {
http.Error(w, "Policy name is required", http.StatusBadRequest)
return
}

err := service.DeletePolicy(ws.config, &policyName)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}

err = ConfigurationManager.WriteConfiguration(ws.config)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
}
}
Loading

0 comments on commit 70f0720

Please sign in to comment.