diff --git a/docker/README.md b/docker/README.md index 2b44d1f8..6e8e259c 100644 --- a/docker/README.md +++ b/docker/README.md @@ -102,34 +102,11 @@ docker run --name some-sftpgo \ -d "drakkan/sftpgo:tag" ``` -Setting the SFTPGO_GRACE_TIME environment variable to a non zero value when creating or running a container will enable a graceful shutdown period in seconds that will allow existing connections to hopefully complete before being forcibly closed when the time has passed. +Setting the `SFTPGO_GRACE_TIME` environment variable to a non zero value when creating or running a container will enable a graceful shutdown period in seconds that will allow existing connections to hopefully complete before being forcibly closed when the time has passed. -```shell -echo "put 10G.dd" | sftp -P 2022 testuser@sftpgo.example.net - -Connected to sftpgo.example.net. -sftp> put 10G.dd -Uploading 10G.dd to /10G.dd -10G.dd 17% 1758MB 100.9MB/s 01:24 ETA -client_loop: send disconnect: Broken pipe -Connection closed. -``` - -While the SFTPGO container is in graceful shutdown mode waiting for the last connection(s) to finish, no new connections will be allowed. - -```shell -Fri 23 Dec 2022 08:47:41 AM UTC -Connected to sftpgo.example.net. -sftp> put d.txt -Uploading d.txt to /d.txt -d.txt 100% 323 216.9KB/s 00:00 -Fri 23 Dec 2022 08:47:42 AM UTC -kex_exchange_identification: Connection closed by remote host -Connection closed. -``` - -If no connections are active or SFTPGO_GRACE_TIME=0 the container will shutdown immediately. +While the SFTPGo container is in graceful shutdown mode waiting for the last connection(s) to finish, no new connections will be allowed. +If no connections are active or `SFTPGO_GRACE_TIME=0` (default value if unset) the container will shutdown immediately. ### Where to Store Data diff --git a/internal/httpd/api_admin.go b/internal/httpd/api_admin.go index 845b4f76..06254dcb 100644 --- a/internal/httpd/api_admin.go +++ b/internal/httpd/api_admin.go @@ -17,6 +17,7 @@ package httpd import ( "context" "errors" + "fmt" "net/http" "github.com/go-chi/jwtauth/v5" @@ -65,12 +66,13 @@ func renderAdmin(w http.ResponseWriter, r *http.Request, username string, status func addAdmin(w http.ResponseWriter, r *http.Request) { r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize) + claims, err := getTokenClaims(r) if err != nil || claims.Username == "" { sendAPIResponse(w, r, err, "Invalid token claims", http.StatusBadRequest) return } - var admin dataprovider.Admin + admin := dataprovider.Admin{} err = render.DecodeJSON(r.Body, &admin) if err != nil { sendAPIResponse(w, r, err, "", http.StatusBadRequest) @@ -81,6 +83,7 @@ func addAdmin(w http.ResponseWriter, r *http.Request) { sendAPIResponse(w, r, err, "", getRespStatus(err)) return } + w.Header().Add("Location", fmt.Sprintf("%s/%s", adminPath, admin.Username)) renderAdmin(w, r, admin.Username, http.StatusCreated) } diff --git a/internal/httpd/api_eventrule.go b/internal/httpd/api_eventrule.go index 7c33deed..09b6e3b7 100644 --- a/internal/httpd/api_eventrule.go +++ b/internal/httpd/api_eventrule.go @@ -16,6 +16,7 @@ package httpd import ( "context" + "fmt" "net/http" "github.com/go-chi/render" @@ -74,11 +75,13 @@ func addEventAction(w http.ResponseWriter, r *http.Request) { sendAPIResponse(w, r, err, "", http.StatusBadRequest) return } - err = dataprovider.AddEventAction(&action, claims.Username, util.GetIPFromRemoteAddress(r.RemoteAddr), claims.Role) + ipAddr := util.GetIPFromRemoteAddress(r.RemoteAddr) + err = dataprovider.AddEventAction(&action, claims.Username, ipAddr, claims.Role) if err != nil { sendAPIResponse(w, r, err, "", getRespStatus(err)) return } + w.Header().Add("Location", fmt.Sprintf("%s/%s", eventActionsPath, action.Name)) renderEventAction(w, r, action.Name, http.StatusCreated) } @@ -188,11 +191,12 @@ func addEventRule(w http.ResponseWriter, r *http.Request) { sendAPIResponse(w, r, err, "", http.StatusBadRequest) return } - err = dataprovider.AddEventRule(&rule, claims.Username, util.GetIPFromRemoteAddress(r.RemoteAddr), claims.Role) - if err != nil { + ipAddr := util.GetIPFromRemoteAddress(r.RemoteAddr) + if err := dataprovider.AddEventRule(&rule, claims.Username, ipAddr, claims.Role); err != nil { sendAPIResponse(w, r, err, "", getRespStatus(err)) return } + w.Header().Add("Location", fmt.Sprintf("%s/%s", eventRulesPath, rule.Name)) renderEventRule(w, r, rule.Name, http.StatusCreated) } diff --git a/internal/httpd/api_folder.go b/internal/httpd/api_folder.go index 0f522fe0..7b1b528f 100644 --- a/internal/httpd/api_folder.go +++ b/internal/httpd/api_folder.go @@ -16,6 +16,7 @@ package httpd import ( "context" + "fmt" "net/http" "github.com/go-chi/render" @@ -42,6 +43,7 @@ func getFolders(w http.ResponseWriter, r *http.Request) { func addFolder(w http.ResponseWriter, r *http.Request) { r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize) + claims, err := getTokenClaims(r) if err != nil || claims.Username == "" { sendAPIResponse(w, r, err, "Invalid token claims", http.StatusBadRequest) @@ -54,11 +56,11 @@ func addFolder(w http.ResponseWriter, r *http.Request) { sendAPIResponse(w, r, err, "", http.StatusBadRequest) return } - err = dataprovider.AddFolder(&folder, claims.Username, util.GetIPFromRemoteAddress(r.RemoteAddr), claims.Role) - if err != nil { + if err := dataprovider.AddFolder(&folder, claims.Username, util.GetIPFromRemoteAddress(r.RemoteAddr), claims.Role); err != nil { sendAPIResponse(w, r, err, "", getRespStatus(err)) return } + w.Header().Add("Location", fmt.Sprintf("%s/%s", folderPath, folder.Name)) renderFolder(w, r, folder.Name, http.StatusCreated) } diff --git a/internal/httpd/api_group.go b/internal/httpd/api_group.go index 15187709..f0186c70 100644 --- a/internal/httpd/api_group.go +++ b/internal/httpd/api_group.go @@ -16,6 +16,7 @@ package httpd import ( "context" + "fmt" "net/http" "github.com/go-chi/render" @@ -58,6 +59,7 @@ func addGroup(w http.ResponseWriter, r *http.Request) { sendAPIResponse(w, r, err, "", getRespStatus(err)) return } + w.Header().Add("Location", fmt.Sprintf("%s/%s", groupPath, group.Name)) renderGroup(w, r, group.Name, http.StatusCreated) } diff --git a/internal/httpd/api_role.go b/internal/httpd/api_role.go index 70678bbd..ff1647c8 100644 --- a/internal/httpd/api_role.go +++ b/internal/httpd/api_role.go @@ -16,6 +16,7 @@ package httpd import ( "context" + "fmt" "net/http" "github.com/go-chi/render" @@ -47,6 +48,7 @@ func addRole(w http.ResponseWriter, r *http.Request) { sendAPIResponse(w, r, err, "Invalid token claims", http.StatusBadRequest) return } + var role dataprovider.Role err = render.DecodeJSON(r.Body, &role) if err != nil { @@ -56,9 +58,10 @@ func addRole(w http.ResponseWriter, r *http.Request) { err = dataprovider.AddRole(&role, claims.Username, util.GetIPFromRemoteAddress(r.RemoteAddr), claims.Role) if err != nil { sendAPIResponse(w, r, err, "", getRespStatus(err)) - return + } else { + w.Header().Add("Location", fmt.Sprintf("%s/%s", rolesPath, role.Name)) + renderRole(w, r, role.Name, http.StatusCreated) } - renderRole(w, r, role.Name, http.StatusCreated) } func updateRole(w http.ResponseWriter, r *http.Request) { diff --git a/internal/httpd/api_user.go b/internal/httpd/api_user.go index dff96a24..15475f56 100644 --- a/internal/httpd/api_user.go +++ b/internal/httpd/api_user.go @@ -113,6 +113,7 @@ func addUser(w http.ResponseWriter, r *http.Request) { sendAPIResponse(w, r, err, "", getRespStatus(err)) return } + w.Header().Add("Location", fmt.Sprintf("%s/%s", userPath, user.Username)) renderUser(w, r, user.Username, claims.Role, http.StatusCreated) } diff --git a/openapi/openapi.yaml b/openapi/openapi.yaml index b6f0ea9d..a78b0389 100644 --- a/openapi/openapi.yaml +++ b/openapi/openapi.yaml @@ -1385,6 +1385,11 @@ paths: responses: '201': description: successful operation + headers: + Location: + schema: + type: string + description: 'URI of the newly created object' content: application/json; charset=utf-8: schema: @@ -1560,6 +1565,11 @@ paths: responses: '201': description: successful operation + headers: + Location: + schema: + type: string + description: 'URI of the newly created object' content: application/json; charset=utf-8: schema: @@ -1735,6 +1745,11 @@ paths: responses: '201': description: successful operation + headers: + Location: + schema: + type: string + description: 'URI of the newly created object' content: application/json; charset=utf-8: schema: @@ -1910,6 +1925,11 @@ paths: responses: '201': description: successful operation + headers: + Location: + schema: + type: string + description: 'URI of the newly created object' content: application/json; charset=utf-8: schema: @@ -2085,6 +2105,11 @@ paths: responses: '201': description: successful operation + headers: + Location: + schema: + type: string + description: 'URI of the newly created object' content: application/json; charset=utf-8: schema: @@ -2574,7 +2599,7 @@ paths: Location: schema: type: string - description: URL to retrieve the details for the new created API key + description: URI to retrieve the details for the new created API key content: application/json; charset=utf-8: schema: @@ -2763,6 +2788,11 @@ paths: responses: '201': description: successful operation + headers: + Location: + schema: + type: string + description: 'URI of the newly created object' content: application/json; charset=utf-8: schema: @@ -3052,6 +3082,11 @@ paths: responses: '201': description: successful operation + headers: + Location: + schema: + type: string + description: 'URI of the newly created object' content: application/json; charset=utf-8: schema: @@ -3820,7 +3855,7 @@ paths: Location: schema: type: string - description: URL to retrieve the details for the new created share + description: URI to retrieve the details for the new created share content: application/json; charset=utf-8: schema: