From 1ccdb64938c79bb35058d6ed98358d8ad9ac9777 Mon Sep 17 00:00:00 2001 From: Chaim Lev-Ari Date: Tue, 31 May 2022 13:00:47 +0300 Subject: [PATCH] refactor(custom-templates): render template variables [EE-2602] (#6937) --- .../customtemplates/customtemplate_create.go | 33 ++++++ .../customtemplates/customtemplate_update.go | 9 ++ api/http/handler/customtemplates/utils.go | 19 ++++ api/portainer.go | 9 ++ .../PortsMappingField.tsx | 4 +- ...-create-custom-template-view.controller.js | 40 ++++++- .../kube-create-custom-template-view.html | 8 +- ...be-edit-custom-template-view.controller.js | 35 ++++++ .../kube-edit-custom-template-view.html | 6 + app/kubernetes/views/deploy/deploy.html | 22 ++-- .../views/deploy/deployController.js | 32 +++++- .../InputList/InputList.stories.tsx | 2 +- .../form-components/InputList/InputList.tsx | 67 ++++++++--- .../stackFromTemplateForm.html | 2 +- .../components/custom-templates/index.ts | 24 ++++ .../custom-templates/variables-field.ts | 71 ++++++++++++ app/portainer/react/components/index.ts | 4 +- .../createCustomTemplateView.html | 21 +++- .../createCustomTemplateViewController.js | 36 +++++- .../customTemplatesView.html | 6 + .../customTemplatesViewController.js | 30 +++++ .../editCustomTemplateView.html | 19 +++- .../editCustomTemplateViewController.js | 42 ++++++- ...platesVariablesDefinitionField.stories.tsx | 36 ++++++ ...ustomTemplatesVariablesDefinitionField.tsx | 105 ++++++++++++++++++ .../index.ts | 1 + .../CustomTemplatesVariablesField.stories.tsx | 52 +++++++++ .../CustomTemplatesVariablesField.tsx | 54 +++++++++ .../CustomTemplatesVariablesField/index.ts | 1 + .../custom-templates/components/utils.ts | 72 ++++++++++++ package.json | 4 +- yarn.lock | 10 ++ 32 files changed, 829 insertions(+), 47 deletions(-) create mode 100644 api/http/handler/customtemplates/utils.go create mode 100644 app/portainer/react/components/custom-templates/index.ts create mode 100644 app/portainer/react/components/custom-templates/variables-field.ts create mode 100644 app/react/portainer/custom-templates/components/CustomTemplatesVariablesDefinitionField/CustomTemplatesVariablesDefinitionField.stories.tsx create mode 100644 app/react/portainer/custom-templates/components/CustomTemplatesVariablesDefinitionField/CustomTemplatesVariablesDefinitionField.tsx create mode 100644 app/react/portainer/custom-templates/components/CustomTemplatesVariablesDefinitionField/index.ts create mode 100644 app/react/portainer/custom-templates/components/CustomTemplatesVariablesField/CustomTemplatesVariablesField.stories.tsx create mode 100644 app/react/portainer/custom-templates/components/CustomTemplatesVariablesField/CustomTemplatesVariablesField.tsx create mode 100644 app/react/portainer/custom-templates/components/CustomTemplatesVariablesField/index.ts create mode 100644 app/react/portainer/custom-templates/components/utils.ts diff --git a/api/http/handler/customtemplates/customtemplate_create.go b/api/http/handler/customtemplates/customtemplate_create.go index 47c79f70f..cf42e3948 100644 --- a/api/http/handler/customtemplates/customtemplate_create.go +++ b/api/http/handler/customtemplates/customtemplate_create.go @@ -1,6 +1,7 @@ package customtemplates import ( + "encoding/json" "errors" "log" "net/http" @@ -115,6 +116,8 @@ type customTemplateFromFileContentPayload struct { Type portainer.StackType `example:"1" enums:"1,2,3" validate:"required"` // Content of stack file FileContent string `validate:"required"` + // Definitions of variables in the stack file + Variables []portainer.CustomTemplateVariableDefinition } func (payload *customTemplateFromFileContentPayload) Validate(r *http.Request) error { @@ -136,6 +139,12 @@ func (payload *customTemplateFromFileContentPayload) Validate(r *http.Request) e if !isValidNote(payload.Note) { return errors.New("Invalid note. tag is not supported") } + + err := validateVariablesDefinitions(payload.Variables) + if err != nil { + return err + } + return nil } @@ -164,6 +173,7 @@ func (handler *Handler) createCustomTemplateFromFileContent(r *http.Request) (*p Platform: (payload.Platform), Type: (payload.Type), Logo: payload.Logo, + Variables: payload.Variables, } templateFolder := strconv.Itoa(customTemplateID) @@ -204,6 +214,8 @@ type customTemplateFromGitRepositoryPayload struct { RepositoryPassword string `example:"myGitPassword"` // Path to the Stack file inside the Git repository ComposeFilePathInRepository string `example:"docker-compose.yml" default:"docker-compose.yml"` + // Definitions of variables in the stack file + Variables []portainer.CustomTemplateVariableDefinition } func (payload *customTemplateFromGitRepositoryPayload) Validate(r *http.Request) error { @@ -236,6 +248,12 @@ func (payload *customTemplateFromGitRepositoryPayload) Validate(r *http.Request) if !isValidNote(payload.Note) { return errors.New("Invalid note. tag is not supported") } + + err := validateVariablesDefinitions(payload.Variables) + if err != nil { + return err + } + return nil } @@ -256,6 +274,7 @@ func (handler *Handler) createCustomTemplateFromGitRepository(r *http.Request) ( Platform: payload.Platform, Type: payload.Type, Logo: payload.Logo, + Variables: payload.Variables, } projectPath := handler.FileService.GetCustomTemplateProjectPath(strconv.Itoa(customTemplateID)) @@ -316,6 +335,8 @@ type customTemplateFromFileUploadPayload struct { Platform portainer.CustomTemplatePlatform Type portainer.StackType FileContent []byte + // Definitions of variables in the stack file + Variables []portainer.CustomTemplateVariableDefinition } func (payload *customTemplateFromFileUploadPayload) Validate(r *http.Request) error { @@ -361,6 +382,17 @@ func (payload *customTemplateFromFileUploadPayload) Validate(r *http.Request) er } payload.FileContent = composeFileContent + varsString, _ := request.RetrieveMultiPartFormValue(r, "Variables", true) + err = json.Unmarshal([]byte(varsString), &payload.Variables) + if err != nil { + return errors.New("Invalid variables. Ensure that the variables are valid JSON") + } + + err = validateVariablesDefinitions(payload.Variables) + if err != nil { + return err + } + return nil } @@ -381,6 +413,7 @@ func (handler *Handler) createCustomTemplateFromFileUpload(r *http.Request) (*po Type: payload.Type, Logo: payload.Logo, EntryPoint: filesystem.ComposeFileDefaultName, + Variables: payload.Variables, } templateFolder := strconv.Itoa(customTemplateID) diff --git a/api/http/handler/customtemplates/customtemplate_update.go b/api/http/handler/customtemplates/customtemplate_update.go index 650c0f554..0a752de4e 100644 --- a/api/http/handler/customtemplates/customtemplate_update.go +++ b/api/http/handler/customtemplates/customtemplate_update.go @@ -31,6 +31,8 @@ type customTemplateUpdatePayload struct { Type portainer.StackType `example:"1" enums:"1,2,3" validate:"required"` // Content of stack file FileContent string `validate:"required"` + // Definitions of variables in the stack file + Variables []portainer.CustomTemplateVariableDefinition } func (payload *customTemplateUpdatePayload) Validate(r *http.Request) error { @@ -52,6 +54,12 @@ func (payload *customTemplateUpdatePayload) Validate(r *http.Request) error { if !isValidNote(payload.Note) { return errors.New("Invalid note. tag is not supported") } + + err := validateVariablesDefinitions(payload.Variables) + if err != nil { + return err + } + return nil } @@ -124,6 +132,7 @@ func (handler *Handler) customTemplateUpdate(w http.ResponseWriter, r *http.Requ customTemplate.Note = payload.Note customTemplate.Platform = payload.Platform customTemplate.Type = payload.Type + customTemplate.Variables = payload.Variables err = handler.DataStore.CustomTemplate().UpdateCustomTemplate(customTemplate.ID, customTemplate) if err != nil { diff --git a/api/http/handler/customtemplates/utils.go b/api/http/handler/customtemplates/utils.go new file mode 100644 index 000000000..7ae060d49 --- /dev/null +++ b/api/http/handler/customtemplates/utils.go @@ -0,0 +1,19 @@ +package customtemplates + +import ( + "errors" + + portainer "github.com/portainer/portainer/api" +) + +func validateVariablesDefinitions(variables []portainer.CustomTemplateVariableDefinition) error { + for _, variable := range variables { + if variable.Name == "" { + return errors.New("variable name is required") + } + if variable.Label == "" { + return errors.New("variable label is required") + } + } + return nil +} diff --git a/api/portainer.go b/api/portainer.go index 0b081e3ce..3353afb29 100644 --- a/api/portainer.go +++ b/api/portainer.go @@ -128,6 +128,14 @@ type ( SecretKeyName *string } + // CustomTemplateVariableDefinition + CustomTemplateVariableDefinition struct { + Name string `json:"name" example:"MY_VAR"` + Label string `json:"label" example:"My Variable"` + DefaultValue string `json:"defaultValue" example:"default value"` + Description string `json:"description" example:"Description"` + } + // CustomTemplate represents a custom template CustomTemplate struct { // CustomTemplate Identifier @@ -152,6 +160,7 @@ type ( // Type of created stack (1 - swarm, 2 - compose) Type StackType `json:"Type" example:"1"` ResourceControl *ResourceControl `json:"ResourceControl"` + Variables []CustomTemplateVariableDefinition } // CustomTemplateID represents a custom template identifier diff --git a/app/azure/ContainerInstances/CreateContainerInstanceForm/PortsMappingField.tsx b/app/azure/ContainerInstances/CreateContainerInstanceForm/PortsMappingField.tsx index b00ca7979..d49dd768c 100644 --- a/app/azure/ContainerInstances/CreateContainerInstanceForm/PortsMappingField.tsx +++ b/app/azure/ContainerInstances/CreateContainerInstanceForm/PortsMappingField.tsx @@ -47,7 +47,7 @@ export function PortsMappingField({ value, onChange, errors }: Props) { function Item({ onChange, item, error }: ItemProps) { return (
-
+
host ) { /> - +