diff --git a/api/filesystem/filesystem.go b/api/filesystem/filesystem.go index a0c85727d..acbb12db5 100644 --- a/api/filesystem/filesystem.go +++ b/api/filesystem/filesystem.go @@ -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 { diff --git a/api/http/handler/schedules/handler.go b/api/http/handler/schedules/handler.go index 408c8c65c..073c05606 100644 --- a/api/http/handler/schedules/handler.go +++ b/api/http/handler/schedules/handler.go @@ -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 } diff --git a/api/http/handler/schedules/schedule_create.go b/api/http/handler/schedules/schedule_create.go index 4d8fbd740..625d707ca 100644 --- a/api/http/handler/schedules/schedule_create.go +++ b/api/http/handler/schedules/schedule_create.go @@ -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 } diff --git a/api/http/handler/schedules/schedule_delete.go b/api/http/handler/schedules/schedule_delete.go index 502f7d7e4..51c01eec9 100644 --- a/api/http/handler/schedules/schedule_delete.go +++ b/api/http/handler/schedules/schedule_delete.go @@ -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} diff --git a/api/http/handler/schedules/schedule_file.go b/api/http/handler/schedules/schedule_file.go new file mode 100644 index 000000000..790f4d2e4 --- /dev/null +++ b/api/http/handler/schedules/schedule_file.go @@ -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)}) +} diff --git a/api/http/handler/schedules/schedule_update.go b/api/http/handler/schedules/schedule_update.go index 209c47da0..e4de5b5f1 100644 --- a/api/http/handler/schedules/schedule_update.go +++ b/api/http/handler/schedules/schedule_update.go @@ -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) diff --git a/api/portainer.go b/api/portainer.go index b337c6d4b..9e3efb8e8 100644 --- a/api/portainer.go +++ b/api/portainer.go @@ -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 diff --git a/app/portainer/components/forms/schedule-form/scheduleForm.html b/app/portainer/components/forms/schedule-form/scheduleForm.html index 1f287ece1..02ed2ebbe 100644 --- a/app/portainer/components/forms/schedule-form/scheduleForm.html +++ b/app/portainer/components/forms/schedule-form/scheduleForm.html @@ -55,14 +55,14 @@ +
/host
folder.
+
+ /host
folder.
-
-