2023-05-04 14:11:19 +00:00
package edgestacks
import (
"fmt"
"net/http"
"github.com/portainer/libhttp/request"
portainer "github.com/portainer/portainer/api"
2023-05-05 23:39:22 +00:00
"github.com/portainer/portainer/api/dataservices"
2023-05-04 14:11:19 +00:00
"github.com/portainer/portainer/api/filesystem"
httperrors "github.com/portainer/portainer/api/http/errors"
2023-05-05 23:39:22 +00:00
"github.com/asaskevich/govalidator"
"github.com/pkg/errors"
2023-05-04 14:11:19 +00:00
)
type edgeStackFromStringPayload struct {
// Name of the stack
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" `
// List of identifiers of EdgeGroups
EdgeGroups [ ] portainer . EdgeGroupID ` example:"1" `
// Deployment type to deploy this stack
2023-05-05 02:01:43 +00:00
// Valid values are: 0 - 'compose', 1 - 'kubernetes'
// compose is enabled only for docker environments
// kubernetes is enabled only for kubernetes environments
2023-05-04 14:11:19 +00:00
DeploymentType portainer . EdgeStackDeploymentType ` example:"0" enums:"0,1,2" `
// List of Registries to use for this stack
Registries [ ] portainer . RegistryID
// Uses the manifest's namespaces instead of the default one
UseManifestNamespaces bool
}
func ( payload * edgeStackFromStringPayload ) Validate ( r * http . Request ) error {
if govalidator . IsNull ( payload . Name ) {
return httperrors . NewInvalidPayloadError ( "Invalid stack name" )
}
2023-05-05 02:01:43 +00:00
2023-05-04 14:11:19 +00:00
if govalidator . IsNull ( payload . StackFileContent ) {
return httperrors . NewInvalidPayloadError ( "Invalid stack file content" )
}
2023-05-05 02:01:43 +00:00
2023-05-04 14:11:19 +00:00
if len ( payload . EdgeGroups ) == 0 {
return httperrors . NewInvalidPayloadError ( "Edge Groups are mandatory for an Edge stack" )
}
2023-05-05 02:01:43 +00:00
if payload . DeploymentType != portainer . EdgeStackDeploymentCompose && payload . DeploymentType != portainer . EdgeStackDeploymentKubernetes {
return httperrors . NewInvalidPayloadError ( "Invalid deployment type" )
}
2023-05-04 14:11:19 +00:00
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 edgeStackFromStringPayload 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 400 "Bad request"
// @failure 500 "Internal server error"
// @failure 503 "Edge compute features are disabled"
// @router /edge_stacks/create/string [post]
2023-05-05 23:39:22 +00:00
func ( handler * Handler ) createEdgeStackFromFileContent ( r * http . Request , tx dataservices . DataStoreTx , dryrun bool ) ( * portainer . EdgeStack , error ) {
2023-05-04 14:11:19 +00:00
var payload edgeStackFromStringPayload
err := request . DecodeAndValidateJSONPayload ( r , & payload )
if err != nil {
return nil , err
}
2023-05-05 23:39:22 +00:00
stack , err := handler . edgeStacksService . BuildEdgeStack ( tx , payload . Name , payload . DeploymentType , payload . EdgeGroups , payload . Registries , payload . UseManifestNamespaces )
2023-05-04 14:11:19 +00:00
if err != nil {
return nil , errors . Wrap ( err , "failed to create Edge stack object" )
}
if dryrun {
return stack , nil
}
2023-05-05 23:39:22 +00:00
return handler . edgeStacksService . PersistEdgeStack ( tx , stack , func ( stackFolder string , relatedEndpointIds [ ] portainer . EndpointID ) ( composePath string , manifestPath string , projectPath string , err error ) {
return handler . storeFileContent ( tx , stackFolder , payload . DeploymentType , relatedEndpointIds , [ ] byte ( payload . StackFileContent ) )
2023-05-04 14:11:19 +00:00
} )
}
2023-05-05 23:39:22 +00:00
func ( handler * Handler ) storeFileContent ( tx dataservices . DataStoreTx , stackFolder string , deploymentType portainer . EdgeStackDeploymentType , relatedEndpointIds [ ] portainer . EndpointID , fileContent [ ] byte ) ( composePath , manifestPath , projectPath string , err error ) {
hasWrongType , err := hasWrongEnvironmentType ( tx . Endpoint ( ) , relatedEndpointIds , deploymentType )
2023-05-05 02:01:43 +00:00
if err != nil {
return "" , "" , "" , fmt . Errorf ( "unable to check for existence of non fitting environments: %w" , err )
}
if hasWrongType {
return "" , "" , "" , fmt . Errorf ( "edge stack with config do not match the environment type" )
}
2023-05-04 14:11:19 +00:00
if deploymentType == portainer . EdgeStackDeploymentCompose {
composePath = filesystem . ComposeFileDefaultName
projectPath , err := handler . FileService . StoreEdgeStackFileFromBytes ( stackFolder , composePath , fileContent )
if err != nil {
return "" , "" , "" , err
}
2023-05-05 02:01:43 +00:00
return composePath , "" , projectPath , nil
2023-05-04 14:11:19 +00:00
}
if deploymentType == portainer . EdgeStackDeploymentKubernetes {
manifestPath = filesystem . ManifestFileDefaultName
projectPath , err := handler . FileService . StoreEdgeStackFileFromBytes ( stackFolder , manifestPath , fileContent )
if err != nil {
return "" , "" , "" , err
}
return "" , manifestPath , projectPath , nil
}
errMessage := fmt . Sprintf ( "invalid deployment type: %d" , deploymentType )
return "" , "" , "" , httperrors . NewInvalidPayloadError ( errMessage )
}