feat(stacks): use source id to create git stacks [BE-13043] (#2870)

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Chaim Lev-Ari
2026-06-15 18:49:26 +03:00
committed by GitHub
parent 04048c3818
commit fcdd6b4510
50 changed files with 1383 additions and 504 deletions
+66 -42
View File
@@ -22,17 +22,25 @@ import (
)
type stackGitUpdatePayload struct {
AutoUpdate *portainer.AutoUpdateSettings
Env []portainer.Pair
Prune bool
RepositoryURL string
ConfigFilePath string
AdditionalFiles []string
RepositoryReferenceName string
AutoUpdate *portainer.AutoUpdateSettings
Env []portainer.Pair
Prune bool
ConfigFilePath string
AdditionalFiles []string
RepositoryReferenceName string
// SourceID references an existing Source for git credentials/URL.
// When set, the inline URL and authentication fields are ignored.
SourceID portainer.SourceID
// Deprecated: use SourceID instead. URL of a Git repository hosting the Stack file.
RepositoryURL string
// Deprecated: use SourceID instead. Use basic authentication to clone the Git repository.
RepositoryAuthentication bool
RepositoryUsername string
RepositoryPassword string
TLSSkipVerify bool
// Deprecated: use SourceID instead. Username used in basic authentication.
RepositoryUsername string
// Deprecated: use SourceID instead. Password used in basic authentication.
RepositoryPassword string
// Deprecated: use SourceID instead. Skip TLS verification when cloning the Git repository.
TLSSkipVerify bool
}
func (payload *stackGitUpdatePayload) Validate(r *http.Request) error {
@@ -41,7 +49,7 @@ func (payload *stackGitUpdatePayload) Validate(r *http.Request) error {
// @id StackUpdateGit
// @summary Update a stack's Git configs
// @description Update the Git settings in a stack, e.g., RepositoryReferenceName and AutoUpdate
// @description Update the Git settings in a stack, e.g., RepositoryReferenceName and AutoUpdate. When SourceID is set, URL/auth/TLS are taken from the referenced Source.
// @description **Access policy**: authenticated
// @tags stacks
// @security ApiKeyAuth
@@ -51,7 +59,7 @@ func (payload *stackGitUpdatePayload) Validate(r *http.Request) error {
// @param id path int true "Stack identifier"
// @param endpointId query int false "Stacks created before version 1.18.0 might not have an associated environment(endpoint) identifier. Use this optional parameter to set the environment(endpoint) identifier used by the stack."
// @param body body stackGitUpdatePayload true "Git configs for pull and redeploy a stack"
// @success 200 {object} portainer.Stack "Success"
// @success 200 {object} stackResponse "Success"
// @failure 400 "Invalid request"
// @failure 403 "Permission denied"
// @failure 404 "Not found"
@@ -152,6 +160,7 @@ func (handler *Handler) stackUpdateGit(w http.ResponseWriter, r *http.Request) *
deployments.StopAutoupdate(stack.ID, stack.AutoUpdate.JobID, handler.Scheduler)
}
// Record the current git config as the deployment baseline if it was never set (legacy stacks).
if stack.CurrentDeploymentInfo == nil {
stack.CurrentDeploymentInfo = &portainer.StackDeploymentInfo{
RepositoryURL: gitConfig.URL,
@@ -159,15 +168,12 @@ func (handler *Handler) stackUpdateGit(w http.ResponseWriter, r *http.Request) *
ConfigFilePath: gitConfig.ConfigFilePath,
AdditionalFiles: stack.AdditionalFiles,
ConfigHash: gitConfig.ConfigHash,
SourceID: sourceID,
}
}
// Update gitConfig based on payload; the updated config is saved to Source (not stack.GitConfig).
gitConfig.ReferenceName = payload.RepositoryReferenceName
gitConfig.TLSSkipVerify = payload.TLSSkipVerify
if payload.RepositoryURL != "" {
gitConfig.URL = payload.RepositoryURL
}
if payload.ConfigFilePath != "" {
gitConfig.ConfigFilePath = payload.ConfigFilePath
}
@@ -186,32 +192,48 @@ func (handler *Handler) stackUpdateGit(w http.ResponseWriter, r *http.Request) *
stack.Option = &portainer.StackOption{Prune: payload.Prune}
}
if payload.RepositoryAuthentication {
password := payload.RepositoryPassword
// When the existing stack is using the custom username/password and the password is not updated,
// the stack should keep using the saved username/password
if password == "" && gitConfig.Authentication != nil {
password = gitConfig.Authentication.Password
if payload.SourceID != 0 {
src, httpErr := validateSourceForStack(handler.DataStore, payload.SourceID)
if httpErr != nil {
return httpErr
}
gitConfig.Authentication = &gittypes.GitAuthentication{
Username: payload.RepositoryUsername,
Password: password,
}
if _, err := handler.GitService.LatestCommitID(
context.TODO(),
gitConfig.URL,
gitConfig.ReferenceName,
gitConfig.Authentication.Username,
gitConfig.Authentication.Password,
gitConfig.TLSSkipVerify,
); err != nil {
return httperror.InternalServerError("Unable to fetch git repository", err)
if src.Git == nil {
return httperror.BadRequest("Source has no git configuration", errors.New("source has no git config"))
}
} else {
gitConfig.Authentication = nil
gitConfig.TLSSkipVerify = payload.TLSSkipVerify
if payload.RepositoryURL != "" {
gitConfig.URL = payload.RepositoryURL
}
if payload.RepositoryAuthentication {
password := payload.RepositoryPassword
// When the existing stack is using the custom username/password and the password is not updated,
// the stack should keep using the saved username/password
if password == "" && gitConfig.Authentication != nil {
password = gitConfig.Authentication.Password
}
gitConfig.Authentication = &gittypes.GitAuthentication{
Username: payload.RepositoryUsername,
Password: password,
}
if _, err := handler.GitService.LatestCommitID(
context.TODO(),
gitConfig.URL,
gitConfig.ReferenceName,
gitConfig.Authentication.Username,
gitConfig.Authentication.Password,
gitConfig.TLSSkipVerify,
); err != nil {
return httperror.InternalServerError("Unable to fetch git repository", err)
}
} else {
gitConfig.Authentication = nil
}
}
if payload.AutoUpdate != nil && payload.AutoUpdate.Interval != "" {
@@ -222,18 +244,20 @@ func (handler *Handler) stackUpdateGit(w http.ResponseWriter, r *http.Request) *
}
}
// Save the updated stack and git config to DB.
var resp *stackResponse
if err := handler.DataStore.UpdateTx(func(tx dataservices.DataStoreTx) error {
if err := tx.Stack().Update(stack.ID, stack); err != nil {
return err
}
if err := saveStackGitConfig(tx, stack.WorkflowID, stack.ID, sourceID, gitConfig); err != nil {
if err := saveStackGitConfig(tx, stack.WorkflowID, stack.ID, sourceID, payload.SourceID, gitConfig); err != nil {
return err
}
return fillStackGitConfig(tx, stack)
var err error
resp, err = newStackResponse(tx, stack)
return err
}); err != nil {
return httperror.InternalServerError("Unable to persist the stack changes inside the database", err)
}
return response.JSON(w, stack)
return response.JSON(w, resp)
}