diff --git a/api/http/handler/stacks/create_kubernetes_stack.go b/api/http/handler/stacks/create_kubernetes_stack.go index 56d5e0278..f6129d81d 100644 --- a/api/http/handler/stacks/create_kubernetes_stack.go +++ b/api/http/handler/stacks/create_kubernetes_stack.go @@ -16,6 +16,7 @@ import ( portainer "github.com/portainer/portainer/api" "github.com/portainer/portainer/api/filesystem" k "github.com/portainer/portainer/api/kubernetes" + "github.com/portainer/portainer/api/http/client" ) const defaultReferenceName = "refs/heads/master" @@ -37,6 +38,12 @@ type kubernetesGitDeploymentPayload struct { FilePathInRepository string } +type kubernetesManifestURLDeploymentPayload struct { + Namespace string + ComposeFormat bool + ManifestURL string +} + func (payload *kubernetesStringDeploymentPayload) Validate(r *http.Request) error { if govalidator.IsNull(payload.StackFileContent) { return errors.New("Invalid stack file content") @@ -66,6 +73,13 @@ func (payload *kubernetesGitDeploymentPayload) Validate(r *http.Request) error { return nil } +func (payload *kubernetesManifestURLDeploymentPayload) Validate(r *http.Request) error { + if govalidator.IsNull(payload.ManifestURL) || !govalidator.IsURL(payload.ManifestURL) { + return errors.New("Invalid manifest URL") + } + return nil +} + type createKubernetesStackResponse struct { Output string `json:"Output"` } @@ -171,6 +185,61 @@ func (handler *Handler) createKubernetesStackFromGitRepository(w http.ResponseWr return response.JSON(w, resp) } + +func (handler *Handler) createKubernetesStackFromManifestURL(w http.ResponseWriter, r *http.Request, endpoint *portainer.Endpoint) *httperror.HandlerError { + var payload kubernetesManifestURLDeploymentPayload + if err := request.DecodeAndValidateJSONPayload(r, &payload); err != nil { + return &httperror.HandlerError{StatusCode: http.StatusBadRequest, Message: "Invalid request payload", Err: err} + } + + stackID := handler.DataStore.Stack().GetNextIdentifier() + stack := &portainer.Stack{ + ID: portainer.StackID(stackID), + Type: portainer.KubernetesStack, + EndpointID: endpoint.ID, + EntryPoint: filesystem.ManifestFileDefaultName, + Status: portainer.StackStatusActive, + CreationDate: time.Now().Unix(), + } + + var manifestContent []byte + manifestContent, err := client.Get(payload.ManifestURL, 30) + if err != nil { + return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve manifest from URL", err} + } + + stackFolder := strconv.Itoa(int(stack.ID)) + projectPath, err := handler.FileService.StoreStackFileFromBytes(stackFolder, stack.EntryPoint, manifestContent) + if err != nil { + return &httperror.HandlerError{StatusCode: http.StatusInternalServerError, Message: "Unable to persist Kubernetes manifest file on disk", Err: err} + } + stack.ProjectPath = projectPath + + doCleanUp := true + defer handler.cleanUp(stack, &doCleanUp) + + output, err := handler.deployKubernetesStack(r, endpoint, string(manifestContent), payload.ComposeFormat, payload.Namespace, k.KubeAppLabels{ + StackID: stackID, + Name: stack.Name, + Owner: stack.CreatedBy, + Kind: "url", + }) + if err != nil { + return &httperror.HandlerError{StatusCode: http.StatusInternalServerError, Message: "Unable to deploy Kubernetes stack", Err: err} + } + + err = handler.DataStore.Stack().CreateStack(stack) + if err != nil { + return &httperror.HandlerError{StatusCode: http.StatusInternalServerError, Message: "Unable to persist the Kubernetes stack inside the database", Err: err} + } + + resp := &createKubernetesStackResponse{ + Output: output, + } + + return response.JSON(w, resp) +} + func (handler *Handler) deployKubernetesStack(r *http.Request, endpoint *portainer.Endpoint, stackConfig string, composeFormat bool, namespace string, appLabels k.KubeAppLabels) (string, error) { handler.stackCreationMutex.Lock() defer handler.stackCreationMutex.Unlock() diff --git a/api/http/handler/stacks/stack_create.go b/api/http/handler/stacks/stack_create.go index d21bf3a1c..2ffb455e8 100644 --- a/api/http/handler/stacks/stack_create.go +++ b/api/http/handler/stacks/stack_create.go @@ -149,6 +149,8 @@ func (handler *Handler) createKubernetesStack(w http.ResponseWriter, r *http.Req return handler.createKubernetesStackFromFileContent(w, r, endpoint) case "repository": return handler.createKubernetesStackFromGitRepository(w, r, endpoint) + case "url": + return handler.createKubernetesStackFromManifestURL(w, r, endpoint) } return &httperror.HandlerError{StatusCode: http.StatusBadRequest, Message: "Invalid value for query parameter: method. Value must be one of: string or repository", Err: errors.New(request.ErrInvalidQueryParameter)} } diff --git a/app/kubernetes/models/deploy.js b/app/kubernetes/models/deploy.js index 797dee0ea..6d162ce5d 100644 --- a/app/kubernetes/models/deploy.js +++ b/app/kubernetes/models/deploy.js @@ -7,9 +7,11 @@ export const KubernetesDeployBuildMethods = Object.freeze({ GIT: 1, WEB_EDITOR: 2, CUSTOM_TEMPLATE: 3, + URL: 4 }); export const KubernetesDeployRequestMethods = Object.freeze({ REPOSITORY: 'repository', STRING: 'string', + URL: 'url' }); diff --git a/app/kubernetes/views/deploy/deploy.html b/app/kubernetes/views/deploy/deploy.html index 9f5d71240..dd7b61228 100644 --- a/app/kubernetes/views/deploy/deploy.html +++ b/app/kubernetes/views/deploy/deploy.html @@ -111,6 +111,33 @@ + + +