Skip to content
5 changes: 5 additions & 0 deletions docs/changelog/131536.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
pr: 131536
summary: "Component Templates: Add `{created,modified}_date`"
area: Ingest Node
type: enhancement
issues: []
Original file line number Diff line number Diff line change
Expand Up @@ -644,6 +644,8 @@ private static ProjectMetadata getProjectWithDataStreamWithSettings(
Template.builder().settings(componentTemplateSettings).build(),
null,
null,
null,
null,
null
);
builder.componentTemplates(Map.of("component_template_1", componentTemplate));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
setup:
- requires:
test_runner_features: capabilities
capabilities:
- method: PUT
path: /_component_template/{id}
capabilities: [ component_template_tracking_info ]
reason: "Templates have tracking info: modified_date and created_date"
- requires:
test_runner_features: contains

---
"Test PUT setting created_date":
- do:
catch: bad_request
cluster.put_component_template:
name: test_tracking
body:
template:
settings:
number_of_shards: 1
created_date: "2025-07-04T12:50:48.415Z"
- match: { status: 400 }
- contains: { error.reason: "[component_template] unknown field [created_date] did you mean [created_date_millis]?" }

---
"Test PUT setting created_date_millis":
- do:
catch: bad_request
cluster.put_component_template:
name: test_tracking
body:
template:
settings:
number_of_shards: 1
created_date_millis: 1
- match: { status: 400 }
- match: { error.reason: "Validation Failed: 1: Provided a template property which is managed by the system: created_date_millis;" }

---
"Test PUT setting modified_date":
- do:
catch: bad_request
cluster.put_component_template:
name: test_tracking
body:
template:
settings:
number_of_shards: 1
modified_date: "2025-07-04T12:50:48.415Z"
- match: { status: 400 }
- contains: { error.reason: "[component_template] unknown field [modified_date] did you mean [modified_date_millis]?" }

---
"Test PUT setting modified_date_millis":
- do:
catch: bad_request
cluster.put_component_template:
name: test_tracking
body:
template:
settings:
number_of_shards: 1
modified_date_millis: 1
- match: { status: 400 }
- match: { error.reason: "Validation Failed: 1: Provided a template property which is managed by the system: modified_date_millis;" }

---
"Test update preserves created_date but updates modified_date":
- do:
cluster.put_component_template:
name: test_tracking
body:
template:
settings:
number_of_shards: 1
- match: { acknowledged: true }

- do:
cluster.get_component_template:
human: true
name: test_tracking
- set: { component_templates.0.component_template.created_date: first_created }
- set: { component_templates.0.component_template.created_date_millis: first_created_millis }
- set: { component_templates.0.component_template.modified_date: first_modified }
- set: { component_templates.0.component_template.modified_date_millis: first_modified_millis }
- match: { $first_created: "/^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}\\.\\d{3}Z$/" }
- match: { $first_modified: "/^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}\\.\\d{3}Z$/" }
- match: { $first_created: $first_modified }
- match: { $first_created_millis: $first_modified_millis }
- gte: { $first_modified_millis: 0 }

- do:
cluster.put_component_template:
name: test_tracking
body:
template:
settings:
number_of_shards: 2

- do:
cluster.get_component_template:
human: true
name: test_tracking
- set: { component_templates.0.component_template.created_date: second_created }
- set: { component_templates.0.component_template.created_date_millis: second_created_millis }
- match: { $second_created: $first_created }
- match: { $second_created_millis: $first_created_millis }

Original file line number Diff line number Diff line change
Expand Up @@ -353,6 +353,7 @@ static TransportVersion def(int id) {
public static final TransportVersion NODE_WEIGHTS_ADDED_TO_NODE_BALANCE_STATS = def(9_129_0_00);
public static final TransportVersion RERANK_SNIPPETS = def(9_130_0_00);
public static final TransportVersion PIPELINE_TRACKING_INFO = def(9_131_0_00);
public static final TransportVersion COMPONENT_TEMPLATE_TRACKING_INFO = def(9_132_0_00);

/*
* STOP! READ THIS FIRST! No, really,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,18 @@ public ActionRequestValidationException validate() {
if (componentTemplate == null) {
validationException = addValidationError("a component template is required", validationException);
}
if (componentTemplate.createdDateMillis().isPresent()) {
validationException = addValidationError(
"Provided a template property which is managed by the system: created_date_millis",
validationException
);
}
if (componentTemplate.modifiedDateMillis().isPresent()) {
validationException = addValidationError(
"Provided a template property which is managed by the system: modified_date_millis",
validationException
);
}
return validationException;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,9 @@ public static ComponentTemplate normalizeComponentTemplate(
template,
componentTemplate.version(),
componentTemplate.metadata(),
componentTemplate.deprecated()
componentTemplate.deprecated(),
componentTemplate.createdDateMillis().orElse(null),
componentTemplate.modifiedDateMillis().orElse(null)
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import java.io.IOException;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;

/**
* A component template is a re-usable {@link Template} as well as metadata about the template. Each
Expand All @@ -38,19 +39,25 @@ public class ComponentTemplate implements SimpleDiffable<ComponentTemplate>, ToX
private static final ParseField VERSION = new ParseField("version");
private static final ParseField METADATA = new ParseField("_meta");
private static final ParseField DEPRECATED = new ParseField("deprecated");
private static final ParseField CREATED_DATE = new ParseField("created_date");
private static final ParseField CREATED_DATE_MILLIS = new ParseField("created_date_millis");
private static final ParseField MODIFIED_DATE = new ParseField("modified_date");
private static final ParseField MODIFIED_DATE_MILLIS = new ParseField("modified_date_millis");

@SuppressWarnings("unchecked")
public static final ConstructingObjectParser<ComponentTemplate, Void> PARSER = new ConstructingObjectParser<>(
"component_template",
false,
a -> new ComponentTemplate((Template) a[0], (Long) a[1], (Map<String, Object>) a[2], (Boolean) a[3])
a -> new ComponentTemplate((Template) a[0], (Long) a[1], (Map<String, Object>) a[2], (Boolean) a[3], (Long) a[4], (Long) a[5])
);

static {
PARSER.declareObject(ConstructingObjectParser.constructorArg(), Template.PARSER, TEMPLATE);
PARSER.declareLong(ConstructingObjectParser.optionalConstructorArg(), VERSION);
PARSER.declareObject(ConstructingObjectParser.optionalConstructorArg(), (p, c) -> p.map(), METADATA);
PARSER.declareBoolean(ConstructingObjectParser.optionalConstructorArg(), DEPRECATED);
PARSER.declareLong(ConstructingObjectParser.optionalConstructorArg(), CREATED_DATE_MILLIS);
PARSER.declareLong(ConstructingObjectParser.optionalConstructorArg(), MODIFIED_DATE_MILLIS);
}

private final Template template;
Expand All @@ -60,6 +67,10 @@ public class ComponentTemplate implements SimpleDiffable<ComponentTemplate>, ToX
private final Map<String, Object> metadata;
@Nullable
private final Boolean deprecated;
@Nullable
private final Long createdDateMillis;
@Nullable
private final Long modifiedDateMillis;

static Diff<ComponentTemplate> readComponentTemplateDiffFrom(StreamInput in) throws IOException {
return SimpleDiffable.readDiffFrom(ComponentTemplate::new, in);
Expand All @@ -70,19 +81,23 @@ public static ComponentTemplate parse(XContentParser parser) {
}

public ComponentTemplate(Template template, @Nullable Long version, @Nullable Map<String, Object> metadata) {
this(template, version, metadata, null);
this(template, version, metadata, null, null, null);
}

public ComponentTemplate(
Template template,
@Nullable Long version,
@Nullable Map<String, Object> metadata,
@Nullable Boolean deprecated
@Nullable Boolean deprecated,
@Nullable Long createdDateMillis,
@Nullable Long modifiedDateMillis
) {
this.template = template;
this.version = version;
this.metadata = metadata;
this.deprecated = deprecated;
this.createdDateMillis = createdDateMillis;
this.modifiedDateMillis = modifiedDateMillis;
}

public ComponentTemplate(StreamInput in) throws IOException {
Expand All @@ -98,6 +113,13 @@ public ComponentTemplate(StreamInput in) throws IOException {
} else {
deprecated = null;
}
if (in.getTransportVersion().onOrAfter(TransportVersions.COMPONENT_TEMPLATE_TRACKING_INFO)) {
this.createdDateMillis = in.readOptionalLong();
this.modifiedDateMillis = in.readOptionalLong();
} else {
this.createdDateMillis = null;
this.modifiedDateMillis = null;
}
}

public Template template() {
Expand All @@ -122,6 +144,14 @@ public boolean isDeprecated() {
return Boolean.TRUE.equals(deprecated);
}

public Optional<Long> createdDateMillis() {
return Optional.ofNullable(createdDateMillis);
}

public Optional<Long> modifiedDateMillis() {
return Optional.ofNullable(modifiedDateMillis);
}

@Override
public void writeTo(StreamOutput out) throws IOException {
this.template.writeTo(out);
Expand All @@ -135,11 +165,15 @@ public void writeTo(StreamOutput out) throws IOException {
if (out.getTransportVersion().onOrAfter(TransportVersions.V_8_12_0)) {
out.writeOptionalBoolean(this.deprecated);
}
if (out.getTransportVersion().onOrAfter(TransportVersions.COMPONENT_TEMPLATE_TRACKING_INFO)) {
out.writeOptionalLong(this.createdDateMillis);
out.writeOptionalLong(this.modifiedDateMillis);
}
}

@Override
public int hashCode() {
return Objects.hash(template, version, metadata, deprecated);
return Objects.hash(template, version, metadata, deprecated, createdDateMillis, modifiedDateMillis);
}

@Override
Expand All @@ -154,7 +188,9 @@ public boolean equals(Object obj) {
return Objects.equals(template, other.template)
&& Objects.equals(version, other.version)
&& Objects.equals(metadata, other.metadata)
&& Objects.equals(deprecated, other.deprecated);
&& Objects.equals(deprecated, other.deprecated)
&& Objects.equals(createdDateMillis, other.createdDateMillis)
&& Objects.equals(modifiedDateMillis, other.modifiedDateMillis);
}

@Override
Expand Down Expand Up @@ -184,6 +220,20 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params, @Nulla
if (this.deprecated != null) {
builder.field(DEPRECATED.getPreferredName(), this.deprecated);
}
if (this.createdDateMillis != null) {
builder.timestampFieldsFromUnixEpochMillis(
CREATED_DATE_MILLIS.getPreferredName(),
CREATED_DATE.getPreferredName(),
this.createdDateMillis
);
}
if (this.modifiedDateMillis != null) {
builder.timestampFieldsFromUnixEpochMillis(
MODIFIED_DATE_MILLIS.getPreferredName(),
MODIFIED_DATE.getPreferredName(),
this.modifiedDateMillis
);
}
builder.endObject();
return builder;
}
Expand Down
Loading
Loading