@@ -294,25 +294,26 @@ public ProjectMetadata execute(ProjectMetadata currentProject) throws Exception
294
294
);
295
295
}
296
296
297
- // Public visible for testing
297
+ /**
298
+ * Add the given component template to the project. If {@code create} is true, we will fail if there exists a component template with
299
+ * the same name. If a component template with the same name exists, but the content is identical, no change will be made.
300
+ * This method will perform all necessary validation but assumes that the component template has already been normalized (see
301
+ * {@link #normalizeComponentTemplate(ComponentTemplate)}.
302
+ */
298
303
public ProjectMetadata addComponentTemplate (
299
304
final ProjectMetadata project ,
300
305
final boolean create ,
301
306
final String name ,
302
307
final ComponentTemplate template
303
- ) throws Exception {
304
- final ComponentTemplate existing = project .componentTemplates ().get (name );
305
- if (create && existing != null ) {
306
- throw new IllegalArgumentException ("component template [" + name + "] already exists" );
307
- }
308
-
309
- CompressedXContent mappings = template .template ().mappings ();
310
- CompressedXContent wrappedMappings = wrapMappingsIfNecessary (mappings , xContentRegistry );
311
-
312
- // We may need to normalize index settings, so do that also
313
- Settings finalSettings = template .template ().settings ();
314
- if (finalSettings != null ) {
315
- finalSettings = Settings .builder ().put (finalSettings ).normalizePrefix (IndexMetadata .INDEX_SETTING_PREFIX ).build ();
308
+ ) throws IOException {
309
+ final ComponentTemplate existingTemplate = project .componentTemplates ().get (name );
310
+ if (existingTemplate != null ) {
311
+ if (create ) {
312
+ throw new IllegalArgumentException ("component template [" + name + "] already exists" );
313
+ }
314
+ if (template .contentEquals (existingTemplate )) {
315
+ return project ;
316
+ }
316
317
}
317
318
318
319
// Collect all the composable (index) templates that use this component template, we'll use
@@ -325,9 +326,9 @@ public ProjectMetadata addComponentTemplate(
325
326
.collect (Collectors .toMap (Map .Entry ::getKey , Map .Entry ::getValue ));
326
327
327
328
// if we're updating a component template, let's check if it's part of any V2 template that will yield the CT update invalid
328
- if (create == false && finalSettings != null ) {
329
+ if (create == false && template . template (). settings () != null ) {
329
330
// if the CT is specifying the `index.hidden` setting it cannot be part of any global template
330
- if (IndexMetadata .INDEX_HIDDEN_SETTING .exists (finalSettings )) {
331
+ if (IndexMetadata .INDEX_HIDDEN_SETTING .exists (template . template (). settings () )) {
331
332
List <String > globalTemplatesThatUseThisComponent = new ArrayList <>();
332
333
for (Map .Entry <String , ComposableIndexTemplate > entry : templatesUsingComponent .entrySet ()) {
333
334
ComposableIndexTemplate templateV2 = entry .getValue ();
@@ -351,47 +352,27 @@ public ProjectMetadata addComponentTemplate(
351
352
}
352
353
}
353
354
354
- final Template finalTemplate = Template .builder (template .template ()).settings (finalSettings ).mappings (wrappedMappings ).build ();
355
- final long now = instantSource .instant ().toEpochMilli ();
356
- final ComponentTemplate finalComponentTemplate ;
357
- if (existing == null ) {
358
- finalComponentTemplate = new ComponentTemplate (
359
- finalTemplate ,
360
- template .version (),
361
- template .metadata (),
362
- template .deprecated (),
363
- now ,
364
- now
365
- );
366
- } else {
367
- final ComponentTemplate templateToCompareToExisting = new ComponentTemplate (
368
- finalTemplate ,
369
- template .version (),
370
- template .metadata (),
371
- template .deprecated (),
372
- existing .createdDateMillis ().orElse (null ),
373
- existing .modifiedDateMillis ().orElse (null )
374
- );
375
- if (templateToCompareToExisting .equals (existing )) {
376
- return project ;
377
- }
378
- finalComponentTemplate = new ComponentTemplate (
379
- finalTemplate ,
380
- template .version (),
381
- template .metadata (),
382
- template .deprecated (),
383
- existing .createdDateMillis ().orElse (null ),
384
- now
385
- );
386
- }
355
+ final Long now = instantSource .instant ().toEpochMilli ();
356
+ final Long createdDateMillis = existingTemplate == null ? now : existingTemplate .createdDateMillis ().orElse (null );
357
+ final ComponentTemplate finalComponentTemplate = new ComponentTemplate (
358
+ template .template (),
359
+ template .version (),
360
+ template .metadata (),
361
+ template .deprecated (),
362
+ createdDateMillis ,
363
+ now
364
+ );
387
365
388
- validateTemplate (finalSettings , wrappedMappings , indicesService );
366
+ // These two validation checks are only scoped to the component template itself (and don't depend on any other entities in the
367
+ // cluster state) and could thus be done in the transport action. However, since we're parsing mappings here, we shouldn't be doing
368
+ // it directly on the transport thread. Instead, we should fork to a different threadpool (management/generic).
369
+ validateTemplate (finalComponentTemplate .template ().settings (), finalComponentTemplate .template ().mappings (), indicesService );
389
370
validate (name , finalComponentTemplate .template (), List .of (), null );
390
371
391
372
ProjectMetadata projectWithComponentTemplateAdded = ProjectMetadata .builder (project ).put (name , finalComponentTemplate ).build ();
392
373
// Validate all composable index templates that use this component template
393
374
if (templatesUsingComponent .isEmpty () == false ) {
394
- Exception validationFailure = null ;
375
+ IllegalArgumentException validationFailure = null ;
395
376
for (Map .Entry <String , ComposableIndexTemplate > entry : templatesUsingComponent .entrySet ()) {
396
377
final String composableTemplateName = entry .getKey ();
397
378
final ComposableIndexTemplate composableTemplate = entry .getValue ();
@@ -425,10 +406,42 @@ public ProjectMetadata addComponentTemplate(
425
406
.addWarningHeaderIfDataRetentionNotEffective (globalRetentionSettings .get (false ), false );
426
407
}
427
408
428
- logger .info ("{} component template [{}]" , existing == null ? "adding" : "updating" , name );
409
+ logger .info ("{} component template [{}]" , existingTemplate == null ? "adding" : "updating" , name );
429
410
return projectWithComponentTemplateAdded ;
430
411
}
431
412
413
+ /**
414
+ * Normalize the given component template by trying to normalize settings and wrapping mappings if necessary. Returns the same instance
415
+ * if nothing needs to be done.
416
+ */
417
+ public ComponentTemplate normalizeComponentTemplate (final ComponentTemplate componentTemplate ) throws IOException {
418
+ Template template = componentTemplate .template ();
419
+ // Normalize the index settings if necessary
420
+ Settings prefixedSettings = null ;
421
+ if (template .settings () != null ) {
422
+ prefixedSettings = template .settings ().maybeNormalizePrefix (IndexMetadata .INDEX_SETTING_PREFIX );
423
+ }
424
+ // TODO: theoretically, we could avoid parsing the mappings once by combining this wrapping with the mapping validation later on,
425
+ // but that refactoring will be non-trivial as we currently don't seem to have methods available to merge already-parsed mappings;
426
+ // we only allow merging mappings from CompressedXContent.
427
+ CompressedXContent wrappedMappings = MetadataIndexTemplateService .wrapMappingsIfNecessary (template .mappings (), xContentRegistry );
428
+
429
+ // No need to build a new component template if we didn't change anything.
430
+ // We can check for reference equality since `maybeNormalizePrefix` and `wrapMappingsIfNecessary` return the same instance if
431
+ // nothing needs to be done.
432
+ if (prefixedSettings == template .settings () && wrappedMappings == template .mappings ()) {
433
+ return componentTemplate ;
434
+ }
435
+ return new ComponentTemplate (
436
+ Template .builder (template ).settings (prefixedSettings ).mappings (wrappedMappings ).build (),
437
+ componentTemplate .version (),
438
+ componentTemplate .metadata (),
439
+ componentTemplate .deprecated (),
440
+ componentTemplate .createdDateMillis ().orElse (null ),
441
+ componentTemplate .modifiedDateMillis ().orElse (null )
442
+ );
443
+ }
444
+
432
445
/**
433
446
* Mappings in templates don't have to include <code>_doc</code>, so update the mappings to include this single type if necessary
434
447
*
@@ -2048,7 +2061,7 @@ private static void validateCompositeTemplate(
2048
2061
}
2049
2062
2050
2063
public static void validateTemplate (Settings validateSettings , CompressedXContent mappings , IndicesService indicesService )
2051
- throws Exception {
2064
+ throws IOException {
2052
2065
// Hard to validate settings if they're non-existent, so used empty ones if none were provided
2053
2066
Settings settings = validateSettings ;
2054
2067
if (settings == null ) {
0 commit comments