mirror of
https://github.com/portainer/portainer.git
synced 2025-08-05 05:45:22 +02:00
feat(templates): remove template management features (#3719)
* feat(api): remove template management features * feat(templates): remove template management features
This commit is contained in:
parent
45f93882d0
commit
5563ff60fc
36 changed files with 26 additions and 965 deletions
|
@ -9,14 +9,9 @@ import (
|
|||
"github.com/portainer/portainer/api/http/security"
|
||||
)
|
||||
|
||||
const (
|
||||
errTemplateManagementDisabled = portainer.Error("Template management is disabled")
|
||||
)
|
||||
|
||||
// Handler represents an HTTP API handler for managing templates.
|
||||
type Handler struct {
|
||||
*mux.Router
|
||||
TemplateService portainer.TemplateService
|
||||
SettingsService portainer.SettingsService
|
||||
}
|
||||
|
||||
|
@ -28,29 +23,5 @@ func NewHandler(bouncer *security.RequestBouncer) *Handler {
|
|||
|
||||
h.Handle("/templates",
|
||||
bouncer.RestrictedAccess(httperror.LoggerHandler(h.templateList))).Methods(http.MethodGet)
|
||||
h.Handle("/templates",
|
||||
bouncer.AdminAccess(h.templateManagementCheck(httperror.LoggerHandler(h.templateCreate)))).Methods(http.MethodPost)
|
||||
h.Handle("/templates/{id}",
|
||||
bouncer.RestrictedAccess(h.templateManagementCheck(httperror.LoggerHandler(h.templateInspect)))).Methods(http.MethodGet)
|
||||
h.Handle("/templates/{id}",
|
||||
bouncer.AdminAccess(h.templateManagementCheck(httperror.LoggerHandler(h.templateUpdate)))).Methods(http.MethodPut)
|
||||
h.Handle("/templates/{id}",
|
||||
bouncer.AdminAccess(h.templateManagementCheck(httperror.LoggerHandler(h.templateDelete)))).Methods(http.MethodDelete)
|
||||
return h
|
||||
}
|
||||
|
||||
func (handler *Handler) templateManagementCheck(next http.Handler) http.Handler {
|
||||
return httperror.LoggerHandler(func(rw http.ResponseWriter, r *http.Request) *httperror.HandlerError {
|
||||
settings, err := handler.SettingsService.Settings()
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve settings from the database", err}
|
||||
}
|
||||
|
||||
if settings.TemplatesURL != "" {
|
||||
return &httperror.HandlerError{http.StatusServiceUnavailable, "Portainer is configured to use external templates, template management is disabled", errTemplateManagementDisabled}
|
||||
}
|
||||
|
||||
next.ServeHTTP(rw, r)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
|
|
@ -1,122 +0,0 @@
|
|||
package templates
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/asaskevich/govalidator"
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
"github.com/portainer/libhttp/request"
|
||||
"github.com/portainer/libhttp/response"
|
||||
"github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/filesystem"
|
||||
)
|
||||
|
||||
type templateCreatePayload struct {
|
||||
// Mandatory
|
||||
Type int
|
||||
Title string
|
||||
Description string
|
||||
AdministratorOnly bool
|
||||
|
||||
// Opt stack/container
|
||||
Name string
|
||||
Logo string
|
||||
Note string
|
||||
Platform string
|
||||
Categories []string
|
||||
Env []portainer.TemplateEnv
|
||||
|
||||
// Mandatory container
|
||||
Image string
|
||||
|
||||
// Mandatory stack
|
||||
Repository portainer.TemplateRepository
|
||||
|
||||
// Opt container
|
||||
Registry string
|
||||
Command string
|
||||
Network string
|
||||
Volumes []portainer.TemplateVolume
|
||||
Ports []string
|
||||
Labels []portainer.Pair
|
||||
Privileged bool
|
||||
Interactive bool
|
||||
RestartPolicy string
|
||||
Hostname string
|
||||
}
|
||||
|
||||
func (payload *templateCreatePayload) Validate(r *http.Request) error {
|
||||
if payload.Type == 0 || (payload.Type != 1 && payload.Type != 2 && payload.Type != 3) {
|
||||
return portainer.Error("Invalid template type. Valid values are: 1 (container), 2 (Swarm stack template) or 3 (Compose stack template).")
|
||||
}
|
||||
if govalidator.IsNull(payload.Title) {
|
||||
return portainer.Error("Invalid template title")
|
||||
}
|
||||
if govalidator.IsNull(payload.Description) {
|
||||
return portainer.Error("Invalid template description")
|
||||
}
|
||||
|
||||
if payload.Type == 1 {
|
||||
if govalidator.IsNull(payload.Image) {
|
||||
return portainer.Error("Invalid template image")
|
||||
}
|
||||
}
|
||||
|
||||
if payload.Type == 2 || payload.Type == 3 {
|
||||
if govalidator.IsNull(payload.Repository.URL) {
|
||||
return portainer.Error("Invalid template repository URL")
|
||||
}
|
||||
if govalidator.IsNull(payload.Repository.StackFile) {
|
||||
payload.Repository.StackFile = filesystem.ComposeFileDefaultName
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// POST request on /api/templates
|
||||
func (handler *Handler) templateCreate(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
|
||||
var payload templateCreatePayload
|
||||
err := request.DecodeAndValidateJSONPayload(r, &payload)
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusBadRequest, "Invalid request payload", err}
|
||||
}
|
||||
|
||||
template := &portainer.Template{
|
||||
Type: portainer.TemplateType(payload.Type),
|
||||
Title: payload.Title,
|
||||
Description: payload.Description,
|
||||
AdministratorOnly: payload.AdministratorOnly,
|
||||
Name: payload.Name,
|
||||
Logo: payload.Logo,
|
||||
Note: payload.Note,
|
||||
Platform: payload.Platform,
|
||||
Categories: payload.Categories,
|
||||
Env: payload.Env,
|
||||
}
|
||||
|
||||
if template.Type == portainer.ContainerTemplate {
|
||||
template.Image = payload.Image
|
||||
template.Registry = payload.Registry
|
||||
template.Command = payload.Command
|
||||
template.Network = payload.Network
|
||||
template.Volumes = payload.Volumes
|
||||
template.Ports = payload.Ports
|
||||
template.Labels = payload.Labels
|
||||
template.Privileged = payload.Privileged
|
||||
template.Interactive = payload.Interactive
|
||||
template.RestartPolicy = payload.RestartPolicy
|
||||
template.Hostname = payload.Hostname
|
||||
}
|
||||
|
||||
if template.Type == portainer.SwarmStackTemplate || template.Type == portainer.ComposeStackTemplate {
|
||||
template.Repository = payload.Repository
|
||||
}
|
||||
|
||||
err = handler.TemplateService.CreateTemplate(template)
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to persist the template inside the database", err}
|
||||
}
|
||||
|
||||
return response.JSON(w, template)
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
package templates
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
"github.com/portainer/libhttp/request"
|
||||
"github.com/portainer/libhttp/response"
|
||||
"github.com/portainer/portainer/api"
|
||||
)
|
||||
|
||||
// DELETE request on /api/templates/:id
|
||||
func (handler *Handler) templateDelete(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
|
||||
id, err := request.RetrieveNumericRouteVariableValue(r, "id")
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusBadRequest, "Invalid template identifier route variable", err}
|
||||
}
|
||||
|
||||
err = handler.TemplateService.DeleteTemplate(portainer.TemplateID(id))
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to remove the template from the database", err}
|
||||
}
|
||||
|
||||
return response.Empty(w)
|
||||
}
|
|
@ -1,27 +0,0 @@
|
|||
package templates
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
"github.com/portainer/libhttp/request"
|
||||
"github.com/portainer/libhttp/response"
|
||||
"github.com/portainer/portainer/api"
|
||||
)
|
||||
|
||||
// GET request on /api/templates/:id
|
||||
func (handler *Handler) templateInspect(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
|
||||
templateID, err := request.RetrieveNumericRouteVariableValue(r, "id")
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusBadRequest, "Invalid template identifier route variable", err}
|
||||
}
|
||||
|
||||
template, err := handler.TemplateService.Template(portainer.TemplateID(templateID))
|
||||
if err == portainer.ErrObjectNotFound {
|
||||
return &httperror.HandlerError{http.StatusNotFound, "Unable to find a template with the specified identifier inside the database", err}
|
||||
} else if err != nil {
|
||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to find a template with the specified identifier inside the database", err}
|
||||
}
|
||||
|
||||
return response.JSON(w, template)
|
||||
}
|
|
@ -1,14 +1,10 @@
|
|||
package templates
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
"github.com/portainer/libhttp/response"
|
||||
"github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/http/client"
|
||||
"github.com/portainer/portainer/api/http/security"
|
||||
)
|
||||
|
||||
// GET request on /api/templates
|
||||
|
@ -18,30 +14,17 @@ func (handler *Handler) templateList(w http.ResponseWriter, r *http.Request) *ht
|
|||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve settings from the database", err}
|
||||
}
|
||||
|
||||
var templates []portainer.Template
|
||||
if settings.TemplatesURL == "" {
|
||||
templates, err = handler.TemplateService.Templates()
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve templates from the database", err}
|
||||
}
|
||||
} else {
|
||||
var templateData []byte
|
||||
templateData, err = client.Get(settings.TemplatesURL, 0)
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve external templates", err}
|
||||
}
|
||||
|
||||
err = json.Unmarshal(templateData, &templates)
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to parse external templates", err}
|
||||
}
|
||||
}
|
||||
|
||||
securityContext, err := security.RetrieveRestrictedRequestContext(r)
|
||||
resp, err := http.Get(settings.TemplatesURL)
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve info from request context", err}
|
||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve templates via the network", err}
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
_, err = io.Copy(w, resp.Body)
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to write templates from templates URL", err}
|
||||
}
|
||||
|
||||
filteredTemplates := security.FilterTemplates(templates, securityContext)
|
||||
return response.JSON(w, filteredTemplates)
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -1,164 +0,0 @@
|
|||
package templates
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
"github.com/portainer/libhttp/request"
|
||||
"github.com/portainer/libhttp/response"
|
||||
"github.com/portainer/portainer/api"
|
||||
)
|
||||
|
||||
type templateUpdatePayload struct {
|
||||
Title *string
|
||||
Description *string
|
||||
AdministratorOnly *bool
|
||||
Name *string
|
||||
Logo *string
|
||||
Note *string
|
||||
Platform *string
|
||||
Categories []string
|
||||
Env []portainer.TemplateEnv
|
||||
Image *string
|
||||
Registry *string
|
||||
Repository portainer.TemplateRepository
|
||||
Command *string
|
||||
Network *string
|
||||
Volumes []portainer.TemplateVolume
|
||||
Ports []string
|
||||
Labels []portainer.Pair
|
||||
Privileged *bool
|
||||
Interactive *bool
|
||||
RestartPolicy *string
|
||||
Hostname *string
|
||||
}
|
||||
|
||||
func (payload *templateUpdatePayload) Validate(r *http.Request) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// PUT request on /api/templates/:id
|
||||
func (handler *Handler) templateUpdate(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
|
||||
templateID, err := request.RetrieveNumericRouteVariableValue(r, "id")
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusBadRequest, "Invalid template identifier route variable", err}
|
||||
}
|
||||
|
||||
template, err := handler.TemplateService.Template(portainer.TemplateID(templateID))
|
||||
if err == portainer.ErrObjectNotFound {
|
||||
return &httperror.HandlerError{http.StatusNotFound, "Unable to find a template with the specified identifier inside the database", err}
|
||||
} else if err != nil {
|
||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to find a template with the specified identifier inside the database", err}
|
||||
}
|
||||
|
||||
var payload templateUpdatePayload
|
||||
err = request.DecodeAndValidateJSONPayload(r, &payload)
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusBadRequest, "Invalid request payload", err}
|
||||
}
|
||||
|
||||
updateTemplate(template, &payload)
|
||||
|
||||
err = handler.TemplateService.UpdateTemplate(template.ID, template)
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusNotFound, "Unable to persist template changes inside the database", err}
|
||||
}
|
||||
|
||||
return response.JSON(w, template)
|
||||
}
|
||||
|
||||
func updateContainerProperties(template *portainer.Template, payload *templateUpdatePayload) {
|
||||
if payload.Image != nil {
|
||||
template.Image = *payload.Image
|
||||
}
|
||||
|
||||
if payload.Registry != nil {
|
||||
template.Registry = *payload.Registry
|
||||
}
|
||||
|
||||
if payload.Command != nil {
|
||||
template.Command = *payload.Command
|
||||
}
|
||||
|
||||
if payload.Network != nil {
|
||||
template.Network = *payload.Network
|
||||
}
|
||||
|
||||
if payload.Volumes != nil {
|
||||
template.Volumes = payload.Volumes
|
||||
}
|
||||
|
||||
if payload.Ports != nil {
|
||||
template.Ports = payload.Ports
|
||||
}
|
||||
|
||||
if payload.Labels != nil {
|
||||
template.Labels = payload.Labels
|
||||
}
|
||||
|
||||
if payload.Privileged != nil {
|
||||
template.Privileged = *payload.Privileged
|
||||
}
|
||||
|
||||
if payload.Interactive != nil {
|
||||
template.Interactive = *payload.Interactive
|
||||
}
|
||||
|
||||
if payload.RestartPolicy != nil {
|
||||
template.RestartPolicy = *payload.RestartPolicy
|
||||
}
|
||||
|
||||
if payload.Hostname != nil {
|
||||
template.Hostname = *payload.Hostname
|
||||
}
|
||||
}
|
||||
|
||||
func updateStackProperties(template *portainer.Template, payload *templateUpdatePayload) {
|
||||
if payload.Repository.URL != "" && payload.Repository.StackFile != "" {
|
||||
template.Repository = payload.Repository
|
||||
}
|
||||
}
|
||||
|
||||
func updateTemplate(template *portainer.Template, payload *templateUpdatePayload) {
|
||||
if payload.Title != nil {
|
||||
template.Title = *payload.Title
|
||||
}
|
||||
|
||||
if payload.Description != nil {
|
||||
template.Description = *payload.Description
|
||||
}
|
||||
|
||||
if payload.Name != nil {
|
||||
template.Name = *payload.Name
|
||||
}
|
||||
|
||||
if payload.Logo != nil {
|
||||
template.Logo = *payload.Logo
|
||||
}
|
||||
|
||||
if payload.Note != nil {
|
||||
template.Note = *payload.Note
|
||||
}
|
||||
|
||||
if payload.Platform != nil {
|
||||
template.Platform = *payload.Platform
|
||||
}
|
||||
|
||||
if payload.Categories != nil {
|
||||
template.Categories = payload.Categories
|
||||
}
|
||||
|
||||
if payload.Env != nil {
|
||||
template.Env = payload.Env
|
||||
}
|
||||
|
||||
if payload.AdministratorOnly != nil {
|
||||
template.AdministratorOnly = *payload.AdministratorOnly
|
||||
}
|
||||
|
||||
if template.Type == portainer.ContainerTemplate {
|
||||
updateContainerProperties(template, payload)
|
||||
} else if template.Type == portainer.SwarmStackTemplate || template.Type == portainer.ComposeStackTemplate {
|
||||
updateStackProperties(template, payload)
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue