mirror of
https://github.com/portainer/portainer.git
synced 2025-08-05 05:45:22 +02:00
feat(schedules): add the ability to update a schedule script (#2438)
This commit is contained in:
parent
695c28d4f8
commit
807c830db0
13 changed files with 124 additions and 56 deletions
|
@ -5,7 +5,6 @@ import (
|
|||
"encoding/json"
|
||||
"encoding/pem"
|
||||
"io/ioutil"
|
||||
"strconv"
|
||||
|
||||
"github.com/portainer/portainer"
|
||||
|
||||
|
@ -322,16 +321,15 @@ func (service *Service) getContentFromPEMFile(filePath string) ([]byte, error) {
|
|||
return block.Bytes, nil
|
||||
}
|
||||
|
||||
// GetScheduleFolder returns the absolute path on the FS for a schedule based
|
||||
// GetScheduleFolder returns the absolute path on the filesystem for a schedule based
|
||||
// on its identifier.
|
||||
func (service *Service) GetScheduleFolder(scheduleIdentifier portainer.ScheduleID) string {
|
||||
return path.Join(service.fileStorePath, ScheduleStorePath, strconv.Itoa(int(scheduleIdentifier)))
|
||||
func (service *Service) GetScheduleFolder(identifier string) string {
|
||||
return path.Join(service.fileStorePath, ScheduleStorePath, identifier)
|
||||
}
|
||||
|
||||
// StoreScheduledJobFileFromBytes creates a subfolder in the ScheduleStorePath and stores a new file from bytes.
|
||||
// It returns the path to the folder where the file is stored.
|
||||
func (service *Service) StoreScheduledJobFileFromBytes(scheduleIdentifier portainer.ScheduleID, data []byte) (string, error) {
|
||||
identifier := strconv.Itoa(int(scheduleIdentifier))
|
||||
func (service *Service) StoreScheduledJobFileFromBytes(identifier string, data []byte) (string, error) {
|
||||
scheduleStorePath := path.Join(ScheduleStorePath, identifier)
|
||||
err := service.createDirectoryInStore(scheduleStorePath)
|
||||
if err != nil {
|
||||
|
|
|
@ -35,6 +35,7 @@ func NewHandler(bouncer *security.RequestBouncer) *Handler {
|
|||
bouncer.AdministratorAccess(httperror.LoggerHandler(h.scheduleUpdate))).Methods(http.MethodPut)
|
||||
h.Handle("/schedules/{id}",
|
||||
bouncer.AdministratorAccess(httperror.LoggerHandler(h.scheduleDelete))).Methods(http.MethodDelete)
|
||||
|
||||
h.Handle("/schedules/{id}/file",
|
||||
bouncer.AdministratorAccess(httperror.LoggerHandler(h.scheduleFile))).Methods(http.MethodGet)
|
||||
return h
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ package schedules
|
|||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/asaskevich/govalidator"
|
||||
|
@ -138,7 +139,7 @@ func (handler *Handler) createScheduleFromFile(w http.ResponseWriter, r *http.Re
|
|||
func (handler *Handler) createSchedule(name, image, cronExpression string, endpoints []portainer.EndpointID, file []byte) (*portainer.Schedule, error) {
|
||||
scheduleIdentifier := portainer.ScheduleID(handler.ScheduleService.GetNextIdentifier())
|
||||
|
||||
scriptPath, err := handler.FileService.StoreScheduledJobFileFromBytes(scheduleIdentifier, file)
|
||||
scriptPath, err := handler.FileService.StoreScheduledJobFileFromBytes(strconv.Itoa(int(scheduleIdentifier)), file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ package schedules
|
|||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
"github.com/portainer/libhttp/request"
|
||||
|
@ -27,7 +28,7 @@ func (handler *Handler) scheduleDelete(w http.ResponseWriter, r *http.Request) *
|
|||
return &httperror.HandlerError{http.StatusBadRequest, "Cannot remove system schedules", errors.New("Cannot remove system schedule")}
|
||||
}
|
||||
|
||||
scheduleFolder := handler.FileService.GetScheduleFolder(portainer.ScheduleID(scheduleID))
|
||||
scheduleFolder := handler.FileService.GetScheduleFolder(strconv.Itoa(scheduleID))
|
||||
err = handler.FileService.RemoveDirectory(scheduleFolder)
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to remove the files associated to the schedule on the filesystem", err}
|
||||
|
|
41
api/http/handler/schedules/schedule_file.go
Normal file
41
api/http/handler/schedules/schedule_file.go
Normal file
|
@ -0,0 +1,41 @@
|
|||
package schedules
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
"github.com/portainer/libhttp/request"
|
||||
"github.com/portainer/libhttp/response"
|
||||
"github.com/portainer/portainer"
|
||||
)
|
||||
|
||||
type scheduleFileResponse struct {
|
||||
ScheduleFileContent string `json:"ScheduleFileContent"`
|
||||
}
|
||||
|
||||
// GET request on /api/schedules/:id/file
|
||||
func (handler *Handler) scheduleFile(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
|
||||
scheduleID, err := request.RetrieveNumericRouteVariableValue(r, "id")
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusBadRequest, "Invalid schedule identifier route variable", err}
|
||||
}
|
||||
|
||||
schedule, err := handler.ScheduleService.Schedule(portainer.ScheduleID(scheduleID))
|
||||
if err == portainer.ErrObjectNotFound {
|
||||
return &httperror.HandlerError{http.StatusNotFound, "Unable to find a schedule with the specified identifier inside the database", err}
|
||||
} else if err != nil {
|
||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to find a schedule with the specified identifier inside the database", err}
|
||||
}
|
||||
|
||||
if schedule.JobType != portainer.ScriptExecutionJobType {
|
||||
return &httperror.HandlerError{http.StatusBadRequest, "Unable to retrieve script file", errors.New("This type of schedule do not have any associated script file")}
|
||||
}
|
||||
|
||||
scheduleFileContent, err := handler.FileService.GetFileContent(schedule.ScriptExecutionJob.ScriptPath)
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve schedule script file from disk", err}
|
||||
}
|
||||
|
||||
return response.JSON(w, &scheduleFileResponse{ScheduleFileContent: string(scheduleFileContent)})
|
||||
}
|
|
@ -2,6 +2,7 @@ package schedules
|
|||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
"github.com/portainer/libhttp/request"
|
||||
|
@ -15,6 +16,7 @@ type scheduleUpdatePayload struct {
|
|||
Image *string
|
||||
CronExpression *string
|
||||
Endpoints []portainer.EndpointID
|
||||
FileContent *string
|
||||
}
|
||||
|
||||
func (payload *scheduleUpdatePayload) Validate(r *http.Request) error {
|
||||
|
@ -41,8 +43,16 @@ func (handler *Handler) scheduleUpdate(w http.ResponseWriter, r *http.Request) *
|
|||
}
|
||||
|
||||
updateJobSchedule := updateSchedule(schedule, &payload)
|
||||
if updateJobSchedule {
|
||||
|
||||
if payload.FileContent != nil {
|
||||
_, err := handler.FileService.StoreScheduledJobFileFromBytes(strconv.Itoa(scheduleID), []byte(*payload.FileContent))
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to persist script file changes on the filesystem", err}
|
||||
}
|
||||
updateJobSchedule = true
|
||||
}
|
||||
|
||||
if updateJobSchedule {
|
||||
jobContext := cron.NewScriptExecutionJobContext(handler.JobService, handler.EndpointService, handler.FileService)
|
||||
jobRunner := cron.NewScriptExecutionJobRunner(schedule.ScriptExecutionJob, jobContext)
|
||||
err := handler.JobScheduler.UpdateSchedule(schedule, jobRunner)
|
||||
|
|
|
@ -654,8 +654,8 @@ type (
|
|||
LoadKeyPair() ([]byte, []byte, error)
|
||||
WriteJSONToFile(path string, content interface{}) error
|
||||
FileExists(path string) (bool, error)
|
||||
StoreScheduledJobFileFromBytes(scheduleIdentifier ScheduleID, data []byte) (string, error)
|
||||
GetScheduleFolder(scheduleIdentifier ScheduleID) string
|
||||
StoreScheduledJobFileFromBytes(identifier string, data []byte) (string, error)
|
||||
GetScheduleFolder(identifier string) string
|
||||
}
|
||||
|
||||
// GitService represents a service for managing Git
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue