diff --git a/ReadMe.md b/ReadMe.md index fdc38fff..0768b2f7 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -401,7 +401,7 @@ Create new backup: `curl -s localhost:7171/backup/create -X POST | jq .` - Optional query argument `schema` works the same as the `--schema` CLI argument (backup schema only). - Optional query argument `rbac` works the same as the `--rbac` CLI argument (backup RBAC). - Optional query argument `configs` works the same as the `--configs` CLI argument (backup configs). -- Optional query argument `callback` allow pass callback URL which will call with POST with `application/json` with payload `{"status":"error|success","error":"not empty when error happens"}`. +- Optional query argument `callback` allow pass callback URL which will call with POST with `application/json` with payload `{"status":"error|success","error":"not empty when error happens", "operation_id" : ""}`. - Additional example: `curl -s 'localhost:7171/backup/create?table=default.billing&name=billing_test' -X POST` Note: this operation is asynchronous, so the API will return once the operation has started. @@ -443,7 +443,7 @@ Upload backup to remote storage: `curl -s localhost:7171/backup/upload/"}`. Note: this operation is asynchronous, so the API will return once the operation has started. @@ -464,7 +464,7 @@ Download backup from remote storage: `curl -s localhost:7171/backup/download/"}`. Note: this operation is asynchronous, so the API will return once the operation has started. @@ -482,7 +482,7 @@ Create schema and restore data from backup: `curl -s localhost:7171/backup/resto - Optional query argument `configs` works the same as the `--configs` CLI argument (restore configs). - Optional query argument `restore_database_mapping` works the same as the `--restore-database-mapping` CLI argument. - Optional query argument `restore_table_mapping` works the same as the `--restore-table-mapping` CLI argument. -- Optional query argument `callback` allow pass callback URL which will call with POST with `application/json` with payload `{"status":"error|success","error":"not empty when error happens"}`. +- Optional query argument `callback` allow pass callback URL which will call with POST with `application/json` with payload `{"status":"error|success","error":"not empty when error happens", "operation_id" : ""}`. ### POST /backup/delete diff --git a/pkg/server/server.go b/pkg/server/server.go index 11cd3257..62f20bb1 100644 --- a/pkg/server/server.go +++ b/pkg/server/server.go @@ -36,6 +36,7 @@ import ( "github.com/Altinity/clickhouse-backup/v2/pkg/server/metrics" "github.com/Altinity/clickhouse-backup/v2/pkg/status" "github.com/Altinity/clickhouse-backup/v2/pkg/utils" + "github.com/google/uuid" ) type APIServer struct { @@ -874,6 +875,8 @@ func (api *APIServer) httpCreateHandler(w http.ResponseWriter, r *http.Request) checkPartsColumns := true fullCommand := "create" query := r.URL.Query() + operationId, _ := uuid.NewUUID() + if tp, exist := query["table"]; exist { tablePattern = tp[0] fullCommand = fmt.Sprintf("%s --tables=\"%s\"", fullCommand, tablePattern) @@ -930,7 +933,7 @@ func (api *APIServer) httpCreateHandler(w http.ResponseWriter, r *http.Request) if err != nil { log.Error().Msgf("API /backup/create error: %v", err) status.Current.Stop(commandId, err) - api.errorCallback(context.Background(), err, callback) + api.errorCallback(context.Background(), err, operationId.String(), callback) return } go func() { @@ -940,16 +943,18 @@ func (api *APIServer) httpCreateHandler(w http.ResponseWriter, r *http.Request) }() status.Current.Stop(commandId, nil) - api.successCallback(context.Background(), callback) + api.successCallback(context.Background(), operationId.String(), callback) }() api.sendJSONEachRow(w, http.StatusCreated, struct { - Status string `json:"status"` - Operation string `json:"operation"` - BackupName string `json:"backup_name"` + Status string `json:"status"` + Operation string `json:"operation"` + BackupName string `json:"backup_name"` + OperationId string `json:"operation_id"` }{ - Status: "acknowledged", - Operation: "create", - BackupName: backupName, + Status: "acknowledged", + Operation: "create", + BackupName: backupName, + OperationId: operationId.String(), }) } @@ -1118,6 +1123,7 @@ func (api *APIServer) httpUploadHandler(w http.ResponseWriter, r *http.Request) schemaOnly := false resume := false fullCommand := "upload" + operationId, _ := uuid.NewUUID() if _, exist := query["delete-source"]; exist { deleteSource = true @@ -1167,7 +1173,7 @@ func (api *APIServer) httpUploadHandler(w http.ResponseWriter, r *http.Request) if err != nil { log.Error().Msgf("Upload error: %v", err) status.Current.Stop(commandId, err) - api.errorCallback(context.Background(), err, callback) + api.errorCallback(context.Background(), err, operationId.String(), callback) return } go func() { @@ -1176,20 +1182,22 @@ func (api *APIServer) httpUploadHandler(w http.ResponseWriter, r *http.Request) } }() status.Current.Stop(commandId, nil) - api.successCallback(context.Background(), callback) + api.successCallback(context.Background(), operationId.String(), callback) }() api.sendJSONEachRow(w, http.StatusOK, struct { - Status string `json:"status"` - Operation string `json:"operation"` - BackupName string `json:"backup_name"` - BackupFrom string `json:"backup_from,omitempty"` - Diff bool `json:"diff"` + Status string `json:"status"` + Operation string `json:"operation"` + BackupName string `json:"backup_name"` + BackupFrom string `json:"backup_from,omitempty"` + Diff bool `json:"diff"` + OperationId string `json:"operation_id"` }{ - Status: "acknowledged", - Operation: "upload", - BackupName: name, - BackupFrom: diffFrom, - Diff: diffFrom != "", + Status: "acknowledged", + Operation: "upload", + BackupName: name, + BackupFrom: diffFrom, + Diff: diffFrom != "", + OperationId: operationId.String(), }) } @@ -1219,6 +1227,7 @@ func (api *APIServer) httpRestoreHandler(w http.ResponseWriter, r *http.Request) restoreRBAC := false restoreConfigs := false fullCommand := "restore" + operationId, _ := uuid.NewUUID() query := r.URL.Query() if tp, exist := query["table"]; exist { @@ -1310,19 +1319,21 @@ func (api *APIServer) httpRestoreHandler(w http.ResponseWriter, r *http.Request) status.Current.Stop(commandId, err) if err != nil { log.Error().Msgf("API /backup/restore error: %v", err) - api.errorCallback(context.Background(), err, callback) + api.errorCallback(context.Background(), err, operationId.String(), callback) return } - api.successCallback(context.Background(), callback) + api.successCallback(context.Background(), operationId.String(), callback) }() api.sendJSONEachRow(w, http.StatusOK, struct { - Status string `json:"status"` - Operation string `json:"operation"` - BackupName string `json:"backup_name"` + Status string `json:"status"` + Operation string `json:"operation"` + BackupName string `json:"backup_name"` + OperationId string `json:"operation_id"` }{ - Status: "acknowledged", - Operation: "restore", - BackupName: name, + Status: "acknowledged", + Operation: "restore", + BackupName: name, + OperationId: operationId.String(), }) } @@ -1346,6 +1357,7 @@ func (api *APIServer) httpDownloadHandler(w http.ResponseWriter, r *http.Request schemaOnly := false resume := false fullCommand := "download" + operationId, _ := uuid.NewUUID() if tp, exist := query["table"]; exist { tablePattern = tp[0] @@ -1381,7 +1393,7 @@ func (api *APIServer) httpDownloadHandler(w http.ResponseWriter, r *http.Request if err != nil { log.Error().Msgf("API /backup/download error: %v", err) status.Current.Stop(commandId, err) - api.errorCallback(context.Background(), err, callback) + api.errorCallback(context.Background(), err, operationId.String(), callback) return } go func() { @@ -1390,16 +1402,18 @@ func (api *APIServer) httpDownloadHandler(w http.ResponseWriter, r *http.Request } }() status.Current.Stop(commandId, nil) - api.successCallback(context.Background(), callback) + api.successCallback(context.Background(), operationId.String(), callback) }() api.sendJSONEachRow(w, http.StatusOK, struct { - Status string `json:"status"` - Operation string `json:"operation"` - BackupName string `json:"backup_name"` + Status string `json:"status"` + Operation string `json:"operation"` + BackupName string `json:"backup_name"` + OperationId string `json:"operation_id"` }{ - Status: "acknowledged", - Operation: "download", - BackupName: name, + Status: "acknowledged", + Operation: "download", + BackupName: name, + OperationId: operationId.String(), }) } diff --git a/pkg/server/utils.go b/pkg/server/utils.go index caa1d82b..01339f0a 100644 --- a/pkg/server/utils.go +++ b/pkg/server/utils.go @@ -4,9 +4,10 @@ import ( "context" "encoding/json" "fmt" - "github.com/rs/zerolog/log" "net/http" "reflect" + + "github.com/rs/zerolog/log" ) func (api *APIServer) flushOutput(w http.ResponseWriter, out string) { @@ -61,15 +62,17 @@ func (api *APIServer) sendJSONEachRow(w http.ResponseWriter, statusCode int, v i // CallbackResponse is the response that is returned to callers type CallbackResponse struct { - Status string `json:"status"` - Error string `json:"error"` + Status string `json:"status"` + Error string `json:"error"` + OperationId string `json:"operation_id"` } // errorCallback executes callbacks with a payload notifying callers that the operation has failed -func (api *APIServer) errorCallback(ctx context.Context, err error, callback callbackFn) { +func (api *APIServer) errorCallback(ctx context.Context, err error, operationId string, callback callbackFn) { payload := &CallbackResponse{ - Status: "error", - Error: err.Error(), + Status: "error", + Error: err.Error(), + OperationId: operationId, } for _, e := range callback(ctx, payload) { log.Error().Err(e).Send() @@ -77,10 +80,11 @@ func (api *APIServer) errorCallback(ctx context.Context, err error, callback cal } // successCallback executes callbacks with a payload notifying callers that the operation succeeded -func (api *APIServer) successCallback(ctx context.Context, callback callbackFn) { +func (api *APIServer) successCallback(ctx context.Context, operationId string, callback callbackFn) { payload := &CallbackResponse{ - Status: "success", - Error: "", + Status: "success", + Error: "", + OperationId: operationId, } for _, e := range callback(ctx, payload) { log.Error().Err(e).Send()