Update golangci-lint to v2.12.2 with canonical config
All checks were successful
Check / check (pull_request) Successful in 3m22s
All checks were successful
Check / check (pull_request) Successful in 3m22s
Bump golangci-lint from v2.10.1 to v2.12.2 in the Dockerfile lint stage (tag+digest pin) and script/bootstrap release-archive pins (linux amd64/arm64 sha256s). Replace .golangci.yml with the canonical v2-layout config so linter settings (lll 88, funlen 80/50, cyclop 15, dupl 100) actually apply. Fix all findings surfaced by the new linter and config: - noctx: use httptest.NewRequestWithContext in all tests - gosec G710/G703: route app redirects through a path-escaping redirectToApp helper; annotate internal log path usage - goconst: introduce shared constants for template/JSON keys and repeated test literals - lll: wrap lines to the 88-column limit - dupl: extract shared helpers (generic findAllByAppID in models, deleteAppResource in handlers, parsePush in webhook payloads, table-driven/helper-based test dedup) - nolintlint: drop nolint directives made obsolete by the new limits Record the change in TODO.md; make check is green.
This commit is contained in:
@@ -7,6 +7,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
@@ -15,6 +16,7 @@ import (
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
|
||||
"sneak.berlin/go/upaas/internal/database"
|
||||
"sneak.berlin/go/upaas/internal/models"
|
||||
"sneak.berlin/go/upaas/internal/service/app"
|
||||
"sneak.berlin/go/upaas/templates"
|
||||
@@ -27,6 +29,23 @@ const (
|
||||
deploymentsHistoryLimit = 50
|
||||
)
|
||||
|
||||
// redirectToApp issues a SeeOther redirect to the page for the given
|
||||
// app ID, with an optional suffix such as "/deployments" or
|
||||
// "?success=updated". The ID is path-escaped so the target is always
|
||||
// a relative application URL.
|
||||
func redirectToApp(
|
||||
writer http.ResponseWriter,
|
||||
request *http.Request,
|
||||
appID, suffix string,
|
||||
) {
|
||||
http.Redirect(
|
||||
writer,
|
||||
request,
|
||||
"/apps/"+url.PathEscape(appID)+suffix,
|
||||
http.StatusSeeOther,
|
||||
)
|
||||
}
|
||||
|
||||
// HandleAppNew returns the new app form handler.
|
||||
func (h *Handlers) HandleAppNew() http.HandlerFunc {
|
||||
tmpl := templates.GetParsed()
|
||||
@@ -39,7 +58,9 @@ func (h *Handlers) HandleAppNew() http.HandlerFunc {
|
||||
}
|
||||
|
||||
// HandleAppCreate handles app creation.
|
||||
func (h *Handlers) HandleAppCreate() http.HandlerFunc { //nolint:funlen // validation adds necessary length
|
||||
//
|
||||
//nolint:funlen // validation adds necessary length
|
||||
func (h *Handlers) HandleAppCreate() http.HandlerFunc {
|
||||
tmpl := templates.GetParsed()
|
||||
|
||||
return func(writer http.ResponseWriter, request *http.Request) {
|
||||
@@ -160,10 +181,14 @@ func (h *Handlers) HandleAppDetail() http.HandlerFunc {
|
||||
}
|
||||
|
||||
webhookURL := "https://" + request.Host + "/webhook/" + application.WebhookSecret
|
||||
deployKey := formatDeployKey(application.SSHPublicKey, application.CreatedAt, application.Name)
|
||||
deployKey := formatDeployKey(
|
||||
application.SSHPublicKey,
|
||||
application.CreatedAt,
|
||||
application.Name,
|
||||
)
|
||||
|
||||
data := h.addGlobals(map[string]any{
|
||||
"App": application,
|
||||
dataKeyApp: application,
|
||||
"EnvVars": envVars,
|
||||
"Labels": labels,
|
||||
"Volumes": volumes,
|
||||
@@ -201,7 +226,7 @@ func (h *Handlers) HandleAppEdit() http.HandlerFunc {
|
||||
}
|
||||
|
||||
data := h.addGlobals(map[string]any{
|
||||
"App": application,
|
||||
dataKeyApp: application,
|
||||
}, request)
|
||||
|
||||
h.renderTemplate(writer, tmpl, "app_edit.html", data)
|
||||
@@ -209,7 +234,7 @@ func (h *Handlers) HandleAppEdit() http.HandlerFunc {
|
||||
}
|
||||
|
||||
// HandleAppUpdate handles app updates.
|
||||
func (h *Handlers) HandleAppUpdate() http.HandlerFunc { //nolint:funlen // validation adds necessary length
|
||||
func (h *Handlers) HandleAppUpdate() http.HandlerFunc {
|
||||
tmpl := templates.GetParsed()
|
||||
|
||||
return func(writer http.ResponseWriter, request *http.Request) {
|
||||
@@ -234,8 +259,8 @@ func (h *Handlers) HandleAppUpdate() http.HandlerFunc { //nolint:funlen // valid
|
||||
nameErr := validateAppName(newName)
|
||||
if nameErr != nil {
|
||||
data := h.addGlobals(map[string]any{
|
||||
"App": application,
|
||||
"Error": "Invalid app name: " + nameErr.Error(),
|
||||
dataKeyApp: application,
|
||||
dataKeyError: "Invalid app name: " + nameErr.Error(),
|
||||
}, request)
|
||||
h.renderTemplate(writer, tmpl, "app_edit.html", data)
|
||||
|
||||
@@ -245,8 +270,8 @@ func (h *Handlers) HandleAppUpdate() http.HandlerFunc { //nolint:funlen // valid
|
||||
repoURLErr := validateRepoURL(request.FormValue("repo_url"))
|
||||
if repoURLErr != nil {
|
||||
data := h.addGlobals(map[string]any{
|
||||
"App": application,
|
||||
"Error": "Invalid repository URL: " + repoURLErr.Error(),
|
||||
dataKeyApp: application,
|
||||
dataKeyError: "Invalid repository URL: " + repoURLErr.Error(),
|
||||
}, request)
|
||||
h.renderTemplate(writer, tmpl, "app_edit.html", data)
|
||||
|
||||
@@ -264,8 +289,8 @@ func (h *Handlers) HandleAppUpdate() http.HandlerFunc { //nolint:funlen // valid
|
||||
limitsErr := applyResourceLimits(application, request)
|
||||
if limitsErr != "" {
|
||||
data := h.addGlobals(map[string]any{
|
||||
"App": application,
|
||||
"Error": limitsErr,
|
||||
dataKeyApp: application,
|
||||
dataKeyError: limitsErr,
|
||||
}, request)
|
||||
h.renderTemplate(writer, tmpl, "app_edit.html", data)
|
||||
|
||||
@@ -277,16 +302,15 @@ func (h *Handlers) HandleAppUpdate() http.HandlerFunc { //nolint:funlen // valid
|
||||
h.log.Error("failed to update app", "error", saveErr)
|
||||
|
||||
data := h.addGlobals(map[string]any{
|
||||
"App": application,
|
||||
"Error": "Failed to update app",
|
||||
dataKeyApp: application,
|
||||
dataKeyError: "Failed to update app",
|
||||
}, request)
|
||||
h.renderTemplate(writer, tmpl, "app_edit.html", data)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
redirectURL := "/apps/" + application.ID + "?success=updated"
|
||||
http.Redirect(writer, request, redirectURL, http.StatusSeeOther)
|
||||
redirectToApp(writer, request, application.ID, "?success=updated")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -371,12 +395,7 @@ func (h *Handlers) HandleAppDeploy() http.HandlerFunc {
|
||||
}
|
||||
}(deployCtx, application)
|
||||
|
||||
http.Redirect(
|
||||
writer,
|
||||
request,
|
||||
"/apps/"+application.ID+"/deployments",
|
||||
http.StatusSeeOther,
|
||||
)
|
||||
redirectToApp(writer, request, application.ID, "/deployments")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -397,12 +416,7 @@ func (h *Handlers) HandleCancelDeploy() http.HandlerFunc {
|
||||
h.log.Info("deployment cancelled by user", "app", application.Name)
|
||||
}
|
||||
|
||||
http.Redirect(
|
||||
writer,
|
||||
request,
|
||||
"/apps/"+application.ID,
|
||||
http.StatusSeeOther,
|
||||
)
|
||||
redirectToApp(writer, request, application.ID, "")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -421,12 +435,12 @@ func (h *Handlers) HandleAppRollback() http.HandlerFunc {
|
||||
rollbackErr := h.deploy.Rollback(request.Context(), application)
|
||||
if rollbackErr != nil {
|
||||
h.log.Error("rollback failed", "error", rollbackErr, "app", application.Name)
|
||||
http.Redirect(writer, request, "/apps/"+application.ID, http.StatusSeeOther)
|
||||
redirectToApp(writer, request, application.ID, "")
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
http.Redirect(writer, request, "/apps/"+application.ID+"?success=rolledback", http.StatusSeeOther)
|
||||
redirectToApp(writer, request, application.ID, "?success=rolledback")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -450,7 +464,7 @@ func (h *Handlers) HandleAppDeployments() http.HandlerFunc {
|
||||
)
|
||||
|
||||
data := h.addGlobals(map[string]any{
|
||||
"App": application,
|
||||
dataKeyApp: application,
|
||||
"Deployments": deployments,
|
||||
}, request)
|
||||
|
||||
@@ -523,7 +537,7 @@ func (h *Handlers) HandleAppLogs() http.HandlerFunc {
|
||||
return
|
||||
}
|
||||
|
||||
_, _ = writer.Write([]byte(SanitizeLogs(logs))) // #nosec G705 -- logs sanitized, Content-Type is text/plain
|
||||
_, _ = writer.Write([]byte(SanitizeLogs(logs))) // #nosec G705 -- output sanitized
|
||||
}
|
||||
}
|
||||
|
||||
@@ -562,8 +576,8 @@ func (h *Handlers) HandleDeploymentLogsAPI() http.HandlerFunc {
|
||||
}
|
||||
|
||||
response := map[string]any{
|
||||
"logs": logs,
|
||||
"status": deployment.Status,
|
||||
jsonKeyLogs: logs,
|
||||
jsonKeyStatus: deployment.Status,
|
||||
}
|
||||
|
||||
_ = json.NewEncoder(writer).Encode(response)
|
||||
@@ -606,7 +620,7 @@ func (h *Handlers) HandleDeploymentLogDownload() http.HandlerFunc {
|
||||
}
|
||||
|
||||
// Check if file exists — logPath is constructed internally, not from user input
|
||||
_, err := os.Stat(logPath) // #nosec G703 -- path from internal GetLogFilePath, not user input
|
||||
_, err := os.Stat(logPath) // #nosec G703 -- internal path, not user input
|
||||
if os.IsNotExist(err) {
|
||||
http.NotFound(writer, request)
|
||||
|
||||
@@ -626,7 +640,7 @@ func (h *Handlers) HandleDeploymentLogDownload() http.HandlerFunc {
|
||||
writer.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
||||
writer.Header().Set("Content-Disposition", "attachment; filename=\""+filename+"\"")
|
||||
|
||||
http.ServeFile(writer, request, logPath)
|
||||
http.ServeFile(writer, request, logPath) // #nosec G703 -- internal path
|
||||
}
|
||||
}
|
||||
|
||||
@@ -650,8 +664,8 @@ func (h *Handlers) HandleContainerLogsAPI() http.HandlerFunc {
|
||||
containerInfo, containerErr := h.docker.FindContainerByAppID(request.Context(), appID)
|
||||
if containerErr != nil || containerInfo == nil {
|
||||
response := map[string]any{
|
||||
"logs": "No container running\n",
|
||||
"status": "stopped",
|
||||
jsonKeyLogs: "No container running\n",
|
||||
jsonKeyStatus: "stopped",
|
||||
}
|
||||
_ = json.NewEncoder(writer).Encode(response)
|
||||
|
||||
@@ -671,8 +685,8 @@ func (h *Handlers) HandleContainerLogsAPI() http.HandlerFunc {
|
||||
)
|
||||
|
||||
response := map[string]any{
|
||||
"logs": "Failed to fetch container logs\n",
|
||||
"status": "error",
|
||||
jsonKeyLogs: "Failed to fetch container logs\n",
|
||||
jsonKeyStatus: "error",
|
||||
}
|
||||
_ = json.NewEncoder(writer).Encode(response)
|
||||
|
||||
@@ -685,8 +699,8 @@ func (h *Handlers) HandleContainerLogsAPI() http.HandlerFunc {
|
||||
}
|
||||
|
||||
response := map[string]any{
|
||||
"logs": SanitizeLogs(logs),
|
||||
"status": status,
|
||||
jsonKeyLogs: SanitizeLogs(logs),
|
||||
jsonKeyStatus: status,
|
||||
}
|
||||
|
||||
_ = json.NewEncoder(writer).Encode(response)
|
||||
@@ -720,7 +734,7 @@ func (h *Handlers) HandleAppStatusAPI() http.HandlerFunc {
|
||||
}
|
||||
|
||||
response := map[string]any{
|
||||
"status": string(application.Status),
|
||||
jsonKeyStatus: string(application.Status),
|
||||
"latestDeploymentID": latestDeploymentID,
|
||||
"latestDeploymentStatus": latestDeploymentStatus,
|
||||
}
|
||||
@@ -757,7 +771,7 @@ func (h *Handlers) HandleRecentDeploymentsAPI() http.HandlerFunc {
|
||||
for _, d := range deployments {
|
||||
deploymentsData = append(deploymentsData, map[string]any{
|
||||
"id": d.ID,
|
||||
"status": string(d.Status),
|
||||
jsonKeyStatus: string(d.Status),
|
||||
"duration": d.Duration(),
|
||||
"shortCommit": d.ShortCommit(),
|
||||
"finishedAtISO": d.FinishedAtISO(),
|
||||
@@ -799,7 +813,7 @@ func (h *Handlers) handleContainerAction(
|
||||
|
||||
containerInfo, containerErr := h.docker.FindContainerByAppID(ctx, appID)
|
||||
if containerErr != nil || containerInfo == nil {
|
||||
http.Redirect(writer, request, "/apps/"+appID, http.StatusSeeOther)
|
||||
redirectToApp(writer, request, appID, "")
|
||||
|
||||
return
|
||||
}
|
||||
@@ -832,7 +846,7 @@ func (h *Handlers) handleContainerAction(
|
||||
"action", action, "app", application.Name, "container", containerID)
|
||||
}
|
||||
|
||||
http.Redirect(writer, request, "/apps/"+appID, http.StatusSeeOther)
|
||||
redirectToApp(writer, request, appID, "")
|
||||
}
|
||||
|
||||
// HandleAppRestart handles restarting an app's container.
|
||||
@@ -886,7 +900,7 @@ func (h *Handlers) addKeyValueToApp(
|
||||
value := request.FormValue("value")
|
||||
|
||||
if key == "" || value == "" {
|
||||
http.Redirect(writer, request, "/apps/"+application.ID, http.StatusSeeOther)
|
||||
redirectToApp(writer, request, application.ID, "")
|
||||
|
||||
return
|
||||
}
|
||||
@@ -896,7 +910,7 @@ func (h *Handlers) addKeyValueToApp(
|
||||
h.log.Error("failed to add key-value pair", "error", saveErr)
|
||||
}
|
||||
|
||||
http.Redirect(writer, request, "/apps/"+application.ID, http.StatusSeeOther)
|
||||
redirectToApp(writer, request, application.ID, "")
|
||||
}
|
||||
|
||||
// envPairJSON represents a key-value pair in the JSON request body.
|
||||
@@ -957,7 +971,7 @@ func (h *Handlers) HandleEnvVarSave() http.HandlerFunc {
|
||||
decodeErr := json.NewDecoder(request.Body).Decode(&pairs)
|
||||
if decodeErr != nil {
|
||||
h.respondJSON(writer, request, map[string]string{
|
||||
"error": "invalid request body",
|
||||
jsonKeyError: "invalid request body",
|
||||
}, http.StatusBadRequest)
|
||||
|
||||
return
|
||||
@@ -966,7 +980,7 @@ func (h *Handlers) HandleEnvVarSave() http.HandlerFunc {
|
||||
modelPairs, validationErr := validateEnvPairs(pairs)
|
||||
if validationErr != "" {
|
||||
h.respondJSON(writer, request, map[string]string{
|
||||
"error": validationErr,
|
||||
jsonKeyError: validationErr,
|
||||
}, http.StatusBadRequest)
|
||||
|
||||
return
|
||||
@@ -978,7 +992,7 @@ func (h *Handlers) HandleEnvVarSave() http.HandlerFunc {
|
||||
if replaceErr != nil {
|
||||
h.log.Error("failed to replace env vars", "error", replaceErr)
|
||||
h.respondJSON(writer, request, map[string]string{
|
||||
"error": "failed to save environment variables",
|
||||
jsonKeyError: "failed to save environment variables",
|
||||
}, http.StatusInternalServerError)
|
||||
|
||||
return
|
||||
@@ -1006,32 +1020,77 @@ func (h *Handlers) HandleLabelAdd() http.HandlerFunc {
|
||||
}
|
||||
}
|
||||
|
||||
// deleteAppResource handles deletion of an app-owned resource (label,
|
||||
// volume, or port) identified by an int64 URL parameter. The
|
||||
// deleteByID closure reports whether the resource was found to belong
|
||||
// to the app, and returns the deletion error if one occurred.
|
||||
func (h *Handlers) deleteAppResource(
|
||||
writer http.ResponseWriter,
|
||||
request *http.Request,
|
||||
idParam, logName string,
|
||||
deleteByID deleteByIDFunc,
|
||||
) {
|
||||
appID := chi.URLParam(request, "id")
|
||||
idStr := chi.URLParam(request, idParam)
|
||||
|
||||
id, parseErr := strconv.ParseInt(idStr, 10, 64)
|
||||
if parseErr != nil {
|
||||
http.NotFound(writer, request)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
found, deleteErr := deleteByID(request.Context(), appID, id)
|
||||
if !found {
|
||||
http.NotFound(writer, request)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if deleteErr != nil {
|
||||
h.log.Error("failed to delete "+logName, "error", deleteErr)
|
||||
}
|
||||
|
||||
redirectToApp(writer, request, appID, "")
|
||||
}
|
||||
|
||||
// deleteByIDFunc looks up an app-owned resource by ID and deletes it
|
||||
// when it belongs to the given app. It reports whether the resource
|
||||
// was found, and returns lookup or deletion errors.
|
||||
type deleteByIDFunc func(ctx context.Context, appID string, id int64) (bool, error)
|
||||
|
||||
// makeDeleteByID builds a deleteByIDFunc from a model's find
|
||||
// function, its app-ID accessor, and its delete method.
|
||||
func makeDeleteByID[T any](
|
||||
db *database.Database,
|
||||
find func(context.Context, *database.Database, int64) (*T, error),
|
||||
appIDOf func(*T) string,
|
||||
del func(*T, context.Context) error,
|
||||
) deleteByIDFunc {
|
||||
return func(ctx context.Context, appID string, id int64) (bool, error) {
|
||||
resource, findErr := find(ctx, db, id)
|
||||
if findErr != nil {
|
||||
return false, findErr
|
||||
}
|
||||
|
||||
if resource == nil || appIDOf(resource) != appID {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
return true, del(resource, ctx)
|
||||
}
|
||||
}
|
||||
|
||||
// HandleLabelDelete handles deleting a label.
|
||||
func (h *Handlers) HandleLabelDelete() http.HandlerFunc {
|
||||
return func(writer http.ResponseWriter, request *http.Request) {
|
||||
appID := chi.URLParam(request, "id")
|
||||
labelIDStr := chi.URLParam(request, "labelID")
|
||||
|
||||
labelID, parseErr := strconv.ParseInt(labelIDStr, 10, 64)
|
||||
if parseErr != nil {
|
||||
http.NotFound(writer, request)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
label, findErr := models.FindLabel(request.Context(), h.db, labelID)
|
||||
if findErr != nil || label == nil || label.AppID != appID {
|
||||
http.NotFound(writer, request)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
deleteErr := label.Delete(request.Context())
|
||||
if deleteErr != nil {
|
||||
h.log.Error("failed to delete label", "error", deleteErr)
|
||||
}
|
||||
|
||||
http.Redirect(writer, request, "/apps/"+appID, http.StatusSeeOther)
|
||||
h.deleteAppResource(
|
||||
writer, request, "labelID", "label",
|
||||
makeDeleteByID(h.db, models.FindLabel,
|
||||
func(l *models.Label) string { return l.AppID },
|
||||
(*models.Label).Delete,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1059,12 +1118,7 @@ func (h *Handlers) HandleVolumeAdd() http.HandlerFunc {
|
||||
readOnly := request.FormValue("readonly") == "1"
|
||||
|
||||
if hostPath == "" || containerPath == "" {
|
||||
http.Redirect(
|
||||
writer,
|
||||
request,
|
||||
"/apps/"+application.ID,
|
||||
http.StatusSeeOther,
|
||||
)
|
||||
redirectToApp(writer, request, application.ID, "")
|
||||
|
||||
return
|
||||
}
|
||||
@@ -1072,7 +1126,7 @@ func (h *Handlers) HandleVolumeAdd() http.HandlerFunc {
|
||||
pathErr := validateVolumePaths(hostPath, containerPath)
|
||||
if pathErr != nil {
|
||||
h.log.Error("invalid volume path", "error", pathErr)
|
||||
http.Redirect(writer, request, "/apps/"+application.ID, http.StatusSeeOther)
|
||||
redirectToApp(writer, request, application.ID, "")
|
||||
|
||||
return
|
||||
}
|
||||
@@ -1088,36 +1142,20 @@ func (h *Handlers) HandleVolumeAdd() http.HandlerFunc {
|
||||
h.log.Error("failed to add volume", "error", saveErr)
|
||||
}
|
||||
|
||||
http.Redirect(writer, request, "/apps/"+application.ID, http.StatusSeeOther)
|
||||
redirectToApp(writer, request, application.ID, "")
|
||||
}
|
||||
}
|
||||
|
||||
// HandleVolumeDelete handles deleting a volume mount.
|
||||
func (h *Handlers) HandleVolumeDelete() http.HandlerFunc {
|
||||
return func(writer http.ResponseWriter, request *http.Request) {
|
||||
appID := chi.URLParam(request, "id")
|
||||
volumeIDStr := chi.URLParam(request, "volumeID")
|
||||
|
||||
volumeID, parseErr := strconv.ParseInt(volumeIDStr, 10, 64)
|
||||
if parseErr != nil {
|
||||
http.NotFound(writer, request)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
volume, findErr := models.FindVolume(request.Context(), h.db, volumeID)
|
||||
if findErr != nil || volume == nil || volume.AppID != appID {
|
||||
http.NotFound(writer, request)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
deleteErr := volume.Delete(request.Context())
|
||||
if deleteErr != nil {
|
||||
h.log.Error("failed to delete volume", "error", deleteErr)
|
||||
}
|
||||
|
||||
http.Redirect(writer, request, "/apps/"+appID, http.StatusSeeOther)
|
||||
h.deleteAppResource(
|
||||
writer, request, "volumeID", "volume",
|
||||
makeDeleteByID(h.db, models.FindVolume,
|
||||
func(v *models.Volume) string { return v.AppID },
|
||||
(*models.Volume).Delete,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1145,7 +1183,7 @@ func (h *Handlers) HandlePortAdd() http.HandlerFunc {
|
||||
request.FormValue("container_port"),
|
||||
)
|
||||
if !valid {
|
||||
http.Redirect(writer, request, "/apps/"+application.ID, http.StatusSeeOther)
|
||||
redirectToApp(writer, request, application.ID, "")
|
||||
|
||||
return
|
||||
}
|
||||
@@ -1166,7 +1204,7 @@ func (h *Handlers) HandlePortAdd() http.HandlerFunc {
|
||||
h.log.Error("failed to save port", "error", saveErr)
|
||||
}
|
||||
|
||||
http.Redirect(writer, request, "/apps/"+application.ID, http.StatusSeeOther)
|
||||
redirectToApp(writer, request, application.ID, "")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1190,29 +1228,13 @@ func parsePortValues(hostPortStr, containerPortStr string) (int, int, bool) {
|
||||
// HandlePortDelete handles deleting a port mapping.
|
||||
func (h *Handlers) HandlePortDelete() http.HandlerFunc {
|
||||
return func(writer http.ResponseWriter, request *http.Request) {
|
||||
appID := chi.URLParam(request, "id")
|
||||
portIDStr := chi.URLParam(request, "portID")
|
||||
|
||||
portID, parseErr := strconv.ParseInt(portIDStr, 10, 64)
|
||||
if parseErr != nil {
|
||||
http.NotFound(writer, request)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
port, findErr := models.FindPort(request.Context(), h.db, portID)
|
||||
if findErr != nil || port == nil || port.AppID != appID {
|
||||
http.NotFound(writer, request)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
deleteErr := port.Delete(request.Context())
|
||||
if deleteErr != nil {
|
||||
h.log.Error("failed to delete port", "error", deleteErr)
|
||||
}
|
||||
|
||||
http.Redirect(writer, request, "/apps/"+appID, http.StatusSeeOther)
|
||||
h.deleteAppResource(
|
||||
writer, request, "portID", "port",
|
||||
makeDeleteByID(h.db, models.FindPort,
|
||||
func(p *models.Port) string { return p.AppID },
|
||||
(*models.Port).Delete,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1274,7 +1296,7 @@ func (h *Handlers) HandleLabelEdit() http.HandlerFunc {
|
||||
value := request.FormValue("value")
|
||||
|
||||
if key == "" || value == "" {
|
||||
http.Redirect(writer, request, "/apps/"+appID, http.StatusSeeOther)
|
||||
redirectToApp(writer, request, appID, "")
|
||||
|
||||
return
|
||||
}
|
||||
@@ -1287,7 +1309,7 @@ func (h *Handlers) HandleLabelEdit() http.HandlerFunc {
|
||||
h.log.Error("failed to update label", "error", saveErr)
|
||||
}
|
||||
|
||||
http.Redirect(writer, request, "/apps/"+appID, http.StatusSeeOther)
|
||||
redirectToApp(writer, request, appID, "")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1323,7 +1345,7 @@ func (h *Handlers) HandleVolumeEdit() http.HandlerFunc {
|
||||
readOnly := request.FormValue("readonly") == "1"
|
||||
|
||||
if hostPath == "" || containerPath == "" {
|
||||
http.Redirect(writer, request, "/apps/"+appID, http.StatusSeeOther)
|
||||
redirectToApp(writer, request, appID, "")
|
||||
|
||||
return
|
||||
}
|
||||
@@ -1331,7 +1353,7 @@ func (h *Handlers) HandleVolumeEdit() http.HandlerFunc {
|
||||
pathErr := validateVolumePaths(hostPath, containerPath)
|
||||
if pathErr != nil {
|
||||
h.log.Error("invalid volume path", "error", pathErr)
|
||||
http.Redirect(writer, request, "/apps/"+appID, http.StatusSeeOther)
|
||||
redirectToApp(writer, request, appID, "")
|
||||
|
||||
return
|
||||
}
|
||||
@@ -1345,7 +1367,7 @@ func (h *Handlers) HandleVolumeEdit() http.HandlerFunc {
|
||||
h.log.Error("failed to update volume", "error", saveErr)
|
||||
}
|
||||
|
||||
http.Redirect(writer, request, "/apps/"+appID, http.StatusSeeOther)
|
||||
redirectToApp(writer, request, appID, "")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1389,8 +1411,9 @@ func optionalNullString(s string) sql.NullString {
|
||||
return sql.NullString{}
|
||||
}
|
||||
|
||||
// applyResourceLimits parses CPU and memory limit form values and applies them to the app.
|
||||
// Returns an error message string if validation fails, or empty string on success.
|
||||
// applyResourceLimits parses CPU and memory limit form values and
|
||||
// applies them to the app. Returns an error message string if
|
||||
// validation fails, or empty string on success.
|
||||
func applyResourceLimits(application *models.App, request *http.Request) string {
|
||||
cpuLimit, cpuErr := parseOptionalFloat64(request.FormValue("cpu_limit"))
|
||||
if cpuErr != nil {
|
||||
@@ -1425,7 +1448,8 @@ func memoryUnitMultiplier(suffix byte) int64 {
|
||||
}
|
||||
|
||||
// parseOptionalFloat64 parses an optional float64 form field.
|
||||
// Returns a valid NullFloat64 if the string is non-empty and parses to a positive number.
|
||||
// Returns a valid NullFloat64 if the string is non-empty and parses
|
||||
// to a positive number.
|
||||
// Returns an empty NullFloat64 if the string is empty.
|
||||
// Returns an error if the string is non-empty but invalid or non-positive.
|
||||
func parseOptionalFloat64(s string) (sql.NullFloat64, error) {
|
||||
@@ -1447,7 +1471,8 @@ func parseOptionalFloat64(s string) (sql.NullFloat64, error) {
|
||||
}
|
||||
|
||||
// parseOptionalMemoryBytes parses an optional memory limit string into bytes.
|
||||
// Accepts plain bytes (e.g. "536870912") or suffixed values (e.g. "512m", "1g", "256k").
|
||||
// Accepts plain bytes (e.g. "536870912") or suffixed values
|
||||
// (e.g. "512m", "1g", "256k").
|
||||
// Returns a valid NullInt64 with bytes if non-empty, empty NullInt64 if blank.
|
||||
func parseOptionalMemoryBytes(s string) (sql.NullInt64, error) {
|
||||
s = strings.TrimSpace(s)
|
||||
|
||||
Reference in New Issue
Block a user