diff --git a/api/http/handler/edgestacks/edgestack_create_file.go b/api/http/handler/edgestacks/edgestack_create_file.go index 60f86ffd2..5b669226a 100644 --- a/api/http/handler/edgestacks/edgestack_create_file.go +++ b/api/http/handler/edgestacks/edgestack_create_file.go @@ -14,11 +14,10 @@ type edgeStackFromFileUploadPayload struct { StackFileContent []byte EdgeGroups []portainer.EdgeGroupID // Deployment type to deploy this stack - // Valid values are: 0 - 'compose', 1 - 'kubernetes', 2 - 'nomad' - // for compose stacks will use kompose to convert to kubernetes manifest for kubernetes environments(endpoints) - // kubernetes deploytype is enabled only for kubernetes environments(endpoints) - // nomad deploytype is enabled only for nomad environments(endpoints) - DeploymentType portainer.EdgeStackDeploymentType `example:"0" enums:"0,1,2"` + // Valid values are: 0 - 'compose', 1 - 'kubernetes' + // compose is enabled only for docker environments + // kubernetes is enabled only for kubernetes environments + DeploymentType portainer.EdgeStackDeploymentType `example:"0" enums:"0,1"` Registries []portainer.RegistryID // Uses the manifest's namespaces instead of the default one UseManifestNamespaces bool @@ -49,6 +48,9 @@ func (payload *edgeStackFromFileUploadPayload) Validate(r *http.Request) error { return httperrors.NewInvalidPayloadError("Invalid deployment type") } payload.DeploymentType = portainer.EdgeStackDeploymentType(deploymentType) + if payload.DeploymentType != portainer.EdgeStackDeploymentCompose && payload.DeploymentType != portainer.EdgeStackDeploymentKubernetes { + return httperrors.NewInvalidPayloadError("Invalid deployment type") + } var registries []portainer.RegistryID err = request.RetrieveMultiPartFormJSONValue(r, "Registries", ®istries, true) diff --git a/api/http/handler/edgestacks/edgestack_create_git.go b/api/http/handler/edgestacks/edgestack_create_git.go index d006e8fa5..a8a4df300 100644 --- a/api/http/handler/edgestacks/edgestack_create_git.go +++ b/api/http/handler/edgestacks/edgestack_create_git.go @@ -31,10 +31,9 @@ type edgeStackFromGitRepositoryPayload struct { // List of identifiers of EdgeGroups EdgeGroups []portainer.EdgeGroupID `example:"1"` // Deployment type to deploy this stack - // Valid values are: 0 - 'compose', 1 - 'kubernetes', 2 - 'nomad' - // for compose stacks will use kompose to convert to kubernetes manifest for kubernetes environments(endpoints) - // kubernetes deploy type is enabled only for kubernetes environments(endpoints) - // nomad deploy type is enabled only for nomad environments(endpoints) + // Valid values are: 0 - 'compose', 1 - 'kubernetes' + // compose is enabled only for docker environments + // kubernetes is enabled only for kubernetes environments DeploymentType portainer.EdgeStackDeploymentType `example:"0" enums:"0,1,2"` // List of Registries to use for this stack Registries []portainer.RegistryID @@ -48,12 +47,19 @@ func (payload *edgeStackFromGitRepositoryPayload) Validate(r *http.Request) erro if govalidator.IsNull(payload.Name) { return httperrors.NewInvalidPayloadError("Invalid stack name") } + if govalidator.IsNull(payload.RepositoryURL) || !govalidator.IsURL(payload.RepositoryURL) { return httperrors.NewInvalidPayloadError("Invalid repository URL. Must correspond to a valid URL format") } + if payload.RepositoryAuthentication && govalidator.IsNull(payload.RepositoryPassword) { return httperrors.NewInvalidPayloadError("Invalid repository credentials. Password must be specified when authentication is enabled") } + + if payload.DeploymentType != portainer.EdgeStackDeploymentCompose && payload.DeploymentType != portainer.EdgeStackDeploymentKubernetes { + return httperrors.NewInvalidPayloadError("Invalid deployment type") + } + if govalidator.IsNull(payload.FilePathInRepository) { switch payload.DeploymentType { case portainer.EdgeStackDeploymentCompose: @@ -62,9 +68,11 @@ func (payload *edgeStackFromGitRepositoryPayload) Validate(r *http.Request) erro payload.FilePathInRepository = filesystem.ManifestFileDefaultName } } + if len(payload.EdgeGroups) == 0 { return httperrors.NewInvalidPayloadError("Invalid edge groups. At least one edge group must be specified") } + return nil } @@ -119,6 +127,14 @@ func (handler *Handler) createEdgeStackFromGitRepository(r *http.Request, dryrun } func (handler *Handler) storeManifestFromGitRepository(stackFolder string, relatedEndpointIds []portainer.EndpointID, deploymentType portainer.EdgeStackDeploymentType, currentUserID portainer.UserID, repositoryConfig gittypes.RepoConfig) (composePath, manifestPath, projectPath string, err error) { + hasWrongType, err := hasWrongEnvironmentType(handler.DataStore.Endpoint(), relatedEndpointIds, deploymentType) + 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") + } + projectPath = handler.FileService.GetEdgeStackProjectPath(stackFolder) repositoryUsername := "" repositoryPassword := "" @@ -133,14 +149,7 @@ func (handler *Handler) storeManifestFromGitRepository(stackFolder string, relat } if deploymentType == portainer.EdgeStackDeploymentCompose { - composePath := repositoryConfig.ConfigFilePath - - manifestPath, err := handler.convertAndStoreKubeManifestIfNeeded(stackFolder, projectPath, composePath, relatedEndpointIds) - if err != nil { - return "", "", "", fmt.Errorf("Failed creating and storing kube manifest: %w", err) - } - - return composePath, manifestPath, projectPath, nil + return repositoryConfig.ConfigFilePath, "", projectPath, nil } if deploymentType == portainer.EdgeStackDeploymentKubernetes { diff --git a/api/http/handler/edgestacks/edgestack_create_string.go b/api/http/handler/edgestacks/edgestack_create_string.go index 4a4b92cc5..b46c0fb34 100644 --- a/api/http/handler/edgestacks/edgestack_create_string.go +++ b/api/http/handler/edgestacks/edgestack_create_string.go @@ -20,10 +20,9 @@ type edgeStackFromStringPayload struct { // List of identifiers of EdgeGroups EdgeGroups []portainer.EdgeGroupID `example:"1"` // Deployment type to deploy this stack - // Valid values are: 0 - 'compose', 1 - 'kubernetes', 2 - 'nomad' - // for compose stacks will use kompose to convert to kubernetes manifest for kubernetes environments(endpoints) - // kubernetes deploy type is enabled only for kubernetes environments(endpoints) - // nomad deploy type is enabled only for nomad environments(endpoints) + // Valid values are: 0 - 'compose', 1 - 'kubernetes' + // compose is enabled only for docker environments + // kubernetes is enabled only for kubernetes environments DeploymentType portainer.EdgeStackDeploymentType `example:"0" enums:"0,1,2"` // List of Registries to use for this stack Registries []portainer.RegistryID @@ -35,12 +34,19 @@ func (payload *edgeStackFromStringPayload) Validate(r *http.Request) error { if govalidator.IsNull(payload.Name) { return httperrors.NewInvalidPayloadError("Invalid stack name") } + if govalidator.IsNull(payload.StackFileContent) { return httperrors.NewInvalidPayloadError("Invalid stack file content") } + if len(payload.EdgeGroups) == 0 { return httperrors.NewInvalidPayloadError("Edge Groups are mandatory for an Edge stack") } + + if payload.DeploymentType != portainer.EdgeStackDeploymentCompose && payload.DeploymentType != portainer.EdgeStackDeploymentKubernetes { + return httperrors.NewInvalidPayloadError("Invalid deployment type") + } + return nil } @@ -81,6 +87,14 @@ func (handler *Handler) createEdgeStackFromFileContent(r *http.Request, dryrun b } func (handler *Handler) storeFileContent(stackFolder string, deploymentType portainer.EdgeStackDeploymentType, relatedEndpointIds []portainer.EndpointID, fileContent []byte) (composePath, manifestPath, projectPath string, err error) { + hasWrongType, err := hasWrongEnvironmentType(handler.DataStore.Endpoint(), relatedEndpointIds, deploymentType) + 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") + } + if deploymentType == portainer.EdgeStackDeploymentCompose { composePath = filesystem.ComposeFileDefaultName @@ -89,22 +103,8 @@ func (handler *Handler) storeFileContent(stackFolder string, deploymentType port return "", "", "", err } - manifestPath, err = handler.convertAndStoreKubeManifestIfNeeded(stackFolder, projectPath, composePath, relatedEndpointIds) - if err != nil { - return "", "", "", fmt.Errorf("Failed creating and storing kube manifest: %w", err) - } + return composePath, "", projectPath, nil - return composePath, manifestPath, projectPath, nil - - } - - hasDockerEndpoint, err := hasDockerEndpoint(handler.DataStore.Endpoint(), relatedEndpointIds) - if err != nil { - return "", "", "", fmt.Errorf("unable to check for existence of docker environment: %w", err) - } - - if hasDockerEndpoint { - return "", "", "", errors.New("edge stack with docker environment cannot be deployed with kubernetes or nomad config") } if deploymentType == portainer.EdgeStackDeploymentKubernetes { diff --git a/api/http/handler/edgestacks/edgestack_update.go b/api/http/handler/edgestacks/edgestack_update.go index c167d7661..37486d8b1 100644 --- a/api/http/handler/edgestacks/edgestack_update.go +++ b/api/http/handler/edgestacks/edgestack_update.go @@ -146,6 +146,14 @@ func (handler *Handler) edgeStackUpdate(w http.ResponseWriter, r *http.Request) stackFolder := strconv.Itoa(int(stack.ID)) + hasWrongType, err := hasWrongEnvironmentType(handler.DataStore.Endpoint(), relatedEndpointIds, payload.DeploymentType) + if err != nil { + return httperror.BadRequest("unable to check for existence of non fitting environments: %w", err) + } + if hasWrongType { + return httperror.BadRequest("edge stack with config do not match the environment type", nil) + } + if payload.DeploymentType == portainer.EdgeStackDeploymentCompose { if stack.EntryPoint == "" { stack.EntryPoint = filesystem.ComposeFileDefaultName @@ -171,15 +179,6 @@ func (handler *Handler) edgeStackUpdate(w http.ResponseWriter, r *http.Request) stack.UseManifestNamespaces = payload.UseManifestNamespaces - hasDockerEndpoint, err := hasDockerEndpoint(handler.DataStore.Endpoint(), relatedEndpointIds) - if err != nil { - return httperror.InternalServerError("Unable to check for existence of docker environment", err) - } - - if hasDockerEndpoint { - return httperror.BadRequest("Edge stack with docker environment cannot be deployed with kubernetes config", err) - } - _, err = handler.FileService.StoreEdgeStackFileFromBytes(stackFolder, stack.ManifestPath, []byte(payload.StackFileContent)) if err != nil { return httperror.InternalServerError("Unable to persist updated Kubernetes manifest file on disk", err) diff --git a/api/http/handler/edgestacks/endpoints.go b/api/http/handler/edgestacks/endpoints.go index 9a9dde2ff..034920012 100644 --- a/api/http/handler/edgestacks/endpoints.go +++ b/api/http/handler/edgestacks/endpoints.go @@ -30,3 +30,16 @@ func hasEndpointPredicate(endpointService dataservices.EndpointService, endpoint return false, nil } + +func hasWrongEnvironmentType(endpointService dataservices.EndpointService, endpointIDs []portainer.EndpointID, deploymentType portainer.EdgeStackDeploymentType) (bool, error) { + return hasEndpointPredicate(endpointService, endpointIDs, func(e *portainer.Endpoint) bool { + switch deploymentType { + case portainer.EdgeStackDeploymentKubernetes: + return !endpointutils.IsKubernetesEndpoint(e) + case portainer.EdgeStackDeploymentCompose: + return !endpointutils.IsDockerEndpoint(e) + default: + return true + } + }) +}