mirror of
https://github.com/portainer/portainer.git
synced 2025-08-02 20:35:25 +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
|
@ -18,7 +18,7 @@ func (handler *Handler) edgeTemplateList(w http.ResponseWriter, r *http.Request)
|
|||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve settings from the database", err}
|
||||
}
|
||||
|
||||
url := portainer.EdgeTemplatesURL
|
||||
url := portainer.DefaultTemplatesURL
|
||||
if settings.TemplatesURL != "" {
|
||||
url = settings.TemplatesURL
|
||||
}
|
||||
|
|
|
@ -17,7 +17,6 @@ type publicSettingsResponse struct {
|
|||
AllowVolumeBrowserForRegularUsers bool `json:"AllowVolumeBrowserForRegularUsers"`
|
||||
EnableHostManagementFeatures bool `json:"EnableHostManagementFeatures"`
|
||||
EnableEdgeComputeFeatures bool `json:"EnableEdgeComputeFeatures"`
|
||||
ExternalTemplates bool `json:"ExternalTemplates"`
|
||||
OAuthLoginURI string `json:"OAuthLoginURI"`
|
||||
}
|
||||
|
||||
|
@ -36,7 +35,6 @@ func (handler *Handler) settingsPublic(w http.ResponseWriter, r *http.Request) *
|
|||
AllowVolumeBrowserForRegularUsers: settings.AllowVolumeBrowserForRegularUsers,
|
||||
EnableHostManagementFeatures: settings.EnableHostManagementFeatures,
|
||||
EnableEdgeComputeFeatures: settings.EnableEdgeComputeFeatures,
|
||||
ExternalTemplates: false,
|
||||
OAuthLoginURI: fmt.Sprintf("%s?response_type=code&client_id=%s&redirect_uri=%s&scope=%s&prompt=login",
|
||||
settings.OAuthSettings.AuthorizationURI,
|
||||
settings.OAuthSettings.ClientID,
|
||||
|
@ -44,9 +42,5 @@ func (handler *Handler) settingsPublic(w http.ResponseWriter, r *http.Request) *
|
|||
settings.OAuthSettings.Scopes),
|
||||
}
|
||||
|
||||
if settings.TemplatesURL != "" {
|
||||
publicSettings.ExternalTemplates = true
|
||||
}
|
||||
|
||||
return response.JSON(w, publicSettings)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -79,24 +79,6 @@ func FilterRegistries(registries []portainer.Registry, context *RestrictedReques
|
|||
return filteredRegistries
|
||||
}
|
||||
|
||||
// FilterTemplates filters templates based on the user role.
|
||||
// Non-administrator template do not have access to templates where the AdministratorOnly flag is set to true.
|
||||
func FilterTemplates(templates []portainer.Template, context *RestrictedRequestContext) []portainer.Template {
|
||||
filteredTemplates := templates
|
||||
|
||||
if !context.IsAdmin {
|
||||
filteredTemplates = make([]portainer.Template, 0)
|
||||
|
||||
for _, template := range templates {
|
||||
if !template.AdministratorOnly {
|
||||
filteredTemplates = append(filteredTemplates, template)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return filteredTemplates
|
||||
}
|
||||
|
||||
// FilterEndpoints filters endpoints based on user role and team memberships.
|
||||
// Non administrator users only have access to authorized endpoints (can be inherited via endoint groups).
|
||||
func FilterEndpoints(endpoints []portainer.Endpoint, groups []portainer.EndpointGroup, context *RestrictedRequestContext) []portainer.Endpoint {
|
||||
|
|
|
@ -78,7 +78,6 @@ type Server struct {
|
|||
TagService portainer.TagService
|
||||
TeamService portainer.TeamService
|
||||
TeamMembershipService portainer.TeamMembershipService
|
||||
TemplateService portainer.TemplateService
|
||||
UserService portainer.UserService
|
||||
WebhookService portainer.WebhookService
|
||||
Handler *handler.Handler
|
||||
|
@ -282,7 +281,6 @@ func (server *Server) Start() error {
|
|||
var supportHandler = support.NewHandler(requestBouncer)
|
||||
|
||||
var templatesHandler = templates.NewHandler(requestBouncer)
|
||||
templatesHandler.TemplateService = server.TemplateService
|
||||
templatesHandler.SettingsService = server.SettingsService
|
||||
|
||||
var uploadHandler = upload.NewHandler(requestBouncer)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue