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 ) { /> - +