github.com/kyma-incubator/compass/components/director@v0.0.0-20230623144113-d764f56ff805/internal/systemfetcher/template.go (about) 1 package systemfetcher 2 3 import ( 4 "bytes" 5 "context" 6 "encoding/json" 7 "fmt" 8 9 "k8s.io/client-go/util/jsonpath" 10 11 "github.com/imdario/mergo" 12 13 "github.com/kyma-incubator/compass/components/director/internal/model" 14 "github.com/pkg/errors" 15 ) 16 17 //go:generate mockery --name=applicationTemplateService --output=automock --outpkg=automock --case=underscore --exported=true --disable-version-string 18 type applicationTemplateService interface { 19 Get(ctx context.Context, id string) (*model.ApplicationTemplate, error) 20 PrepareApplicationCreateInputJSON(appTemplate *model.ApplicationTemplate, values model.ApplicationFromTemplateInputValues) (string, error) 21 } 22 23 //go:generate mockery --name=applicationConverter --output=automock --outpkg=automock --case=underscore --exported=true --disable-version-string 24 type applicationConverter interface { 25 CreateInputJSONToModel(ctx context.Context, in string) (model.ApplicationRegisterInput, error) 26 } 27 28 // PlaceholderMapping is the mapping we have between a placeholder key we use in templates, 29 // and input field from the external system provider. 30 type PlaceholderMapping struct { 31 PlaceholderName string `json:"placeholder_name"` 32 SystemKey string `json:"system_key"` 33 Optional bool `json:"optional"` 34 } 35 36 type renderer struct { 37 appTemplateService applicationTemplateService 38 appConverter applicationConverter 39 40 appInputOverride string 41 placeholdersMapping []PlaceholderMapping 42 placeholdersOverride []model.ApplicationTemplatePlaceholder 43 } 44 45 // NewTemplateRenderer returns a new application input renderer by a given application template. 46 func NewTemplateRenderer(appTemplateService applicationTemplateService, appConverter applicationConverter, appInputOverride string, mapping []PlaceholderMapping) (*renderer, error) { 47 if _, err := appConverter.CreateInputJSONToModel(context.Background(), appInputOverride); err != nil { 48 return nil, errors.Wrapf(err, "while converting override application input JSON into application input") 49 } 50 placeholders := make([]model.ApplicationTemplatePlaceholder, 0) 51 for i := 0; i < len(mapping); i++ { 52 placeholders = append(placeholders, model.ApplicationTemplatePlaceholder{ 53 Name: mapping[i].PlaceholderName, 54 JSONPath: &mapping[i].SystemKey, 55 Optional: &mapping[i].Optional, 56 }) 57 } 58 59 return &renderer{ 60 appTemplateService: appTemplateService, 61 appConverter: appConverter, 62 appInputOverride: appInputOverride, 63 placeholdersMapping: mapping, 64 placeholdersOverride: placeholders, 65 }, nil 66 } 67 68 func (r *renderer) ApplicationRegisterInputFromTemplate(ctx context.Context, sc System) (*model.ApplicationRegisterInput, error) { 69 appTemplate, err := r.appTemplateService.Get(ctx, sc.TemplateID) 70 if err != nil { 71 return nil, errors.Wrapf(err, "while getting application template with ID %s", sc.TemplateID) 72 } 73 74 inputValues, err := r.getTemplateInputs(sc.SystemPayload, appTemplate) 75 if err != nil { 76 return nil, errors.Wrapf(err, "while getting template inputs for Application Template with name %s", appTemplate.Name) 77 } 78 placeholdersOverride, err := r.extendPlaceholdersOverride(sc.SystemPayload, appTemplate) 79 if err != nil { 80 return nil, errors.Wrapf(err, "while extending placeholders override for Application Template with name %s", appTemplate.Name) 81 } 82 83 appTemplate.Placeholders = placeholdersOverride 84 appTemplate.ApplicationInputJSON, err = r.mergedApplicationInput(appTemplate.ApplicationInputJSON, r.appInputOverride) 85 if err != nil { 86 return nil, errors.Wrap(err, "while merging application input from template and override application input") 87 } 88 appRegisterInputJSON, err := r.appTemplateService.PrepareApplicationCreateInputJSON(appTemplate, *inputValues) 89 if err != nil { 90 return nil, errors.Wrapf(err, "while preparing ApplicationRegisterInput JSON from Application Template with name %s", appTemplate.Name) 91 } 92 93 appRegisterInput, err := r.appConverter.CreateInputJSONToModel(ctx, appRegisterInputJSON) 94 if err != nil { 95 return nil, errors.Wrapf(err, "while preparing ApplicationRegisterInput model from Application Template with name %s", appTemplate.Name) 96 } 97 98 return &appRegisterInput, nil 99 } 100 101 func (r *renderer) getTemplateInputs(systemPayload map[string]interface{}, appTemplate *model.ApplicationTemplate) (*model.ApplicationFromTemplateInputValues, error) { 102 parser := jsonpath.New("parser") 103 104 placeholdersMappingInputValues := model.ApplicationFromTemplateInputValues{} 105 for _, pm := range r.placeholdersMapping { 106 if err := parser.Parse(fmt.Sprintf("{%s}", pm.SystemKey)); err != nil { 107 return nil, errors.Wrapf(err, "while parsing placeholder mapping with name %s and system key: %s", pm.PlaceholderName, pm.SystemKey) 108 } 109 110 placeholderInput := new(bytes.Buffer) 111 if err := parser.Execute(placeholderInput, systemPayload); err != nil && !pm.Optional { 112 return nil, errors.Wrapf(err, "missing or empty key %q in system payload.", pm.SystemKey) 113 } 114 115 placeholdersMappingInputValues = append(placeholdersMappingInputValues, &model.ApplicationTemplateValueInput{ 116 Placeholder: pm.PlaceholderName, 117 Value: placeholderInput.String(), 118 }) 119 } 120 var appTemplatePlaceholdersInputValues model.ApplicationFromTemplateInputValues 121 for _, placeholder := range appTemplate.Placeholders { 122 if placeholder.JSONPath != nil && len(*placeholder.JSONPath) > 0 { 123 if err := parser.Parse(fmt.Sprintf("{%s}", *placeholder.JSONPath)); err != nil { 124 return nil, errors.Wrapf(err, "while parsing placeholder jsonPath with name: %s and path: %s for ap template with id: %s", placeholder.Name, *placeholder.JSONPath, appTemplate.ID) 125 } 126 127 placeholderInput := new(bytes.Buffer) 128 if err := parser.Execute(placeholderInput, systemPayload); err != nil { 129 return nil, errors.Wrapf(err, "placeholder value with name: %s and path: %s for app template with id: %s not found in system payload", placeholder.Name, *placeholder.JSONPath, appTemplate.ID) 130 } 131 132 appTemplatePlaceholdersInputValues = append(appTemplatePlaceholdersInputValues, &model.ApplicationTemplateValueInput{ 133 Placeholder: placeholder.Name, 134 Value: placeholderInput.String(), 135 }) 136 } 137 } 138 inputValues := mergeInputValues(appTemplatePlaceholdersInputValues, placeholdersMappingInputValues) 139 return &inputValues, nil 140 } 141 142 func (r *renderer) mergedApplicationInput(originalAppInputJSON, overrideAppInputJSON string) (string, error) { 143 var originalAppInput map[string]interface{} 144 var overrideAppInput map[string]interface{} 145 146 if err := json.Unmarshal([]byte(originalAppInputJSON), &originalAppInput); err != nil { 147 return "", errors.Wrapf(err, "while unmarshaling original application input") 148 } 149 150 if err := json.Unmarshal([]byte(overrideAppInputJSON), &overrideAppInput); err != nil { 151 return "", errors.Wrapf(err, "while unmarshaling override application input") 152 } 153 154 if err := mergo.Merge(&originalAppInput, overrideAppInput); err != nil { 155 return "", errors.Wrapf(err, "while merging original app input: %v into destination app input: %v", originalAppInput, overrideAppInputJSON) 156 } 157 merged, err := json.Marshal(originalAppInput) 158 if err != nil { 159 return "", errors.Wrapf(err, "while marshalling merged app input") 160 } 161 return string(merged), nil 162 } 163 164 func (r *renderer) extendPlaceholdersOverride(systemPayload map[string]interface{}, appTemplate *model.ApplicationTemplate) ([]model.ApplicationTemplatePlaceholder, error) { 165 parser := jsonpath.New("parser") 166 167 var appTemplatePlaceholdersOverride []model.ApplicationTemplatePlaceholder 168 for _, placeholder := range appTemplate.Placeholders { 169 if placeholder.JSONPath != nil && len(*placeholder.JSONPath) > 0 { 170 if err := parser.Parse(fmt.Sprintf("{%s}", *placeholder.JSONPath)); err != nil { 171 return nil, errors.Wrapf(err, "while parsing placeholder jsonPath with name: %s and path: %s for ap template with id: %s", placeholder.Name, *placeholder.JSONPath, appTemplate.ID) 172 } 173 174 placeholderInput := new(bytes.Buffer) 175 if err := parser.Execute(placeholderInput, systemPayload); err != nil { 176 return nil, errors.Wrapf(err, "placeholder value with name: %s and path: %s for app template with id: %s not found in system payload", placeholder.Name, *placeholder.JSONPath, appTemplate.ID) 177 } 178 179 appTemplatePlaceholdersOverride = append(appTemplatePlaceholdersOverride, model.ApplicationTemplatePlaceholder{ 180 Name: placeholder.Name, 181 JSONPath: placeholder.JSONPath, 182 Optional: placeholder.Optional, 183 }) 184 } 185 } 186 placeholdersOverride := mergePlaceholders(appTemplatePlaceholdersOverride, r.placeholdersOverride) 187 188 return placeholdersOverride, nil 189 } 190 191 func mergePlaceholders(appTemplatePlaceholdersOverride []model.ApplicationTemplatePlaceholder, placeholdersOverride []model.ApplicationTemplatePlaceholder) []model.ApplicationTemplatePlaceholder { 192 placeholdersMap := make(map[string]model.ApplicationTemplatePlaceholder) 193 for _, placeholder := range appTemplatePlaceholdersOverride { 194 placeholdersMap[placeholder.Name] = placeholder 195 } 196 for _, placeholder := range placeholdersOverride { 197 if _, exists := placeholdersMap[placeholder.Name]; !exists { 198 placeholdersMap[placeholder.Name] = placeholder 199 } 200 } 201 mergedPlaceholders := make([]model.ApplicationTemplatePlaceholder, 0) 202 for _, p := range placeholdersMap { 203 mergedPlaceholders = append(mergedPlaceholders, p) 204 } 205 return mergedPlaceholders 206 } 207 208 func mergeInputValues(appTemplatePlaceholdersInputValues model.ApplicationFromTemplateInputValues, placeholdersMappingInputValues model.ApplicationFromTemplateInputValues) model.ApplicationFromTemplateInputValues { 209 inputsMap := make(map[string]*model.ApplicationTemplateValueInput) 210 for _, input := range appTemplatePlaceholdersInputValues { 211 inputsMap[input.Placeholder] = input 212 } 213 for _, input := range placeholdersMappingInputValues { 214 if _, exists := inputsMap[input.Placeholder]; !exists { 215 inputsMap[input.Placeholder] = input 216 } 217 } 218 mergedInputs := make(model.ApplicationFromTemplateInputValues, 0) 219 for _, input := range inputsMap { 220 mergedInputs = append(mergedInputs, input) 221 } 222 return mergedInputs 223 }