feat: add private Docker registry authentication for base images
All checks were successful
Check / check (pull_request) Successful in 3m34s
All checks were successful
Check / check (pull_request) Successful in 3m34s
Add per-app registry credentials that are passed to Docker during image builds, allowing apps to use base images from private registries. - New registry_credentials table (migration 007) - RegistryCredential model with full CRUD operations - Docker client passes AuthConfigs to ImageBuild when credentials exist - Deploy service fetches app registry credentials before builds - Web UI section for managing registry credentials (add/edit/delete) - Comprehensive unit tests for model and auth config builder - README updated to list the feature
This commit is contained in:
@@ -148,6 +148,7 @@ func (h *Handlers) HandleAppDetail() http.HandlerFunc {
|
||||
labels, _ := application.GetLabels(request.Context())
|
||||
volumes, _ := application.GetVolumes(request.Context())
|
||||
ports, _ := application.GetPorts(request.Context())
|
||||
registryCreds, _ := application.GetRegistryCredentials(request.Context())
|
||||
deployments, _ := application.GetDeployments(
|
||||
request.Context(),
|
||||
recentDeploymentsLimit,
|
||||
@@ -163,16 +164,17 @@ func (h *Handlers) HandleAppDetail() http.HandlerFunc {
|
||||
deployKey := formatDeployKey(application.SSHPublicKey, application.CreatedAt, application.Name)
|
||||
|
||||
data := h.addGlobals(map[string]any{
|
||||
"App": application,
|
||||
"EnvVars": envVars,
|
||||
"Labels": labels,
|
||||
"Volumes": volumes,
|
||||
"Ports": ports,
|
||||
"Deployments": deployments,
|
||||
"LatestDeployment": latestDeployment,
|
||||
"WebhookURL": webhookURL,
|
||||
"DeployKey": deployKey,
|
||||
"Success": request.URL.Query().Get("success"),
|
||||
"App": application,
|
||||
"EnvVars": envVars,
|
||||
"Labels": labels,
|
||||
"Volumes": volumes,
|
||||
"Ports": ports,
|
||||
"RegistryCredentials": registryCreds,
|
||||
"Deployments": deployments,
|
||||
"LatestDeployment": latestDeployment,
|
||||
"WebhookURL": webhookURL,
|
||||
"DeployKey": deployKey,
|
||||
"Success": request.URL.Query().Get("success"),
|
||||
}, request)
|
||||
|
||||
h.renderTemplate(writer, tmpl, "app_detail.html", data)
|
||||
@@ -1382,3 +1384,126 @@ func formatDeployKey(pubKey string, createdAt time.Time, appName string) string
|
||||
|
||||
return parts[0] + " " + parts[1] + " " + comment
|
||||
}
|
||||
|
||||
// HandleRegistryCredentialAdd handles adding a registry credential.
|
||||
func (h *Handlers) HandleRegistryCredentialAdd() http.HandlerFunc {
|
||||
return func(writer http.ResponseWriter, request *http.Request) {
|
||||
appID := chi.URLParam(request, "id")
|
||||
|
||||
application, findErr := models.FindApp(request.Context(), h.db, appID)
|
||||
if findErr != nil || application == nil {
|
||||
http.NotFound(writer, request)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
parseErr := request.ParseForm()
|
||||
if parseErr != nil {
|
||||
http.Error(writer, "Bad Request", http.StatusBadRequest)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
registryURL := strings.TrimSpace(request.FormValue("registry"))
|
||||
username := strings.TrimSpace(request.FormValue("username"))
|
||||
password := request.FormValue("password")
|
||||
|
||||
if registryURL == "" || username == "" || password == "" {
|
||||
http.Redirect(writer, request, "/apps/"+appID, http.StatusSeeOther)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
cred := models.NewRegistryCredential(h.db)
|
||||
cred.AppID = appID
|
||||
cred.Registry = registryURL
|
||||
cred.Username = username
|
||||
cred.Password = password
|
||||
|
||||
saveErr := cred.Save(request.Context())
|
||||
if saveErr != nil {
|
||||
h.log.Error("failed to save registry credential", "error", saveErr)
|
||||
}
|
||||
|
||||
http.Redirect(writer, request, "/apps/"+appID, http.StatusSeeOther)
|
||||
}
|
||||
}
|
||||
|
||||
// HandleRegistryCredentialEdit handles editing an existing registry credential.
|
||||
func (h *Handlers) HandleRegistryCredentialEdit() http.HandlerFunc {
|
||||
return func(writer http.ResponseWriter, request *http.Request) {
|
||||
appID := chi.URLParam(request, "id")
|
||||
credIDStr := chi.URLParam(request, "credID")
|
||||
|
||||
credID, parseErr := strconv.ParseInt(credIDStr, 10, 64)
|
||||
if parseErr != nil {
|
||||
http.NotFound(writer, request)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
cred, findErr := models.FindRegistryCredential(request.Context(), h.db, credID)
|
||||
if findErr != nil || cred == nil || cred.AppID != appID {
|
||||
http.NotFound(writer, request)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
formErr := request.ParseForm()
|
||||
if formErr != nil {
|
||||
http.Error(writer, "Bad Request", http.StatusBadRequest)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
registryURL := strings.TrimSpace(request.FormValue("registry"))
|
||||
username := strings.TrimSpace(request.FormValue("username"))
|
||||
password := request.FormValue("password")
|
||||
|
||||
if registryURL == "" || username == "" || password == "" {
|
||||
http.Redirect(writer, request, "/apps/"+appID, http.StatusSeeOther)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
cred.Registry = registryURL
|
||||
cred.Username = username
|
||||
cred.Password = password
|
||||
|
||||
saveErr := cred.Save(request.Context())
|
||||
if saveErr != nil {
|
||||
h.log.Error("failed to update registry credential", "error", saveErr)
|
||||
}
|
||||
|
||||
http.Redirect(writer, request, "/apps/"+appID, http.StatusSeeOther)
|
||||
}
|
||||
}
|
||||
|
||||
// HandleRegistryCredentialDelete handles deleting a registry credential.
|
||||
func (h *Handlers) HandleRegistryCredentialDelete() http.HandlerFunc {
|
||||
return func(writer http.ResponseWriter, request *http.Request) {
|
||||
appID := chi.URLParam(request, "id")
|
||||
credIDStr := chi.URLParam(request, "credID")
|
||||
|
||||
credID, parseErr := strconv.ParseInt(credIDStr, 10, 64)
|
||||
if parseErr != nil {
|
||||
http.NotFound(writer, request)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
cred, findErr := models.FindRegistryCredential(request.Context(), h.db, credID)
|
||||
if findErr != nil || cred == nil || cred.AppID != appID {
|
||||
http.NotFound(writer, request)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
deleteErr := cred.Delete(request.Context())
|
||||
if deleteErr != nil {
|
||||
h.log.Error("failed to delete registry credential", "error", deleteErr)
|
||||
}
|
||||
|
||||
http.Redirect(writer, request, "/apps/"+appID, http.StatusSeeOther)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user