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  }