diff --git a/api/exec/kubernetes_deploy.go b/api/exec/kubernetes_deploy.go
index 13ae7faab..e611c4b0a 100644
--- a/api/exec/kubernetes_deploy.go
+++ b/api/exec/kubernetes_deploy.go
@@ -94,7 +94,9 @@ func (deployer *KubernetesDeployer) Deploy(request *http.Request, endpoint *port
args = append(args, "--server", endpoint.URL)
args = append(args, "--insecure-skip-tls-verify")
args = append(args, "--token", token)
- args = append(args, "--namespace", namespace)
+ if (namespace != "") {
+ args = append(args, "--namespace", namespace)
+ }
args = append(args, "apply", "-f", "-")
var stderr bytes.Buffer
diff --git a/api/http/handler/stacks/create_kubernetes_stack.go b/api/http/handler/stacks/create_kubernetes_stack.go
index 4de14d3a3..eca22b9b1 100644
--- a/api/http/handler/stacks/create_kubernetes_stack.go
+++ b/api/http/handler/stacks/create_kubernetes_stack.go
@@ -15,6 +15,7 @@ import (
"github.com/portainer/libhttp/response"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/filesystem"
+ "github.com/portainer/portainer/api/http/client"
)
const defaultReferenceName = "refs/heads/master"
@@ -36,6 +37,12 @@ type kubernetesGitDeploymentPayload struct {
FilePathInRepository string
}
+type kubernetesManifestURLDeploymentPayload struct {
+ ComposeFormat bool
+ ManifestURL string
+}
+
+
func (payload *kubernetesStringDeploymentPayload) Validate(r *http.Request) error {
if govalidator.IsNull(payload.StackFileContent) {
return errors.New("Invalid stack file content")
@@ -65,6 +72,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"`
}
@@ -155,6 +169,55 @@ 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, "")
+ 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(request *http.Request, endpoint *portainer.Endpoint, stackConfig string, composeFormat bool, namespace string) (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..1c9c11b17 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 34fe609fe..fd545c6e4 100644
--- a/app/kubernetes/models/deploy.js
+++ b/app/kubernetes/models/deploy.js
@@ -6,9 +6,11 @@ export const KubernetesDeployManifestTypes = Object.freeze({
export const KubernetesDeployBuildMethods = Object.freeze({
GIT: 1,
WEB_EDITOR: 2,
+ URL: 3
});
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 237acec2c..6e1357a34 100644
--- a/app/kubernetes/views/deploy/deploy.html
+++ b/app/kubernetes/views/deploy/deploy.html
@@ -16,7 +16,7 @@