diff --git a/api/http/handler/customtemplates/customtemplate_create.go b/api/http/handler/customtemplates/customtemplate_create.go index 03c532aa7..27f3c7749 100644 --- a/api/http/handler/customtemplates/customtemplate_create.go +++ b/api/http/handler/customtemplates/customtemplate_create.go @@ -21,30 +21,8 @@ import ( "github.com/rs/zerolog/log" ) -// @id CustomTemplateCreate -// @summary Create a custom template -// @description Create a custom template. -// @description **Access policy**: authenticated -// @tags custom_templates -// @security ApiKeyAuth -// @security jwt -// @accept json,multipart/form-data -// @produce json -// @param method query string true "method for creating template" Enums(string, file, repository) -// @param body_string body customTemplateFromFileContentPayload false "Required when using method=string" -// @param body_repository body customTemplateFromGitRepositoryPayload false "Required when using method=repository" -// @param Title formData string false "Title of the template. required when method is file" -// @param Description formData string false "Description of the template. required when method is file" -// @param Note formData string false "A note that will be displayed in the UI. Supports HTML content" -// @param Platform formData int false "Platform associated to the template (1 - 'linux', 2 - 'windows'). required when method is file" Enums(1,2) -// @param Type formData int false "Type of created stack (1 - swarm, 2 - compose), required when method is file" Enums(1,2) -// @param file formData file false "required when method is file" -// @success 200 {object} portainer.CustomTemplate -// @failure 400 "Invalid request" -// @failure 500 "Server error" -// @router /custom_templates [post] func (handler *Handler) customTemplateCreate(w http.ResponseWriter, r *http.Request) *httperror.HandlerError { - method, err := request.RetrieveQueryParameter(r, "method", false) + method, err := request.RetrieveRouteVariableValue(r, "method") if err != nil { return httperror.BadRequest("Invalid query parameter: method", err) } @@ -154,6 +132,20 @@ func isValidNote(note string) bool { return !match } +// @id CustomTemplateCreateString +// @summary Create a custom template +// @description Create a custom template. +// @description **Access policy**: authenticated +// @tags custom_templates +// @security ApiKeyAuth +// @security jwt +// @accept json +// @produce json +// @param body body customTemplateFromFileContentPayload true "body" +// @success 200 {object} portainer.CustomTemplate +// @failure 400 "Invalid request" +// @failure 500 "Server error" +// @router /custom_templates/string [post] func (handler *Handler) createCustomTemplateFromFileContent(r *http.Request) (*portainer.CustomTemplate, error) { var payload customTemplateFromFileContentPayload err := request.DecodeAndValidateJSONPayload(r, &payload) @@ -251,6 +243,20 @@ func (payload *customTemplateFromGitRepositoryPayload) Validate(r *http.Request) return validateVariablesDefinitions(payload.Variables) } +// @id CustomTemplateCreateRepository +// @summary Create a custom template +// @description Create a custom template. +// @description **Access policy**: authenticated +// @tags custom_templates +// @security ApiKeyAuth +// @security jwt +// @accept json +// @produce json +// @param body body customTemplateFromGitRepositoryPayload true "Required when using method=repository" +// @success 200 {object} portainer.CustomTemplate +// @failure 400 "Invalid request" +// @failure 500 "Server error" +// @router /custom_templates/repository [post] func (handler *Handler) createCustomTemplateFromGitRepository(r *http.Request) (*portainer.CustomTemplate, error) { var payload customTemplateFromGitRepositoryPayload err := request.DecodeAndValidateJSONPayload(r, &payload) @@ -405,6 +411,25 @@ func (payload *customTemplateFromFileUploadPayload) Validate(r *http.Request) er return nil } +// @id CustomTemplateCreateFile +// @summary Create a custom template +// @description Create a custom template. +// @description **Access policy**: authenticated +// @tags custom_templates +// @security ApiKeyAuth +// @security jwt +// @accept multipart/form-data +// @produce json +// @param Title formData string false "Title of the template" +// @param Description formData string false "Description of the template" +// @param Note formData string false "A note that will be displayed in the UI. Supports HTML content" +// @param Platform formData int false "Platform associated to the template (1 - 'linux', 2 - 'windows')" Enums(1,2) +// @param Type formData int false "Type of created stack (1 - swarm, 2 - compose)" Enums(1,2) +// @param file formData file false "File" +// @success 200 {object} portainer.CustomTemplate +// @failure 400 "Invalid request" +// @failure 500 "Server error" +// @router /custom_templates/file [post] func (handler *Handler) createCustomTemplateFromFileUpload(r *http.Request) (*portainer.CustomTemplate, error) { payload := &customTemplateFromFileUploadPayload{} err := payload.Validate(r) diff --git a/api/http/handler/customtemplates/handler.go b/api/http/handler/customtemplates/handler.go index 7be8084a5..17589abf1 100644 --- a/api/http/handler/customtemplates/handler.go +++ b/api/http/handler/customtemplates/handler.go @@ -29,7 +29,8 @@ func NewHandler(bouncer *security.RequestBouncer, dataStore dataservices.DataSto GitService: gitService, gitFetchMutexs: make(map[portainer.TemplateID]*sync.Mutex), } - h.Handle("/custom_templates", + + h.Handle("/custom_templates/create/{method}", bouncer.AuthenticatedAccess(httperror.LoggerHandler(h.customTemplateCreate))).Methods(http.MethodPost) h.Handle("/custom_templates", bouncer.AuthenticatedAccess(httperror.LoggerHandler(h.customTemplateList))).Methods(http.MethodGet) diff --git a/api/http/handler/edgejobs/edgejob_create.go b/api/http/handler/edgejobs/edgejob_create.go index 75cbe2bab..68c9f4d87 100644 --- a/api/http/handler/edgejobs/edgejob_create.go +++ b/api/http/handler/edgejobs/edgejob_create.go @@ -27,22 +27,8 @@ type edgeJobBasePayload struct { EdgeGroups []portainer.EdgeGroupID } -// @id EdgeJobCreate -// @summary Create an EdgeJob -// @description **Access policy**: administrator -// @tags edge_jobs -// @security ApiKeyAuth -// @security jwt -// @produce json -// @param method query string true "Creation Method" Enums(file, string) -// @param body_string body edgeJobCreateFromFileContentPayload true "EdgeGroup data when method is string" -// @param body_file body edgeJobCreateFromFilePayload true "EdgeGroup data when method is file" -// @success 200 {object} portainer.EdgeGroup -// @failure 503 "Edge compute features are disabled" -// @failure 500 -// @router /edge_jobs [post] func (handler *Handler) edgeJobCreate(w http.ResponseWriter, r *http.Request) *httperror.HandlerError { - method, err := request.RetrieveQueryParameter(r, "method", false) + method, err := request.RetrieveRouteVariableValue(r, "method") if err != nil { return httperror.BadRequest("Invalid query parameter: method. Valid values are: file or string", err) } @@ -86,6 +72,18 @@ func (payload *edgeJobCreateFromFileContentPayload) Validate(r *http.Request) er return nil } +// @id EdgeJobCreateString +// @summary Create an EdgeJob from a text +// @description **Access policy**: administrator +// @tags edge_jobs +// @security ApiKeyAuth +// @security jwt +// @produce json +// @param body body edgeJobCreateFromFileContentPayload true "EdgeGroup data when method is string" +// @success 200 {object} portainer.EdgeGroup +// @failure 503 "Edge compute features are disabled" +// @failure 500 +// @router /edge_jobs/create/string [post] func (handler *Handler) createEdgeJobFromFileContent(w http.ResponseWriter, r *http.Request) *httperror.HandlerError { var payload edgeJobCreateFromFileContentPayload err := request.DecodeAndValidateJSONPayload(r, &payload) @@ -177,6 +175,24 @@ func (payload *edgeJobCreateFromFilePayload) Validate(r *http.Request) error { return nil } +// @id EdgeJobCreateFile +// @summary Create an EdgeJob from a file +// @description **Access policy**: administrator +// @tags edge_jobs +// @accept multipart/form-data +// @security ApiKeyAuth +// @security jwt +// @produce json +// @param file formData file true "Content of the Stack file" +// @param Name formData string true "Name of the stack" +// @param CronExpression formData string true "A cron expression to schedule this job" +// @param EdgeGroups formData string true "JSON stringified array of Edge Groups ids" +// @param Endpoints formData string true "JSON stringified array of Environment ids" +// @param Recurring formData bool false "If recurring" +// @success 200 {object} portainer.EdgeGroup +// @failure 503 "Edge compute features are disabled" +// @failure 500 +// @router /edge_jobs/create/file [post] func (handler *Handler) createEdgeJobFromFile(w http.ResponseWriter, r *http.Request) *httperror.HandlerError { payload := &edgeJobCreateFromFilePayload{} err := payload.Validate(r) diff --git a/api/http/handler/edgejobs/handler.go b/api/http/handler/edgejobs/handler.go index 3f409db6b..a4bdcb94b 100644 --- a/api/http/handler/edgejobs/handler.go +++ b/api/http/handler/edgejobs/handler.go @@ -28,7 +28,7 @@ func NewHandler(bouncer *security.RequestBouncer) *Handler { h.Handle("/edge_jobs", bouncer.AdminAccess(bouncer.EdgeComputeOperation(httperror.LoggerHandler(h.edgeJobList)))).Methods(http.MethodGet) - h.Handle("/edge_jobs", + h.Handle("/edge_jobs/create/{method}", bouncer.AdminAccess(bouncer.EdgeComputeOperation(httperror.LoggerHandler(h.edgeJobCreate)))).Methods(http.MethodPost) h.Handle("/edge_jobs/{id}", bouncer.AdminAccess(bouncer.EdgeComputeOperation(httperror.LoggerHandler(h.edgeJobInspect)))).Methods(http.MethodGet) diff --git a/api/http/handler/edgestacks/edgestack_create.go b/api/http/handler/edgestacks/edgestack_create.go index aa707384e..70ef27554 100644 --- a/api/http/handler/edgestacks/edgestack_create.go +++ b/api/http/handler/edgestacks/edgestack_create.go @@ -23,23 +23,8 @@ func (e *InvalidPayloadError) Error() string { return e.msg } -// @id EdgeStackCreate -// @summary Create an EdgeStack -// @description **Access policy**: administrator -// @tags edge_stacks -// @security ApiKeyAuth -// @security jwt -// @produce json -// @param method query string true "Creation Method" Enums(file,string,repository) -// @param body_string body swarmStackFromFileContentPayload true "Required when using method=string" -// @param body_file body swarmStackFromFileUploadPayload true "Required when using method=file" -// @param body_repository body swarmStackFromGitRepositoryPayload true "Required when using method=repository" -// @success 200 {object} portainer.EdgeStack -// @failure 500 -// @failure 503 "Edge compute features are disabled" -// @router /edge_stacks [post] func (handler *Handler) edgeStackCreate(w http.ResponseWriter, r *http.Request) *httperror.HandlerError { - method, err := request.RetrieveQueryParameter(r, "method", false) + method, err := request.RetrieveRouteVariableValue(r, "method") if err != nil { return httperror.BadRequest("Invalid query parameter: method", err) } @@ -74,7 +59,7 @@ func (handler *Handler) createSwarmStack(method string, dryrun bool, userID port case "file": return handler.createSwarmStackFromFileUpload(r, dryrun) } - return nil, errors.New("Invalid value for query parameter: method. Value must be one of: string, repository or file") + return nil, &InvalidPayloadError{"Invalid value for query parameter: method. Value must be one of: string, repository or file"} } type swarmStackFromFileContentPayload struct { @@ -109,6 +94,19 @@ func (payload *swarmStackFromFileContentPayload) Validate(r *http.Request) error return nil } +// @id EdgeStackCreateString +// @summary Create an EdgeStack from a text +// @description **Access policy**: administrator +// @tags edge_stacks +// @security ApiKeyAuth +// @security jwt +// @produce json +// @param body body swarmStackFromFileContentPayload true "stack config" +// @param dryrun query string false "if true, will not create an edge stack, but just will check the settings and return a non-persisted edge stack object" +// @success 200 {object} portainer.EdgeStack +// @failure 500 +// @failure 503 "Edge compute features are disabled" +// @router /edge_stacks/create/string [post] func (handler *Handler) createSwarmStackFromFileContent(r *http.Request, dryrun bool) (*portainer.EdgeStack, error) { var payload swarmStackFromFileContentPayload err := request.DecodeAndValidateJSONPayload(r, &payload) @@ -229,6 +227,20 @@ func (payload *swarmStackFromGitRepositoryPayload) Validate(r *http.Request) err return nil } +// @id EdgeStackCreateRepository +// @summary Create an EdgeStack from a git repository +// @description **Access policy**: administrator +// @tags edge_stacks +// @security ApiKeyAuth +// @security jwt +// @produce json +// @param method query string true "Creation Method" Enums(file,string,repository) +// @param body body swarmStackFromGitRepositoryPayload true "stack config" +// @param dryrun query string false "if true, will not create an edge stack, but just will check the settings and return a non-persisted edge stack object" +// @success 200 {object} portainer.EdgeStack +// @failure 500 +// @failure 503 "Edge compute features are disabled" +// @router /edge_stacks/create/repository [post] func (handler *Handler) createSwarmStackFromGitRepository(r *http.Request, dryrun bool, userID portainer.UserID) (*portainer.EdgeStack, error) { var payload swarmStackFromGitRepositoryPayload err := request.DecodeAndValidateJSONPayload(r, &payload) @@ -299,14 +311,14 @@ func (payload *swarmStackFromFileUploadPayload) Validate(r *http.Request) error } payload.EdgeGroups = edgeGroups - deploymentType, err := request.RetrieveNumericMultiPartFormValue(r, "DeploymentType", true) + deploymentType, err := request.RetrieveNumericMultiPartFormValue(r, "DeploymentType", false) if err != nil { return &InvalidPayloadError{msg: "Invalid deployment type"} } payload.DeploymentType = portainer.EdgeStackDeploymentType(deploymentType) var registries []portainer.RegistryID - request.RetrieveMultiPartFormJSONValue(r, "Registries", ®istries, false) + err = request.RetrieveMultiPartFormJSONValue(r, "Registries", ®istries, true) if err != nil { return errors.New("Invalid registry type") } @@ -318,6 +330,27 @@ func (payload *swarmStackFromFileUploadPayload) Validate(r *http.Request) error return nil } +// @id EdgeStackCreateFile +// @summary Create an EdgeStack from file +// @description **Access policy**: administrator +// @tags edge_stacks +// @security ApiKeyAuth +// @security jwt +// @accept multipart/form-data +// @produce json +// @param Name formData string true "Name of the stack" +// @param file formData file true "Content of the Stack file" +// @param EdgeGroups formData string true "JSON stringified array of Edge Groups ids" +// @param DeploymentType formData int true "deploy type 0 - 'compose', 1 - 'kubernetes', 2 - 'nomad'" +// @param Registries formData string false "JSON stringified array of Registry ids to use for this stack" +// @param UseManifestNamespaces formData bool false "Uses the manifest's namespaces instead of the default one, relevant only for kube environments" +// @param PrePullImage formData bool false "Pre Pull image" +// @param RetryDeploy formData bool false "Retry deploy" +// @param dryrun query string false "if true, will not create an edge stack, but just will check the settings and return a non-persisted edge stack object" +// @success 200 {object} portainer.EdgeStack +// @failure 500 +// @failure 503 "Edge compute features are disabled" +// @router /edge_stacks/create/file [post] func (handler *Handler) createSwarmStackFromFileUpload(r *http.Request, dryrun bool) (*portainer.EdgeStack, error) { payload := &swarmStackFromFileUploadPayload{} err := payload.Validate(r) diff --git a/api/http/handler/edgestacks/edgestack_status_delete.go b/api/http/handler/edgestacks/edgestack_status_delete.go index a5bd29052..2e857d8bf 100644 --- a/api/http/handler/edgestacks/edgestack_status_delete.go +++ b/api/http/handler/edgestacks/edgestack_status_delete.go @@ -16,12 +16,13 @@ import ( // @tags edge_stacks // @produce json // @param id path int true "EdgeStack Id" +// @param environmentId path int true "Environment identifier" // @success 200 {object} portainer.EdgeStack // @failure 500 // @failure 400 // @failure 404 // @failure 403 -// @router /edge_stacks/{id}/status/{endpoint_id} [delete] +// @router /edge_stacks/{id}/status/{environmentId} [delete] func (handler *Handler) edgeStackStatusDelete(w http.ResponseWriter, r *http.Request) *httperror.HandlerError { stackID, err := request.RetrieveNumericRouteVariableValue(r, "id") if err != nil { diff --git a/api/http/handler/edgestacks/edgestack_test.go b/api/http/handler/edgestacks/edgestack_test.go index 2036ccc70..ebc6917b7 100644 --- a/api/http/handler/edgestacks/edgestack_test.go +++ b/api/http/handler/edgestacks/edgestack_test.go @@ -237,7 +237,7 @@ func TestCreateAndInspect(t *testing.T) { r := bytes.NewBuffer(jsonPayload) // Create EdgeStack - req, err := http.NewRequest(http.MethodPost, "/edge_stacks?method=string", r) + req, err := http.NewRequest(http.MethodPost, "/edge_stacks/create/string", r) if err != nil { t.Fatal("request error:", err) } @@ -290,37 +290,32 @@ func TestCreateWithInvalidPayload(t *testing.T) { cases := []struct { Name string Payload interface{} - QueryString string ExpectedStatusCode int + Method string }{ { - Name: "Invalid query string parameter", + Name: "Invalid method parameter", Payload: swarmStackFromFileContentPayload{}, - QueryString: "invalid=query-string", + Method: "invalid", ExpectedStatusCode: 400, }, - { - Name: "Invalid creation method", - Payload: swarmStackFromFileContentPayload{}, - QueryString: "method=invalid-creation-method", - ExpectedStatusCode: 500, - }, + { Name: "Empty swarmStackFromFileContentPayload with string method", Payload: swarmStackFromFileContentPayload{}, - QueryString: "method=string", + Method: "string", ExpectedStatusCode: 400, }, { Name: "Empty swarmStackFromFileContentPayload with repository method", Payload: swarmStackFromFileContentPayload{}, - QueryString: "method=repository", + Method: "repository", ExpectedStatusCode: 400, }, { Name: "Empty swarmStackFromFileContentPayload with file method", Payload: swarmStackFromFileContentPayload{}, - QueryString: "method=file", + Method: "file", ExpectedStatusCode: 400, }, { @@ -331,7 +326,7 @@ func TestCreateWithInvalidPayload(t *testing.T) { EdgeGroups: edgeStack.EdgeGroups, DeploymentType: edgeStack.DeploymentType, }, - QueryString: "method=string", + Method: "string", ExpectedStatusCode: 500, }, { @@ -342,7 +337,7 @@ func TestCreateWithInvalidPayload(t *testing.T) { EdgeGroups: []portainer.EdgeGroupID{}, DeploymentType: edgeStack.DeploymentType, }, - QueryString: "method=string", + Method: "string", ExpectedStatusCode: 400, }, { @@ -353,7 +348,7 @@ func TestCreateWithInvalidPayload(t *testing.T) { EdgeGroups: []portainer.EdgeGroupID{1}, DeploymentType: portainer.EdgeStackDeploymentKubernetes, }, - QueryString: "method=string", + Method: "string", ExpectedStatusCode: 500, }, { @@ -364,7 +359,7 @@ func TestCreateWithInvalidPayload(t *testing.T) { EdgeGroups: []portainer.EdgeGroupID{1}, DeploymentType: portainer.EdgeStackDeploymentCompose, }, - QueryString: "method=string", + Method: "string", ExpectedStatusCode: 400, }, { @@ -380,7 +375,7 @@ func TestCreateWithInvalidPayload(t *testing.T) { EdgeGroups: []portainer.EdgeGroupID{1}, DeploymentType: portainer.EdgeStackDeploymentCompose, }, - QueryString: "method=repository", + Method: "repository", ExpectedStatusCode: 500, }, } @@ -394,7 +389,7 @@ func TestCreateWithInvalidPayload(t *testing.T) { r := bytes.NewBuffer(jsonPayload) // Create EdgeStack - req, err := http.NewRequest(http.MethodPost, fmt.Sprintf("/edge_stacks?%s", tc.QueryString), r) + req, err := http.NewRequest(http.MethodPost, fmt.Sprintf("/edge_stacks/create/%s", tc.Method), r) if err != nil { t.Fatal("request error:", err) } diff --git a/api/http/handler/edgestacks/handler.go b/api/http/handler/edgestacks/handler.go index b7eb8cf23..141eec911 100644 --- a/api/http/handler/edgestacks/handler.go +++ b/api/http/handler/edgestacks/handler.go @@ -34,7 +34,7 @@ func NewHandler(bouncer *security.RequestBouncer, dataStore dataservices.DataSto edgeStacksService: edgeStacksService, } - h.Handle("/edge_stacks", + h.Handle("/edge_stacks/create/{method}", bouncer.AdminAccess(bouncer.EdgeComputeOperation(httperror.LoggerHandler(h.edgeStackCreate)))).Methods(http.MethodPost) h.Handle("/edge_stacks", bouncer.AdminAccess(bouncer.EdgeComputeOperation(httperror.LoggerHandler(h.edgeStackList)))).Methods(http.MethodGet) diff --git a/api/http/handler/endpoints/endpoint_create.go b/api/http/handler/endpoints/endpoint_create.go index d9be746ef..20c1081c0 100644 --- a/api/http/handler/endpoints/endpoint_create.go +++ b/api/http/handler/endpoints/endpoint_create.go @@ -192,7 +192,7 @@ func (payload *endpointCreatePayload) Validate(r *http.Request) error { // @param TagIds formData []int false "List of tag identifiers to which this environment(endpoint) is associated" // @param EdgeCheckinInterval formData int false "The check in interval for edge agent (in seconds)" // @param EdgeTunnelServerAddress formData string true "URL or IP address that will be used to establish a reverse tunnel" -// @param Gpus formData array false "List of GPUs" +// @param Gpus formData string false "List of GPUs - json stringified array of {name, value} structs" // @success 200 {object} portainer.Endpoint "Success" // @failure 400 "Invalid request" // @failure 500 "Server error" diff --git a/api/http/handler/hostmanagement/fdo/configure.go b/api/http/handler/hostmanagement/fdo/configure.go index 05cf2221f..823788c17 100644 --- a/api/http/handler/hostmanagement/fdo/configure.go +++ b/api/http/handler/hostmanagement/fdo/configure.go @@ -54,6 +54,7 @@ func (payload *deviceConfigurePayload) Validate(r *http.Request) error { // @tags intel // @security jwt // @produce json +// @param guid path int true "Guid" // @param body body deviceConfigurePayload true "Device Configuration" // @success 200 "Success" // @failure 400 "Invalid request" diff --git a/api/http/handler/hostmanagement/fdo/profile_delete.go b/api/http/handler/hostmanagement/fdo/profile_delete.go index eadd5ab26..18f380547 100644 --- a/api/http/handler/hostmanagement/fdo/profile_delete.go +++ b/api/http/handler/hostmanagement/fdo/profile_delete.go @@ -16,6 +16,7 @@ import ( // @description **Access policy**: administrator // @tags intel // @security jwt +// @param id path int true "FDO Profile identifier" // @produce json // @success 200 "Success" // @failure 400 "Invalid request" diff --git a/api/http/handler/hostmanagement/fdo/profile_duplicate.go b/api/http/handler/hostmanagement/fdo/profile_duplicate.go index 071041ec5..35f7a4231 100644 --- a/api/http/handler/hostmanagement/fdo/profile_duplicate.go +++ b/api/http/handler/hostmanagement/fdo/profile_duplicate.go @@ -20,6 +20,7 @@ import ( // @tags intel // @security jwt // @produce json +// @param id path int true "FDO Profile identifier" // @success 200 "Success" // @failure 400 "Invalid request" // @failure 500 "Server error" diff --git a/api/http/handler/hostmanagement/fdo/profile_inspect.go b/api/http/handler/hostmanagement/fdo/profile_inspect.go index 5d35e6908..00187f7e7 100644 --- a/api/http/handler/hostmanagement/fdo/profile_inspect.go +++ b/api/http/handler/hostmanagement/fdo/profile_inspect.go @@ -22,6 +22,7 @@ type fdoProfileResponse struct { // @tags intel // @security jwt // @produce json +// @param id path int true "FDO Profile identifier" // @success 200 "Success" // @failure 400 "Invalid request" // @failure 500 "Server error" diff --git a/api/http/handler/hostmanagement/fdo/profile_update.go b/api/http/handler/hostmanagement/fdo/profile_update.go index 777772523..9c5367be9 100644 --- a/api/http/handler/hostmanagement/fdo/profile_update.go +++ b/api/http/handler/hostmanagement/fdo/profile_update.go @@ -19,6 +19,7 @@ import ( // @tags intel // @security jwt // @produce json +// @param id path int true "FDO Profile identifier" // @success 200 "Success" // @failure 400 "Invalid request" // @failure 409 "Profile name already exists" diff --git a/api/http/handler/hostmanagement/openamt/amtactivation.go b/api/http/handler/hostmanagement/openamt/amtactivation.go index e2c8f0922..d3dab5e43 100644 --- a/api/http/handler/hostmanagement/openamt/amtactivation.go +++ b/api/http/handler/hostmanagement/openamt/amtactivation.go @@ -20,7 +20,7 @@ import ( // @tags intel // @security jwt // @produce json -// @param id path int true "Environment(Endpoint) identifier" +// @param id path int true "Environment identifier" // @success 200 "Success" // @failure 400 "Invalid request" // @failure 403 "Permission denied to access settings" diff --git a/api/http/handler/hostmanagement/openamt/amtdevices.go b/api/http/handler/hostmanagement/openamt/amtdevices.go index 6df0db2c3..69c474e83 100644 --- a/api/http/handler/hostmanagement/openamt/amtdevices.go +++ b/api/http/handler/hostmanagement/openamt/amtdevices.go @@ -80,6 +80,8 @@ func (payload *deviceActionPayload) Validate(r *http.Request) error { // @security jwt // @accept json // @produce json +// @param id path int true "Environment identifier" +// @param deviceId path int true "Device identifier" // @param body body deviceActionPayload true "Device Action" // @success 204 "Success" // @failure 400 "Invalid request" @@ -140,6 +142,8 @@ type AuthorizationResponse struct { // @security jwt // @accept json // @produce json +// @param id path int true "Environment identifier" +// @param deviceId path int true "Device identifier" // @param body body deviceFeaturesPayload true "Device Features" // @success 204 "Success" // @failure 400 "Invalid request" diff --git a/api/http/handler/hostmanagement/openamt/amtrpc.go b/api/http/handler/hostmanagement/openamt/amtrpc.go index a0c2ccbf2..f15763a8b 100644 --- a/api/http/handler/hostmanagement/openamt/amtrpc.go +++ b/api/http/handler/hostmanagement/openamt/amtrpc.go @@ -48,6 +48,7 @@ const ( // @tags intel // @security jwt // @produce json +// @param id path int true "Environment identifier" // @success 200 "Success" // @failure 400 "Invalid request" // @failure 403 "Permission denied to access settings" diff --git a/api/http/handler/stacks/create_compose_stack.go b/api/http/handler/stacks/create_compose_stack.go index 912da7761..cff765e93 100644 --- a/api/http/handler/stacks/create_compose_stack.go +++ b/api/http/handler/stacks/create_compose_stack.go @@ -24,7 +24,7 @@ type composeStackFromFileContentPayload struct { Name string `example:"myStack" validate:"required"` // Content of the Stack file StackFileContent string `example:"version: 3\n services:\n web:\n image:nginx" validate:"required"` - // A list of environment(endpoint) variables used during stack deployment + // A list of environment variables used during stack deployment Env []portainer.Pair // Whether the stack is from a app template FromAppTemplate bool `example:"false"` @@ -87,6 +87,21 @@ func (handler *Handler) checkAndCleanStackDupFromSwarm(w http.ResponseWriter, r return nil } +// @id StackCreateDockerStandaloneString +// @summary Deploy a new compose stack from a text +// @description Deploy a new stack into a Docker environment specified via the environment identifier. +// @description **Access policy**: authenticated +// @tags stacks +// @security ApiKeyAuth +// @security jwt +// @accept json +// @produce json +// @param body body composeStackFromFileContentPayload true "stack config" +// @param endpointId query int true "Identifier of the environment that will be used to deploy the stack" +// @success 200 {object} portainer.Stack +// @failure 400 "Invalid request" +// @failure 500 "Server error" +// @router /stacks/create/standalone/string [post] func (handler *Handler) createComposeStackFromFileContent(w http.ResponseWriter, r *http.Request, endpoint *portainer.Endpoint, userID portainer.UserID) *httperror.HandlerError { var payload composeStackFromFileContentPayload err := request.DecodeAndValidateJSONPayload(r, &payload) @@ -158,7 +173,7 @@ type composeStackFromGitRepositoryPayload struct { AdditionalFiles []string `example:"[nz.compose.yml, uat.compose.yml]"` // Optional auto update configuration AutoUpdate *portainer.AutoUpdateSettings - // A list of environment(endpoint) variables used during stack deployment + // A list of environment variables used during stack deployment Env []portainer.Pair // Whether the stack is from a app template FromAppTemplate bool `example:"false"` @@ -201,6 +216,21 @@ func (payload *composeStackFromGitRepositoryPayload) Validate(r *http.Request) e return nil } +// @id StackCreateDockerStandaloneRepository +// @summary Deploy a new compose stack from repository +// @description Deploy a new stack into a Docker environment specified via the environment identifier. +// @description **Access policy**: authenticated +// @tags stacks +// @security ApiKeyAuth +// @security jwt +// @produce json +// @accept json +// @param endpointId query int true "Identifier of the environment that will be used to deploy the stack" +// @param body body composeStackFromGitRepositoryPayload true "stack config" +// @success 200 {object} portainer.Stack +// @failure 400 "Invalid request" +// @failure 500 "Server error" +// @router /stacks/create/standalone/repository [post] func (handler *Handler) createComposeStackFromGitRepository(w http.ResponseWriter, r *http.Request, endpoint *portainer.Endpoint, userID portainer.UserID) *httperror.HandlerError { var payload composeStackFromGitRepositoryPayload err := request.DecodeAndValidateJSONPayload(r, &payload) @@ -318,6 +348,23 @@ func decodeRequestForm(r *http.Request) (*composeStackFromFileUploadPayload, err return payload, nil } +// @id StackCreateDockerStandaloneFile +// @summary Deploy a new compose stack from a file +// @description Deploy a new stack into a Docker environment specified via the environment identifier. +// @description **Access policy**: authenticated +// @tags stacks +// @security ApiKeyAuth +// @security jwt +// @accept multipart/form-data +// @produce json +// @param Name formData string true "Name of the stack" +// @param Env formData string false "Environment variables passed during deployment, represented as a JSON array [{'name': 'name', 'value': 'value'}]." +// @param file formData file false "Stack file" +// @param endpointId query int true "Identifier of the environment that will be used to deploy the stack" +// @success 200 {object} portainer.Stack +// @failure 400 "Invalid request" +// @failure 500 "Server error" +// @router /stacks/create/standalone/file [post] func (handler *Handler) createComposeStackFromFileUpload(w http.ResponseWriter, r *http.Request, endpoint *portainer.Endpoint, userID portainer.UserID) *httperror.HandlerError { payload, err := decodeRequestForm(r) if err != nil { diff --git a/api/http/handler/stacks/create_kubernetes_stack.go b/api/http/handler/stacks/create_kubernetes_stack.go index 7d38b66b2..490ad7988 100644 --- a/api/http/handler/stacks/create_kubernetes_stack.go +++ b/api/http/handler/stacks/create_kubernetes_stack.go @@ -12,6 +12,7 @@ import ( "github.com/portainer/libhttp/response" portainer "github.com/portainer/portainer/api" "github.com/portainer/portainer/api/git/update" + "github.com/portainer/portainer/api/internal/endpointutils" k "github.com/portainer/portainer/api/kubernetes" "github.com/portainer/portainer/api/stacks/deployments" "github.com/portainer/portainer/api/stacks/stackbuilders" @@ -131,7 +132,25 @@ type createKubernetesStackResponse struct { Output string `json:"Output"` } +// @id StackCreateKubernetesFile +// @summary Deploy a new kubernetes stack from a file +// @description Deploy a new stack into a Docker environment specified via the environment identifier. +// @description **Access policy**: authenticated +// @tags stacks +// @security ApiKeyAuth +// @security jwt +// @produce json +// @param body body kubernetesStringDeploymentPayload true "stack config" +// @param endpointId query int true "Identifier of the environment that will be used to deploy the stack" +// @success 200 {object} portainer.Stack +// @failure 400 "Invalid request" +// @failure 500 "Server error" +// @router /stacks/create/kubernetes/string [post] func (handler *Handler) createKubernetesStackFromFileContent(w http.ResponseWriter, r *http.Request, endpoint *portainer.Endpoint, userID portainer.UserID) *httperror.HandlerError { + if !endpointutils.IsKubernetesEndpoint(endpoint) { + return httperror.BadRequest("Environment type does not match", errors.New("Environment type does not match")) + } + var payload kubernetesStringDeploymentPayload if err := request.DecodeAndValidateJSONPayload(r, &payload); err != nil { return httperror.BadRequest("Invalid request payload", err) @@ -170,7 +189,25 @@ func (handler *Handler) createKubernetesStackFromFileContent(w http.ResponseWrit return response.JSON(w, resp) } +// @id StackCreateKubernetesGit +// @summary Deploy a new kubernetes stack from a git repository +// @description Deploy a new stack into a Docker environment specified via the environment identifier. +// @description **Access policy**: authenticated +// @tags stacks +// @security ApiKeyAuth +// @security jwt +// @produce json +// @param body body kubernetesGitDeploymentPayload true "stack config" +// @param endpointId query int true "Identifier of the environment that will be used to deploy the stack" +// @success 200 {object} portainer.Stack +// @failure 400 "Invalid request" +// @failure 500 "Server error" +// @router /stacks/create/kubernetes/repository [post] func (handler *Handler) createKubernetesStackFromGitRepository(w http.ResponseWriter, r *http.Request, endpoint *portainer.Endpoint, userID portainer.UserID) *httperror.HandlerError { + if !endpointutils.IsKubernetesEndpoint(endpoint) { + return httperror.BadRequest("Environment type does not match", errors.New("Environment type does not match")) + } + var payload kubernetesGitDeploymentPayload if err := request.DecodeAndValidateJSONPayload(r, &payload); err != nil { return httperror.BadRequest("Invalid request payload", err) @@ -234,6 +271,20 @@ func (handler *Handler) createKubernetesStackFromGitRepository(w http.ResponseWr return response.JSON(w, resp) } +// @id StackCreateKubernetesUrl +// @summary Deploy a new kubernetes stack from a url +// @description Deploy a new stack into a Docker environment specified via the environment identifier. +// @description **Access policy**: authenticated +// @tags stacks +// @security ApiKeyAuth +// @security jwt +// @produce json +// @param body body kubernetesManifestURLDeploymentPayload true "stack config" +// @param endpointId query int true "Identifier of the environment that will be used to deploy the stack" +// @success 200 {object} portainer.Stack +// @failure 400 "Invalid request" +// @failure 500 "Server error" +// @router /stacks/create/kubernetes/url [post] func (handler *Handler) createKubernetesStackFromManifestURL(w http.ResponseWriter, r *http.Request, endpoint *portainer.Endpoint, userID portainer.UserID) *httperror.HandlerError { var payload kubernetesManifestURLDeploymentPayload if err := request.DecodeAndValidateJSONPayload(r, &payload); err != nil { diff --git a/api/http/handler/stacks/create_swarm_stack.go b/api/http/handler/stacks/create_swarm_stack.go index 031c518c9..93c90d925 100644 --- a/api/http/handler/stacks/create_swarm_stack.go +++ b/api/http/handler/stacks/create_swarm_stack.go @@ -23,7 +23,7 @@ type swarmStackFromFileContentPayload struct { SwarmID string `example:"jpofkc0i9uo9wtx1zesuk649w" validate:"required"` // Content of the Stack file StackFileContent string `example:"version: 3\n services:\n web:\n image:nginx" validate:"required"` - // A list of environment(endpoint) variables used during stack deployment + // A list of environment variables used during stack deployment Env []portainer.Pair // Whether the stack is from a app template FromAppTemplate bool `example:"false"` @@ -52,6 +52,21 @@ func createStackPayloadFromSwarmFileContentPayload(name string, swarmID string, } } +// @id StackCreateDockerSwarmString +// @summary Deploy a new swarm stack from a text +// @description Deploy a new stack into a Docker environment specified via the environment identifier. +// @description **Access policy**: authenticated +// @tags stacks +// @security ApiKeyAuth +// @security jwt +// @accept json +// @produce json +// @param body body swarmStackFromFileContentPayload true "stack config" +// @param endpointId query int true "Identifier of the environment that will be used to deploy the stack" +// @success 200 {object} portainer.Stack +// @failure 400 "Invalid request" +// @failure 500 "Server error" +// @router /stacks/create/swarm/string [post] func (handler *Handler) createSwarmStackFromFileContent(w http.ResponseWriter, r *http.Request, endpoint *portainer.Endpoint, userID portainer.UserID) *httperror.HandlerError { var payload swarmStackFromFileContentPayload err := request.DecodeAndValidateJSONPayload(r, &payload) @@ -96,7 +111,7 @@ type swarmStackFromGitRepositoryPayload struct { Name string `example:"myStack" validate:"required"` // Swarm cluster identifier SwarmID string `example:"jpofkc0i9uo9wtx1zesuk649w" validate:"required"` - // A list of environment(endpoint) variables used during stack deployment + // A list of environment variables used during stack deployment Env []portainer.Pair // URL of a Git repository hosting the Stack file @@ -159,6 +174,21 @@ func createStackPayloadFromSwarmGitPayload(name, swarmID, repoUrl, repoReference } } +// @id StackCreateDockerSwarmRepository +// @summary Deploy a new swarm stack from a git repository +// @description Deploy a new stack into a Docker environment specified via the environment identifier. +// @description **Access policy**: authenticated +// @tags stacks +// @security ApiKeyAuth +// @security jwt +// @produce json +// @accept json +// @param endpointId query int true "Identifier of the environment that will be used to deploy the stack" +// @param body body swarmStackFromGitRepositoryPayload true "stack config" +// @success 200 {object} portainer.Stack +// @failure 400 "Invalid request" +// @failure 500 "Server error" +// @router /stacks/create/swarm/repository [post] func (handler *Handler) createSwarmStackFromGitRepository(w http.ResponseWriter, r *http.Request, endpoint *portainer.Endpoint, userID portainer.UserID) *httperror.HandlerError { var payload swarmStackFromGitRepositoryPayload err := request.DecodeAndValidateJSONPayload(r, &payload) @@ -267,8 +297,26 @@ func (payload *swarmStackFromFileUploadPayload) Validate(r *http.Request) error return nil } +// @id StackCreateDockerSwarmFile +// @summary Deploy a new swarm stack from a file +// @description Deploy a new stack into a Docker environment specified via the environment identifier. +// @description **Access policy**: authenticated +// @tags stacks +// @security ApiKeyAuth +// @security jwt +// @accept multipart/form-data +// @produce json +// @param Name formData string false "Name of the stack" +// @param SwarmID formData string false "Swarm cluster identifier." +// @param Env formData string false "Environment variables passed during deployment, represented as a JSON array [{'name': 'name', 'value': 'value'}]. Optional" +// @param file formData file false "Stack file" +// @param endpointId query int true "Identifier of the environment that will be used to deploy the stack" +// @success 200 {object} portainer.Stack +// @failure 400 "Invalid request" +// @failure 500 "Server error" +// @router /stacks/create/swarm/file [post] func (handler *Handler) createSwarmStackFromFileUpload(w http.ResponseWriter, r *http.Request, endpoint *portainer.Endpoint, userID portainer.UserID) *httperror.HandlerError { - payload := &swarmStackFromFileUploadPayload{} + var payload swarmStackFromFileUploadPayload err := payload.Validate(r) if err != nil { return httperror.BadRequest("Invalid request payload", err) diff --git a/api/http/handler/stacks/handler.go b/api/http/handler/stacks/handler.go index 66e0450aa..caec10ab3 100644 --- a/api/http/handler/stacks/handler.go +++ b/api/http/handler/stacks/handler.go @@ -55,7 +55,8 @@ func NewHandler(bouncer *security.RequestBouncer) *Handler { stackDeletionMutex: &sync.Mutex{}, requestBouncer: bouncer, } - h.Handle("/stacks", + + h.Handle("/stacks/create/{type}/{method}", bouncer.AuthenticatedAccess(httperror.LoggerHandler(h.stackCreate))).Methods(http.MethodPost) h.Handle("/stacks", bouncer.AuthenticatedAccess(httperror.LoggerHandler(h.stackList))).Methods(http.MethodGet) diff --git a/api/http/handler/stacks/stack_create.go b/api/http/handler/stacks/stack_create.go index ee2fb1e7d..a391bc75f 100644 --- a/api/http/handler/stacks/stack_create.go +++ b/api/http/handler/stacks/stack_create.go @@ -13,42 +13,15 @@ import ( "github.com/portainer/portainer/api/stacks/stackutils" ) -// @id StackCreate -// @summary Deploy a new stack -// @description Deploy a new stack into a Docker environment(endpoint) specified via the environment(endpoint) identifier. -// @description **Access policy**: authenticated -// @tags stacks -// @security ApiKeyAuth -// @security jwt -// @accept json,multipart/form-data -// @produce json -// @param type query int true "Stack deployment type. Possible values: 1 (Swarm stack), 2 (Compose stack) or 3 (Kubernetes stack)." Enums(1,2,3) -// @param method query string true "Stack deployment method. Possible values: file, string, repository or url." Enums(string, file, repository, url) -// @param endpointId query int true "Identifier of the environment(endpoint) that will be used to deploy the stack" -// @param body_swarm_string body swarmStackFromFileContentPayload false "Required when using method=string and type=1" -// @param body_swarm_repository body swarmStackFromGitRepositoryPayload false "Required when using method=repository and type=1" -// @param body_compose_string body composeStackFromFileContentPayload false "Required when using method=string and type=2" -// @param body_compose_repository body composeStackFromGitRepositoryPayload false "Required when using method=repository and type=2" -// @param body_kubernetes_string body kubernetesStringDeploymentPayload false "Required when using method=string and type=3" -// @param body_kubernetes_repository body kubernetesGitDeploymentPayload false "Required when using method=repository and type=3" -// @param body_kubernetes_url body kubernetesManifestURLDeploymentPayload false "Required when using method=url and type=3" -// @param Name formData string false "Name of the stack. required when method is file" -// @param SwarmID formData string false "Swarm cluster identifier. Required when method equals file and type equals 1. required when method is file" -// @param Env formData string false "Environment(Endpoint) variables passed during deployment, represented as a JSON array [{'name': 'name', 'value': 'value'}]. Optional, used when method equals file and type equals 1." -// @param file formData file false "Stack file. required when method is file" -// @success 200 {object} portainer.CustomTemplate -// @failure 400 "Invalid request" -// @failure 500 "Server error" -// @router /stacks [post] func (handler *Handler) stackCreate(w http.ResponseWriter, r *http.Request) *httperror.HandlerError { - stackType, err := request.RetrieveNumericQueryParameter(r, "type", false) + stackType, err := request.RetrieveRouteVariableValue(r, "type") if err != nil { - return httperror.BadRequest("Invalid query parameter: type", err) + return httperror.BadRequest("Invalid path parameter: type", err) } - method, err := request.RetrieveQueryParameter(r, "method", false) + method, err := request.RetrieveRouteVariableValue(r, "method") if err != nil { - return httperror.BadRequest("Invalid query parameter: method", err) + return httperror.BadRequest("Invalid path parameter: method", err) } endpointID, err := request.RetrieveNumericQueryParameter(r, "endpointId", false) @@ -87,12 +60,12 @@ func (handler *Handler) stackCreate(w http.ResponseWriter, r *http.Request) *htt return httperror.InternalServerError("Unable to retrieve user details from authentication token", err) } - switch portainer.StackType(stackType) { - case portainer.DockerSwarmStack: + switch stackType { + case "swarm": return handler.createSwarmStack(w, r, method, endpoint, tokenData.ID) - case portainer.DockerComposeStack: + case "standalone": return handler.createComposeStack(w, r, method, endpoint, tokenData.ID) - case portainer.KubernetesStack: + case "kubernetes": return handler.createKubernetesStack(w, r, method, endpoint, tokenData.ID) } diff --git a/api/http/handler/webhooks/webhook_execute.go b/api/http/handler/webhooks/webhook_execute.go index 511448270..e8b156055 100644 --- a/api/http/handler/webhooks/webhook_execute.go +++ b/api/http/handler/webhooks/webhook_execute.go @@ -20,11 +20,11 @@ import ( // @description Acts on a passed in token UUID to restart the docker service // @description **Access policy**: public // @tags webhooks -// @param token path string true "Webhook token" +// @param id path string true "Webhook token" // @success 202 "Webhook executed" // @failure 400 // @failure 500 -// @router /webhooks/{token} [post] +// @router /webhooks/{id} [post] func (handler *Handler) webhookExecute(w http.ResponseWriter, r *http.Request) *httperror.HandlerError { webhookToken, err := request.RetrieveRouteVariableValue(r, "token") diff --git a/api/http/handler/webhooks/webhook_update.go b/api/http/handler/webhooks/webhook_update.go index 089036ae6..0f7436170 100644 --- a/api/http/handler/webhooks/webhook_update.go +++ b/api/http/handler/webhooks/webhook_update.go @@ -28,6 +28,7 @@ func (payload *webhookUpdatePayload) Validate(r *http.Request) error { // @tags webhooks // @accept json // @produce json +// @param id path int true "Webhook id" // @param body body webhookUpdatePayload true "Webhook data" // @success 200 {object} portainer.Webhook // @failure 400 diff --git a/app/edge/rest/edge-jobs.js b/app/edge/rest/edge-jobs.js index 0cd8747dc..05a3f48d8 100644 --- a/app/edge/rest/edge-jobs.js +++ b/app/edge/rest/edge-jobs.js @@ -5,7 +5,7 @@ function EdgeJobsFactory($resource, API_ENDPOINT_EDGE_JOBS) { API_ENDPOINT_EDGE_JOBS + '/:id/:action', {}, { - create: { method: 'POST' }, + create: { method: 'POST', params: { id: 'create', action: '@method' } }, query: { method: 'GET', isArray: true }, get: { method: 'GET', params: { id: '@id' } }, update: { method: 'PUT', params: { id: '@id' } }, diff --git a/app/edge/rest/edge-stacks.js b/app/edge/rest/edge-stacks.js index 5f5345061..9ae34b432 100644 --- a/app/edge/rest/edge-stacks.js +++ b/app/edge/rest/edge-stacks.js @@ -5,7 +5,7 @@ angular.module('portainer.edge').factory('EdgeStacks', function EdgeStacksFactor API_ENDPOINT_EDGE_STACKS + '/:id/:action', {}, { - create: { method: 'POST', ignoreLoadingBar: true }, + create: { method: 'POST', ignoreLoadingBar: true, params: { id: 'create', action: '@method' } }, query: { method: 'GET', isArray: true }, get: { method: 'GET', params: { id: '@id' } }, update: { method: 'PUT', params: { id: '@id' } }, diff --git a/app/edge/services/edge-job.js b/app/edge/services/edge-job.js index c0629a34b..fd36e1f63 100644 --- a/app/edge/services/edge-job.js +++ b/app/edge/services/edge-job.js @@ -49,7 +49,7 @@ function EdgeJobService(EdgeJobs, EdgeJobResults, FileUploadService) { service.createEdgeJobFromFileContent = function (model) { var payload = new ScheduleCreateRequest(model); - return EdgeJobs.create({ method: 'string' }, payload).$promise; + return EdgeJobs.create({}, { method: 'string', ...payload }).$promise; }; service.createEdgeJobFromFileUpload = function (model) { diff --git a/app/edge/services/edge-stack.js b/app/edge/services/edge-stack.js index b325fe960..ecacda4d6 100644 --- a/app/edge/services/edge-stack.js +++ b/app/edge/services/edge-stack.js @@ -30,7 +30,7 @@ angular.module('portainer.edge').factory('EdgeStackService', function EdgeStackS service.createStackFromFileContent = async function createStackFromFileContent(payload) { try { - return await EdgeStacks.create({ method: 'string' }, payload).$promise; + return await EdgeStacks.create({}, { method: 'string', ...payload }).$promise; } catch (err) { throw { msg: 'Unable to create the stack', err }; } @@ -47,9 +47,10 @@ angular.module('portainer.edge').factory('EdgeStackService', function EdgeStackS service.createStackFromGitRepository = async function createStackFromGitRepository(payload, repositoryOptions) { try { return await EdgeStacks.create( - { method: 'repository' }, + {}, { ...payload, + method: 'repository', RepositoryURL: repositoryOptions.RepositoryURL, RepositoryReferenceName: repositoryOptions.RepositoryReferenceName, FilePathInRepository: repositoryOptions.FilePathInRepository, diff --git a/app/portainer/rest/customTemplate.js b/app/portainer/rest/customTemplate.js index a6d00bbf1..f7123552e 100644 --- a/app/portainer/rest/customTemplate.js +++ b/app/portainer/rest/customTemplate.js @@ -7,7 +7,7 @@ function CustomTemplatesFactory($resource, API_ENDPOINT_CUSTOM_TEMPLATES) { API_ENDPOINT_CUSTOM_TEMPLATES + '/:id/:action', {}, { - create: { method: 'POST', ignoreLoadingBar: true }, + create: { method: 'POST', ignoreLoadingBar: true, params: { id: 'create', action: '@method' } }, query: { method: 'GET', isArray: true }, get: { method: 'GET', params: { id: '@id' } }, update: { method: 'PUT', params: { id: '@id' } }, diff --git a/app/portainer/rest/stack.js b/app/portainer/rest/stack.js index 9410caf85..d1c749367 100644 --- a/app/portainer/rest/stack.js +++ b/app/portainer/rest/stack.js @@ -10,7 +10,7 @@ function StackFactory($resource, API_ENDPOINT_STACKS) { { get: { method: 'GET', params: { id: '@id' } }, query: { method: 'GET', isArray: true }, - create: { method: 'POST', ignoreLoadingBar: true }, + create: { method: 'POST', ignoreLoadingBar: true, params: { id: 'create', subaction: '@method', action: '@type' } }, update: { method: 'PUT', params: { id: '@id' }, ignoreLoadingBar: true }, associate: { method: 'PUT', params: { id: '@id', swarmId: '@swarmId', endpointId: '@endpointId', orphanedRunning: '@orphanedRunning', action: 'associate' } }, remove: { method: 'DELETE', params: { id: '@id', external: '@external', endpointId: '@endpointId' } }, diff --git a/app/portainer/services/api/customTemplate.js b/app/portainer/services/api/customTemplate.js index dc6ff8646..0bb620996 100644 --- a/app/portainer/services/api/customTemplate.js +++ b/app/portainer/services/api/customTemplate.js @@ -40,7 +40,7 @@ function CustomTemplateServiceFactory($sanitize, CustomTemplates, FileUploadServ service.createCustomTemplateFromFileContent = async function createCustomTemplateFromFileContent(payload) { try { - return await CustomTemplates.create({ method: 'string' }, payload).$promise; + return await CustomTemplates.create({}, { method: 'string', ...payload }).$promise; } catch (err) { throw { msg: 'Unable to create the customTemplate', err }; } @@ -57,7 +57,7 @@ function CustomTemplateServiceFactory($sanitize, CustomTemplates, FileUploadServ service.createCustomTemplateFromGitRepository = async function createCustomTemplateFromGitRepository(payload) { try { - return await CustomTemplates.create({ method: 'repository' }, payload).$promise; + return await CustomTemplates.create({}, { method: 'repository', ...payload }).$promise; } catch (err) { throw { msg: 'Unable to create the customTemplate', err }; } diff --git a/app/portainer/services/api/stackService.js b/app/portainer/services/api/stackService.js index 2e581bc33..6995c580d 100644 --- a/app/portainer/services/api/stackService.js +++ b/app/portainer/services/api/stackService.js @@ -317,7 +317,7 @@ angular.module('portainer.app').factory('StackService', [ StackFileContent: stackFileContent, Env: env, }; - return Stack.create({ method: 'string', type: 2, endpointId: endpointId }, payload).$promise; + return Stack.create({ endpointId: endpointId }, { method: 'string', type: 'standalone', ...payload }).$promise; }; service.createSwarmStackFromFileContent = function (name, stackFileContent, env, endpointId) { @@ -331,7 +331,7 @@ angular.module('portainer.app').factory('StackService', [ StackFileContent: stackFileContent, Env: env, }; - return Stack.create({ method: 'string', type: 1, endpointId: endpointId }, payload).$promise; + return Stack.create({ endpointId: endpointId }, { method: 'string', type: 'swarm', ...payload }).$promise; }) .then(function success(data) { deferred.resolve(data); @@ -362,7 +362,7 @@ angular.module('portainer.app').factory('StackService', [ payload.AutoUpdate = repositoryOptions.AutoUpdate; } - return Stack.create({ method: 'repository', type: 2, endpointId: endpointId }, payload).$promise; + return Stack.create({ endpointId: endpointId }, { method: 'repository', type: 'standalone', ...payload }).$promise; }; service.createSwarmStackFromGitRepository = function (name, repositoryOptions, env, endpointId) { @@ -390,7 +390,7 @@ angular.module('portainer.app').factory('StackService', [ payload.AutoUpdate = repositoryOptions.AutoUpdate; } - return Stack.create({ method: 'repository', type: 1, endpointId: endpointId }, payload).$promise; + return Stack.create({ endpointId: endpointId }, { method: 'repository', type: 'swarm', ...payload }).$promise; }) .then(function success(data) { deferred.resolve(data); @@ -409,7 +409,7 @@ angular.module('portainer.app').factory('StackService', [ async function kubernetesDeployAsync(endpointId, method, payload) { try { - await Stack.create({ endpointId: endpointId, method: method, type: 3 }, payload).$promise; + await Stack.create({ endpointId: endpointId }, { method, type: 'kubernetes', ...payload }).$promise; } catch (err) { throw { err: err }; } diff --git a/app/portainer/services/fileUpload.js b/app/portainer/services/fileUpload.js index 914efaea0..0dbfec0c6 100644 --- a/app/portainer/services/fileUpload.js +++ b/app/portainer/services/fileUpload.js @@ -65,7 +65,7 @@ angular.module('portainer.app').factory('FileUploadService', [ service.createSchedule = function (payload) { return Upload.upload({ - url: 'api/edge_jobs?method=file', + url: 'api/edge_jobs/create/file', data: { file: payload.File, Name: payload.Name, @@ -90,7 +90,7 @@ angular.module('portainer.app').factory('FileUploadService', [ service.createSwarmStack = function (stackName, swarmId, file, env, endpointId) { return Upload.upload({ - url: 'api/stacks?method=file&type=1&endpointId=' + endpointId, + url: `api/stacks/create/swarm/file?endpointId=${endpointId}`, data: { file: file, Name: stackName, @@ -103,7 +103,7 @@ angular.module('portainer.app').factory('FileUploadService', [ service.createComposeStack = function (stackName, file, env, endpointId) { return Upload.upload({ - url: 'api/stacks?method=file&type=2&endpointId=' + endpointId, + url: `api/stacks/create/standalone/file?endpointId=${endpointId}`, data: { file: file, Name: stackName, @@ -115,7 +115,7 @@ angular.module('portainer.app').factory('FileUploadService', [ service.createEdgeStack = function createEdgeStack({ EdgeGroups, ...payload }, file) { return Upload.upload({ - url: 'api/edge_stacks?method=file', + url: `api/edge_stacks/create/file`, data: { file, EdgeGroups: Upload.json(EdgeGroups), @@ -127,7 +127,7 @@ angular.module('portainer.app').factory('FileUploadService', [ service.createCustomTemplate = function createCustomTemplate(data) { return Upload.upload({ - url: 'api/custom_templates?method=file', + url: 'api/custom_templates/create/file', data, ignoreLoadingBar: true, });