diff --git a/api/git/types/types.go b/api/git/types/types.go index df534c69e..d24283b52 100644 --- a/api/git/types/types.go +++ b/api/git/types/types.go @@ -10,7 +10,8 @@ type RepoConfig struct { ConfigFilePath string `example:"docker-compose.yml"` // Git credentials Authentication *GitAuthentication - ConfigHash string + // Repository hash + ConfigHash string `example:"bc4c183d756879ea4d173315338110b31004b8e0"` } type GitAuthentication struct { diff --git a/api/http/handler/stacks/autoupdate.go b/api/http/handler/stacks/autoupdate.go new file mode 100644 index 000000000..867e71fc2 --- /dev/null +++ b/api/http/handler/stacks/autoupdate.go @@ -0,0 +1,38 @@ +package stacks + +import ( + "log" + "net/http" + "time" + + httperror "github.com/portainer/libhttp/error" + portainer "github.com/portainer/portainer/api" + "github.com/portainer/portainer/api/scheduler" + "github.com/portainer/portainer/api/stacks" +) + +func startAutoupdate(stackID portainer.StackID, interval string, scheduler *scheduler.Scheduler, stackDeployer stacks.StackDeployer, datastore portainer.DataStore, gitService portainer.GitService) (jobID string, e *httperror.HandlerError) { + d, err := time.ParseDuration(interval) + if err != nil { + return "", &httperror.HandlerError{StatusCode: http.StatusBadRequest, Message: "Unable to parse stack's auto update interval", Err: err} + } + + jobID = scheduler.StartJobEvery(d, func() { + if err := stacks.RedeployWhenChanged(stackID, stackDeployer, datastore, gitService); err != nil { + log.Printf("[ERROR] [http,stacks] [message: failed redeploying] [err: %s]\n", err) + } + }) + + return jobID, nil +} + +func stopAutoupdate(stackID portainer.StackID, jobID string, scheduler scheduler.Scheduler) { + if jobID == "" { + return + } + + if err := scheduler.StopJob(jobID); err != nil { + log.Printf("[WARN] could not stop the job for the stack %v", stackID) + } + +} diff --git a/api/http/handler/stacks/create_compose_stack.go b/api/http/handler/stacks/create_compose_stack.go index 721fde2eb..4ca76967f 100644 --- a/api/http/handler/stacks/create_compose_stack.go +++ b/api/http/handler/stacks/create_compose_stack.go @@ -2,7 +2,6 @@ package stacks import ( "fmt" - "log" "net/http" "path" "strconv" @@ -16,7 +15,6 @@ import ( "github.com/portainer/portainer/api/filesystem" gittypes "github.com/portainer/portainer/api/git/types" "github.com/portainer/portainer/api/http/security" - "github.com/portainer/portainer/api/stacks" ) type composeStackFromFileContentPayload struct { @@ -116,8 +114,9 @@ type composeStackFromGitRepositoryPayload struct { // Path to the Stack file inside the Git repository ComposeFile string `example:"docker-compose.yml" default:"docker-compose.yml"` // Applicable when deploying with multiple stack files - AdditionalFiles []string - AutoUpdate *portainer.StackAutoUpdate + AdditionalFiles []string `example:"[nz.compose.yml, uat.compose.yml]"` + // Optional auto update configuration + AutoUpdate *portainer.StackAutoUpdate // A list of environment variables used during stack deployment Env []portainer.Pair } @@ -226,15 +225,10 @@ func (handler *Handler) createComposeStackFromGitRepository(w http.ResponseWrite } if payload.AutoUpdate != nil && payload.AutoUpdate.Interval != "" { - d, err := time.ParseDuration(payload.AutoUpdate.Interval) - if err != nil { - return &httperror.HandlerError{StatusCode: http.StatusBadRequest, Message: "Unable to parse auto update interval", Err: err} + jobID, e := startAutoupdate(stack.ID, stack.AutoUpdate.Interval, handler.Scheduler, handler.StackDeployer, handler.DataStore, handler.GitService) + if e != nil { + return e } - jobID := handler.Scheduler.StartJobEvery(d, func() { - if err := stacks.RedeployWhenChanged(stack.ID, handler.StackDeployer, handler.DataStore, handler.GitService); err != nil { - log.Printf("[ERROR] %s\n", err) - } - }) stack.AutoUpdate.JobID = jobID } diff --git a/api/http/handler/stacks/create_swarm_stack.go b/api/http/handler/stacks/create_swarm_stack.go index a785528ab..e76338384 100644 --- a/api/http/handler/stacks/create_swarm_stack.go +++ b/api/http/handler/stacks/create_swarm_stack.go @@ -3,7 +3,6 @@ package stacks import ( "errors" "fmt" - "log" "net/http" "path" "strconv" @@ -16,7 +15,6 @@ import ( "github.com/portainer/portainer/api/filesystem" gittypes "github.com/portainer/portainer/api/git/types" "github.com/portainer/portainer/api/http/security" - "github.com/portainer/portainer/api/stacks" ) type swarmStackFromFileContentPayload struct { @@ -124,9 +122,11 @@ type swarmStackFromGitRepositoryPayload struct { // Password used in basic authentication. Required when RepositoryAuthentication is true. RepositoryPassword string `example:"myGitPassword"` // Path to the Stack file inside the Git repository - ComposeFile string `example:"docker-compose.yml" default:"docker-compose.yml"` - AdditionalFiles []string - AutoUpdate *portainer.StackAutoUpdate + ComposeFile string `example:"docker-compose.yml" default:"docker-compose.yml"` + // Applicable when deploying with multiple stack files + AdditionalFiles []string `example:"[nz.compose.yml, uat.compose.yml]"` + // Optional auto update configuration + AutoUpdate *portainer.StackAutoUpdate } func (payload *swarmStackFromGitRepositoryPayload) Validate(r *http.Request) error { @@ -234,15 +234,10 @@ func (handler *Handler) createSwarmStackFromGitRepository(w http.ResponseWriter, } if payload.AutoUpdate != nil && payload.AutoUpdate.Interval != "" { - d, err := time.ParseDuration(payload.AutoUpdate.Interval) - if err != nil { - return &httperror.HandlerError{StatusCode: http.StatusBadRequest, Message: "Unable to parse auto update interval", Err: err} + jobID, e := startAutoupdate(stack.ID, stack.AutoUpdate.Interval, handler.Scheduler, handler.StackDeployer, handler.DataStore, handler.GitService) + if e != nil { + return e } - jobID := handler.Scheduler.StartJobEvery(d, func() { - if err := stacks.RedeployWhenChanged(stack.ID, handler.StackDeployer, handler.DataStore, handler.GitService); err != nil { - log.Printf("[ERROR] %s\n", err) - } - }) stack.AutoUpdate.JobID = jobID } diff --git a/api/http/handler/stacks/stack_delete.go b/api/http/handler/stacks/stack_delete.go index 251a9a0b2..f7ce69bf1 100644 --- a/api/http/handler/stacks/stack_delete.go +++ b/api/http/handler/stacks/stack_delete.go @@ -97,8 +97,8 @@ func (handler *Handler) stackDelete(w http.ResponseWriter, r *http.Request) *htt } // stop scheduler updates of the stack before removal - if stack.AutoUpdate != nil && stack.AutoUpdate.JobID != "" { - handler.Scheduler.StopJob(stack.AutoUpdate.JobID) + if stack.AutoUpdate != nil { + stopAutoupdate(stack.ID, stack.AutoUpdate.JobID, *handler.Scheduler) } err = handler.deleteStack(stack, endpoint) diff --git a/api/http/handler/stacks/stack_start.go b/api/http/handler/stacks/stack_start.go index a45869fcf..80c524e7a 100644 --- a/api/http/handler/stacks/stack_start.go +++ b/api/http/handler/stacks/stack_start.go @@ -3,15 +3,12 @@ package stacks import ( "errors" "fmt" - "log" "net/http" - "time" portainer "github.com/portainer/portainer/api" httperrors "github.com/portainer/portainer/api/http/errors" "github.com/portainer/portainer/api/http/security" "github.com/portainer/portainer/api/internal/stackutils" - "github.com/portainer/portainer/api/stacks" httperror "github.com/portainer/libhttp/error" "github.com/portainer/libhttp/request" @@ -89,21 +86,12 @@ func (handler *Handler) stackStart(w http.ResponseWriter, r *http.Request) *http } if stack.AutoUpdate != nil && stack.AutoUpdate.Interval != "" { - if stack.AutoUpdate.JobID != "" { - if err := handler.Scheduler.StopJob(stack.AutoUpdate.JobID); err != nil { - log.Printf("[WARN] could not stop the job for the stack %v", stack.ID) - } - } + stopAutoupdate(stack.ID, stack.AutoUpdate.JobID, *handler.Scheduler) - d, err := time.ParseDuration(stack.AutoUpdate.Interval) - if err != nil { - return &httperror.HandlerError{StatusCode: http.StatusBadRequest, Message: "Unable to parse stack's auto update interval", Err: err} + jobID, e := startAutoupdate(stack.ID, stack.AutoUpdate.Interval, handler.Scheduler, handler.StackDeployer, handler.DataStore, handler.GitService) + if e != nil { + return e } - jobID := handler.Scheduler.StartJobEvery(d, func() { - if err := stacks.RedeployWhenChanged(stack.ID, handler.StackDeployer, handler.DataStore, handler.GitService); err != nil { - log.Printf("[ERROR] %s\n", err) - } - }) stack.AutoUpdate.JobID = jobID } diff --git a/api/http/handler/stacks/stack_stop.go b/api/http/handler/stacks/stack_stop.go index 1e3f34bdd..4b5e3ccc8 100644 --- a/api/http/handler/stacks/stack_stop.go +++ b/api/http/handler/stacks/stack_stop.go @@ -76,7 +76,7 @@ func (handler *Handler) stackStop(w http.ResponseWriter, r *http.Request) *httpe // stop scheduler updates of the stack before stopping if stack.AutoUpdate != nil && stack.AutoUpdate.JobID != "" { - handler.Scheduler.StopJob(stack.AutoUpdate.JobID) + stopAutoupdate(stack.ID, stack.AutoUpdate.JobID, *handler.Scheduler) stack.AutoUpdate.JobID = "" } diff --git a/api/http/handler/stacks/stack_update_git.go b/api/http/handler/stacks/stack_update_git.go index b29dc905e..e6ba2b3ba 100644 --- a/api/http/handler/stacks/stack_update_git.go +++ b/api/http/handler/stacks/stack_update_git.go @@ -2,9 +2,7 @@ package stacks import ( "errors" - "log" "net/http" - "time" "github.com/asaskevich/govalidator" httperror "github.com/portainer/libhttp/error" @@ -16,7 +14,6 @@ import ( httperrors "github.com/portainer/portainer/api/http/errors" "github.com/portainer/portainer/api/http/security" "github.com/portainer/portainer/api/internal/stackutils" - "github.com/portainer/portainer/api/stacks" ) type stackGitUpdatePayload struct { @@ -125,8 +122,8 @@ func (handler *Handler) stackUpdateGit(w http.ResponseWriter, r *http.Request) * } //stop the autoupdate job if there is any - if stack.AutoUpdate != nil && stack.AutoUpdate.JobID != "" { - handler.Scheduler.StopJob(stack.AutoUpdate.JobID) + if stack.AutoUpdate != nil { + stopAutoupdate(stack.ID, stack.AutoUpdate.JobID, *handler.Scheduler) } //update retrieved stack data based on the payload @@ -147,15 +144,10 @@ func (handler *Handler) stackUpdateGit(w http.ResponseWriter, r *http.Request) * } if payload.AutoUpdate != nil && payload.AutoUpdate.Interval != "" { - d, err := time.ParseDuration(payload.AutoUpdate.Interval) - if err != nil { - return &httperror.HandlerError{StatusCode: http.StatusBadRequest, Message: "Unable to parse auto update interval", Err: err} + jobID, e := startAutoupdate(stack.ID, stack.AutoUpdate.Interval, handler.Scheduler, handler.StackDeployer, handler.DataStore, handler.GitService) + if e != nil { + return e } - jobID := handler.Scheduler.StartJobEvery(d, func() { - if err := stacks.RedeployWhenChanged(stack.ID, handler.StackDeployer, handler.DataStore, handler.GitService); err != nil { - log.Printf("[ERROR] %s\n", err) - } - }) stack.AutoUpdate.JobID = jobID } diff --git a/api/portainer.go b/api/portainer.go index 6d59fd586..b987bb691 100644 --- a/api/portainer.go +++ b/api/portainer.go @@ -732,9 +732,12 @@ type ( //StackAutoUpdate represents the git auto sync config for stack deployment StackAutoUpdate struct { - Interval string - Webhook string //a UUID generated from client - JobID string + // Auto update interval + Interval string `example:"1m30s"` + // A UUID generated from client + Webhook string `example:"05de31a2-79fa-4644-9c12-faa67e5c49f0"` + // Autoupdate job id + JobID string `example:"15"` } // StackID represents a stack identifier (it must be composed of Name + "_" + SwarmID to create a unique identifier)