We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Support `match_only_text` type fallback for ES 6 artifacts
The `match_only_text` field data type will be introduced in Elasticsearch 7.14 and an upcoming ECS version via an [RFC proposal](https://github.com/elastic/ecs/blob/master/rfcs/text/0023-match_only_text-data-type.md).

Since `match_only_text` is a feature available in 7.14+, artifacts generated targeting ES 6.x will need to fall back to using the `text` data type.

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `scripts/generators/es_template.py`
Content:
```
1 import copy
2 import json
3 import sys
4 
5 from os.path import join
6 
7 from generators import ecs_helpers
8 from schema.cleaner import field_or_multi_field_datatype_defaults
9 
10 
11 TYPE_FALLBACKS = {
12     'constant_keyword': 'keyword',
13     'wildcard': 'keyword',
14     'version': 'keyword'
15 }
16 
17 # Composable Template
18 
19 
20 def generate(ecs_nested, ecs_version, out_dir, mapping_settings_file):
21     """This generates all artifacts for the composable template approach"""
22     all_component_templates(ecs_nested, ecs_version, out_dir)
23     component_names = component_name_convention(ecs_version, ecs_nested)
24     save_composable_template(ecs_version, component_names, out_dir, mapping_settings_file)
25 
26 
27 def save_composable_template(ecs_version, component_names, out_dir, mapping_settings_file):
28     """Generate the master sample composable template"""
29     template = {
30         "index_patterns": ["try-ecs-*"],
31         "composed_of": component_names,
32         "priority": 1,  # Very low, as this is a sample template
33         "_meta": {
34             "ecs_version": ecs_version,
35             "description": "Sample composable template that includes all ECS fields"
36         },
37         "template": {
38             "settings": {
39                 "index": {
40                     "mapping": {
41                         "total_fields": {
42                             "limit": 2000
43                         }
44                     }
45                 }
46             },
47             "mappings": mapping_settings(mapping_settings_file)
48         }
49     }
50     filename = join(out_dir, "elasticsearch/template.json")
51     save_json(filename, template)
52 
53 
54 def all_component_templates(ecs_nested, ecs_version, out_dir):
55     """Generate one component template per field set"""
56     component_dir = join(out_dir, 'elasticsearch/component')
57     ecs_helpers.make_dirs(component_dir)
58 
59     for (fieldset_name, fieldset) in candidate_components(ecs_nested).items():
60         field_mappings = {}
61         for (flat_name, field) in fieldset['fields'].items():
62             name_parts = flat_name.split('.')
63             dict_add_nested(field_mappings, name_parts, entry_for(field))
64 
65         save_component_template(fieldset_name, ecs_version, component_dir, field_mappings)
66 
67 
68 def save_component_template(template_name, ecs_version, out_dir, field_mappings):
69     filename = join(out_dir, template_name) + ".json"
70     reference_url = "https://www.elastic.co/guide/en/ecs/current/ecs-{}.html".format(template_name)
71 
72     template = {
73         'template': {'mappings': {'properties': field_mappings}},
74         '_meta': {
75             'ecs_version': ecs_version,
76             'documentation': reference_url
77         }
78     }
79     save_json(filename, template)
80 
81 
82 def component_name_convention(ecs_version, ecs_nested):
83     version = ecs_version.replace('+', '-')
84     names = []
85     for (fieldset_name, fieldset) in candidate_components(ecs_nested).items():
86         names.append("ecs_{}_{}".format(version, fieldset_name.lower()))
87     return names
88 
89 
90 def candidate_components(ecs_nested):
91     """Returns same structure as ecs_nested, but skips all field sets with reusable.top_level: False"""
92     components = {}
93     for (fieldset_name, fieldset) in ecs_nested.items():
94         if fieldset.get('reusable', None):
95             if not fieldset['reusable']['top_level']:
96                 continue
97         components[fieldset_name] = fieldset
98     return components
99 
100 
101 # Legacy template
102 
103 
104 def generate_legacy(ecs_flat, ecs_version, out_dir, template_settings_file, mapping_settings_file):
105     """Generate the legacy index template"""
106     field_mappings = {}
107     for flat_name in sorted(ecs_flat):
108         field = ecs_flat[flat_name]
109         name_parts = flat_name.split('.')
110         dict_add_nested(field_mappings, name_parts, entry_for(field))
111 
112     mappings_section = mapping_settings(mapping_settings_file)
113     mappings_section['properties'] = field_mappings
114 
115     generate_legacy_template_version(6, ecs_version, mappings_section, out_dir, template_settings_file)
116     generate_legacy_template_version(7, ecs_version, mappings_section, out_dir, template_settings_file)
117 
118 
119 def generate_legacy_template_version(es_version, ecs_version, mappings_section, out_dir, template_settings_file):
120     ecs_helpers.make_dirs(join(out_dir, 'elasticsearch', str(es_version)))
121     template = template_settings(es_version, ecs_version, mappings_section, template_settings_file)
122 
123     filename = join(out_dir, "elasticsearch/{}/template.json".format(es_version))
124     save_json(filename, template)
125 
126 
127 # Common helpers
128 
129 
130 def dict_add_nested(dct, name_parts, value):
131     current_nesting = name_parts[0]
132     rest_name_parts = name_parts[1:]
133     if len(rest_name_parts) > 0:
134         dct.setdefault(current_nesting, {})
135         dct[current_nesting].setdefault('properties', {})
136 
137         dict_add_nested(
138             dct[current_nesting]['properties'],
139             rest_name_parts,
140             value)
141 
142     else:
143         if current_nesting in dct and 'type' in value and 'object' == value['type']:
144             return
145         dct[current_nesting] = value
146 
147 
148 def entry_for(field):
149     field_entry = {'type': field['type']}
150     try:
151         if field['type'] == 'object' or field['type'] == 'nested':
152             if 'enabled' in field and not field['enabled']:
153                 ecs_helpers.dict_copy_existing_keys(field, field_entry, ['enabled'])
154         # the index field is only valid for field types that are not object and nested
155         elif 'index' in field and not field['index']:
156             ecs_helpers.dict_copy_existing_keys(field, field_entry, ['index', 'doc_values'])
157 
158         if field['type'] == 'keyword':
159             ecs_helpers.dict_copy_existing_keys(field, field_entry, ['ignore_above'])
160         elif field['type'] == 'constant_keyword':
161             ecs_helpers.dict_copy_existing_keys(field, field_entry, ['value'])
162         elif field['type'] == 'text':
163             ecs_helpers.dict_copy_existing_keys(field, field_entry, ['norms'])
164         elif field['type'] == 'alias':
165             ecs_helpers.dict_copy_existing_keys(field, field_entry, ['path'])
166         elif field['type'] == 'scaled_float':
167             ecs_helpers.dict_copy_existing_keys(field, field_entry, ['scaling_factor'])
168 
169         if 'multi_fields' in field:
170             field_entry['fields'] = {}
171             for mf in field['multi_fields']:
172                 mf_type = mf['type']
173                 mf_entry = {'type': mf_type}
174                 if mf_type == 'keyword':
175                     ecs_helpers.dict_copy_existing_keys(mf, mf_entry, ['normalizer', 'ignore_above'])
176                 elif mf_type == 'text':
177                     ecs_helpers.dict_copy_existing_keys(mf, mf_entry, ['norms'])
178                 field_entry['fields'][mf['name']] = mf_entry
179 
180     except KeyError as ex:
181         print("Exception {} occurred for field {}".format(ex, field))
182         raise ex
183     return field_entry
184 
185 
186 def mapping_settings(mapping_settings_file):
187     if mapping_settings_file:
188         with open(mapping_settings_file) as f:
189             mappings = json.load(f)
190     else:
191         mappings = default_mapping_settings()
192     return mappings
193 
194 
195 def template_settings(es_version, ecs_version, mappings_section, template_settings_file):
196     if template_settings_file:
197         with open(template_settings_file) as f:
198             template = json.load(f)
199     else:
200         template = default_template_settings(ecs_version)
201 
202     if es_version == 6:
203         mappings_section = copy.deepcopy(mappings_section)
204         es6_type_fallback(mappings_section['properties'])
205 
206         # error.stack_trace needs special handling to set
207         # index: false and doc_values: false if the field
208         # is present in the mappings
209         try:
210             error_stack_trace_mappings = mappings_section['properties']['error']['properties']['stack_trace']
211             error_stack_trace_mappings.setdefault('index', False)
212             error_stack_trace_mappings.setdefault('doc_values', False)
213         except KeyError:
214             pass
215 
216         template['mappings'] = {'_doc': mappings_section}
217     else:
218         template['mappings'] = mappings_section
219 
220     # _meta can't be at template root in legacy templates, so moving back to mappings section
221     # if present
222     if '_meta' in template:
223         mappings_section['_meta'] = template.pop('_meta')
224 
225     return template
226 
227 
228 def save_json(file, data):
229     open_mode = "wb"
230     if sys.version_info >= (3, 0):
231         open_mode = "w"
232     with open(file, open_mode) as jsonfile:
233         jsonfile.write(json.dumps(data, indent=2, sort_keys=True))
234 
235 
236 def default_template_settings(ecs_version):
237     return {
238         "index_patterns": ["try-ecs-*"],
239         "_meta": {"version": ecs_version},
240         "order": 1,
241         "settings": {
242             "index": {
243                 "mapping": {
244                     "total_fields": {
245                         "limit": 10000
246                     }
247                 },
248                 "refresh_interval": "5s"
249             }
250         }
251     }
252 
253 
254 def default_mapping_settings():
255     return {
256         "date_detection": False,
257         "dynamic_templates": [
258             {
259                 "strings_as_keyword": {
260                     "mapping": {
261                         "ignore_above": 1024,
262                         "type": "keyword"
263                     },
264                     "match_mapping_type": "string"
265                 }
266             }
267         ]
268     }
269 
270 
271 def es6_type_fallback(mappings):
272     """
273     Visits each leaf in mappings object and fallback to an
274     Elasticsearch 6.x supported type.
275 
276     Since a field like `wildcard` won't have the same defaults as
277     a `keyword` field, we must add any missing defaults.
278     """
279 
280     for (name, details) in mappings.items():
281         if 'type' in details:
282             fallback_type = TYPE_FALLBACKS.get(details['type'])
283             if fallback_type:
284                 mappings[name]['type'] = fallback_type
285                 field_or_multi_field_datatype_defaults(mappings[name])
286         if 'properties' in details:
287             es6_type_fallback(details['properties'])
288
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/scripts/generators/es_template.py b/scripts/generators/es_template.py
--- a/scripts/generators/es_template.py
+++ b/scripts/generators/es_template.py
@@ -11,7 +11,8 @@
 TYPE_FALLBACKS = {
     'constant_keyword': 'keyword',
     'wildcard': 'keyword',
-    'version': 'keyword'
+    'version': 'keyword',
+    'match_only_text': 'text'
 }
 
 # Composable Template
@@ -283,5 +284,13 @@
             if fallback_type:
                 mappings[name]['type'] = fallback_type
                 field_or_multi_field_datatype_defaults(mappings[name])
+        # support multi-fields
+        if 'fields' in details:
+            # potentially multiple multi-fields
+            for field_name, field_value in details['fields'].items():
+                fallback_type = TYPE_FALLBACKS.get(field_value['type'])
+                if fallback_type:
+                    mappings[name]['fields'][field_name]['type'] = fallback_type
+                    field_or_multi_field_datatype_defaults(mappings[name]['fields'][field_name])
         if 'properties' in details:
             es6_type_fallback(details['properties'])

{"golden_diff": "diff --git a/scripts/generators/es_template.py b/scripts/generators/es_template.py\n--- a/scripts/generators/es_template.py\n+++ b/scripts/generators/es_template.py\n@@ -11,7 +11,8 @@\n TYPE_FALLBACKS = {\n     'constant_keyword': 'keyword',\n     'wildcard': 'keyword',\n-    'version': 'keyword'\n+    'version': 'keyword',\n+    'match_only_text': 'text'\n }\n \n # Composable Template\n@@ -283,5 +284,13 @@\n             if fallback_type:\n                 mappings[name]['type'] = fallback_type\n                 field_or_multi_field_datatype_defaults(mappings[name])\n+        # support multi-fields\n+        if 'fields' in details:\n+            # potentially multiple multi-fields\n+            for field_name, field_value in details['fields'].items():\n+                fallback_type = TYPE_FALLBACKS.get(field_value['type'])\n+                if fallback_type:\n+                    mappings[name]['fields'][field_name]['type'] = fallback_type\n+                    field_or_multi_field_datatype_defaults(mappings[name]['fields'][field_name])\n         if 'properties' in details:\n             es6_type_fallback(details['properties'])\n", "issue": "Support `match_only_text` type fallback for ES 6 artifacts\nThe `match_only_text` field data type will be introduced in Elasticsearch 7.14 and an upcoming ECS version via an [RFC proposal](https://github.com/elastic/ecs/blob/master/rfcs/text/0023-match_only_text-data-type.md).\r\n\r\nSince `match_only_text` is a feature available in 7.14+, artifacts generated targeting ES 6.x will need to fall back to using the `text` data type.\n", "before_files": [{"content": "import copy\nimport json\nimport sys\n\nfrom os.path import join\n\nfrom generators import ecs_helpers\nfrom schema.cleaner import field_or_multi_field_datatype_defaults\n\n\nTYPE_FALLBACKS = {\n    'constant_keyword': 'keyword',\n    'wildcard': 'keyword',\n    'version': 'keyword'\n}\n\n# Composable Template\n\n\ndef generate(ecs_nested, ecs_version, out_dir, mapping_settings_file):\n    \"\"\"This generates all artifacts for the composable template approach\"\"\"\n    all_component_templates(ecs_nested, ecs_version, out_dir)\n    component_names = component_name_convention(ecs_version, ecs_nested)\n    save_composable_template(ecs_version, component_names, out_dir, mapping_settings_file)\n\n\ndef save_composable_template(ecs_version, component_names, out_dir, mapping_settings_file):\n    \"\"\"Generate the master sample composable template\"\"\"\n    template = {\n        \"index_patterns\": [\"try-ecs-*\"],\n        \"composed_of\": component_names,\n        \"priority\": 1,  # Very low, as this is a sample template\n        \"_meta\": {\n            \"ecs_version\": ecs_version,\n            \"description\": \"Sample composable template that includes all ECS fields\"\n        },\n        \"template\": {\n            \"settings\": {\n                \"index\": {\n                    \"mapping\": {\n                        \"total_fields\": {\n                            \"limit\": 2000\n                        }\n                    }\n                }\n            },\n            \"mappings\": mapping_settings(mapping_settings_file)\n        }\n    }\n    filename = join(out_dir, \"elasticsearch/template.json\")\n    save_json(filename, template)\n\n\ndef all_component_templates(ecs_nested, ecs_version, out_dir):\n    \"\"\"Generate one component template per field set\"\"\"\n    component_dir = join(out_dir, 'elasticsearch/component')\n    ecs_helpers.make_dirs(component_dir)\n\n    for (fieldset_name, fieldset) in candidate_components(ecs_nested).items():\n        field_mappings = {}\n        for (flat_name, field) in fieldset['fields'].items():\n            name_parts = flat_name.split('.')\n            dict_add_nested(field_mappings, name_parts, entry_for(field))\n\n        save_component_template(fieldset_name, ecs_version, component_dir, field_mappings)\n\n\ndef save_component_template(template_name, ecs_version, out_dir, field_mappings):\n    filename = join(out_dir, template_name) + \".json\"\n    reference_url = \"https://www.elastic.co/guide/en/ecs/current/ecs-{}.html\".format(template_name)\n\n    template = {\n        'template': {'mappings': {'properties': field_mappings}},\n        '_meta': {\n            'ecs_version': ecs_version,\n            'documentation': reference_url\n        }\n    }\n    save_json(filename, template)\n\n\ndef component_name_convention(ecs_version, ecs_nested):\n    version = ecs_version.replace('+', '-')\n    names = []\n    for (fieldset_name, fieldset) in candidate_components(ecs_nested).items():\n        names.append(\"ecs_{}_{}\".format(version, fieldset_name.lower()))\n    return names\n\n\ndef candidate_components(ecs_nested):\n    \"\"\"Returns same structure as ecs_nested, but skips all field sets with reusable.top_level: False\"\"\"\n    components = {}\n    for (fieldset_name, fieldset) in ecs_nested.items():\n        if fieldset.get('reusable', None):\n            if not fieldset['reusable']['top_level']:\n                continue\n        components[fieldset_name] = fieldset\n    return components\n\n\n# Legacy template\n\n\ndef generate_legacy(ecs_flat, ecs_version, out_dir, template_settings_file, mapping_settings_file):\n    \"\"\"Generate the legacy index template\"\"\"\n    field_mappings = {}\n    for flat_name in sorted(ecs_flat):\n        field = ecs_flat[flat_name]\n        name_parts = flat_name.split('.')\n        dict_add_nested(field_mappings, name_parts, entry_for(field))\n\n    mappings_section = mapping_settings(mapping_settings_file)\n    mappings_section['properties'] = field_mappings\n\n    generate_legacy_template_version(6, ecs_version, mappings_section, out_dir, template_settings_file)\n    generate_legacy_template_version(7, ecs_version, mappings_section, out_dir, template_settings_file)\n\n\ndef generate_legacy_template_version(es_version, ecs_version, mappings_section, out_dir, template_settings_file):\n    ecs_helpers.make_dirs(join(out_dir, 'elasticsearch', str(es_version)))\n    template = template_settings(es_version, ecs_version, mappings_section, template_settings_file)\n\n    filename = join(out_dir, \"elasticsearch/{}/template.json\".format(es_version))\n    save_json(filename, template)\n\n\n# Common helpers\n\n\ndef dict_add_nested(dct, name_parts, value):\n    current_nesting = name_parts[0]\n    rest_name_parts = name_parts[1:]\n    if len(rest_name_parts) > 0:\n        dct.setdefault(current_nesting, {})\n        dct[current_nesting].setdefault('properties', {})\n\n        dict_add_nested(\n            dct[current_nesting]['properties'],\n            rest_name_parts,\n            value)\n\n    else:\n        if current_nesting in dct and 'type' in value and 'object' == value['type']:\n            return\n        dct[current_nesting] = value\n\n\ndef entry_for(field):\n    field_entry = {'type': field['type']}\n    try:\n        if field['type'] == 'object' or field['type'] == 'nested':\n            if 'enabled' in field and not field['enabled']:\n                ecs_helpers.dict_copy_existing_keys(field, field_entry, ['enabled'])\n        # the index field is only valid for field types that are not object and nested\n        elif 'index' in field and not field['index']:\n            ecs_helpers.dict_copy_existing_keys(field, field_entry, ['index', 'doc_values'])\n\n        if field['type'] == 'keyword':\n            ecs_helpers.dict_copy_existing_keys(field, field_entry, ['ignore_above'])\n        elif field['type'] == 'constant_keyword':\n            ecs_helpers.dict_copy_existing_keys(field, field_entry, ['value'])\n        elif field['type'] == 'text':\n            ecs_helpers.dict_copy_existing_keys(field, field_entry, ['norms'])\n        elif field['type'] == 'alias':\n            ecs_helpers.dict_copy_existing_keys(field, field_entry, ['path'])\n        elif field['type'] == 'scaled_float':\n            ecs_helpers.dict_copy_existing_keys(field, field_entry, ['scaling_factor'])\n\n        if 'multi_fields' in field:\n            field_entry['fields'] = {}\n            for mf in field['multi_fields']:\n                mf_type = mf['type']\n                mf_entry = {'type': mf_type}\n                if mf_type == 'keyword':\n                    ecs_helpers.dict_copy_existing_keys(mf, mf_entry, ['normalizer', 'ignore_above'])\n                elif mf_type == 'text':\n                    ecs_helpers.dict_copy_existing_keys(mf, mf_entry, ['norms'])\n                field_entry['fields'][mf['name']] = mf_entry\n\n    except KeyError as ex:\n        print(\"Exception {} occurred for field {}\".format(ex, field))\n        raise ex\n    return field_entry\n\n\ndef mapping_settings(mapping_settings_file):\n    if mapping_settings_file:\n        with open(mapping_settings_file) as f:\n            mappings = json.load(f)\n    else:\n        mappings = default_mapping_settings()\n    return mappings\n\n\ndef template_settings(es_version, ecs_version, mappings_section, template_settings_file):\n    if template_settings_file:\n        with open(template_settings_file) as f:\n            template = json.load(f)\n    else:\n        template = default_template_settings(ecs_version)\n\n    if es_version == 6:\n        mappings_section = copy.deepcopy(mappings_section)\n        es6_type_fallback(mappings_section['properties'])\n\n        # error.stack_trace needs special handling to set\n        # index: false and doc_values: false if the field\n        # is present in the mappings\n        try:\n            error_stack_trace_mappings = mappings_section['properties']['error']['properties']['stack_trace']\n            error_stack_trace_mappings.setdefault('index', False)\n            error_stack_trace_mappings.setdefault('doc_values', False)\n        except KeyError:\n            pass\n\n        template['mappings'] = {'_doc': mappings_section}\n    else:\n        template['mappings'] = mappings_section\n\n    # _meta can't be at template root in legacy templates, so moving back to mappings section\n    # if present\n    if '_meta' in template:\n        mappings_section['_meta'] = template.pop('_meta')\n\n    return template\n\n\ndef save_json(file, data):\n    open_mode = \"wb\"\n    if sys.version_info >= (3, 0):\n        open_mode = \"w\"\n    with open(file, open_mode) as jsonfile:\n        jsonfile.write(json.dumps(data, indent=2, sort_keys=True))\n\n\ndef default_template_settings(ecs_version):\n    return {\n        \"index_patterns\": [\"try-ecs-*\"],\n        \"_meta\": {\"version\": ecs_version},\n        \"order\": 1,\n        \"settings\": {\n            \"index\": {\n                \"mapping\": {\n                    \"total_fields\": {\n                        \"limit\": 10000\n                    }\n                },\n                \"refresh_interval\": \"5s\"\n            }\n        }\n    }\n\n\ndef default_mapping_settings():\n    return {\n        \"date_detection\": False,\n        \"dynamic_templates\": [\n            {\n                \"strings_as_keyword\": {\n                    \"mapping\": {\n                        \"ignore_above\": 1024,\n                        \"type\": \"keyword\"\n                    },\n                    \"match_mapping_type\": \"string\"\n                }\n            }\n        ]\n    }\n\n\ndef es6_type_fallback(mappings):\n    \"\"\"\n    Visits each leaf in mappings object and fallback to an\n    Elasticsearch 6.x supported type.\n\n    Since a field like `wildcard` won't have the same defaults as\n    a `keyword` field, we must add any missing defaults.\n    \"\"\"\n\n    for (name, details) in mappings.items():\n        if 'type' in details:\n            fallback_type = TYPE_FALLBACKS.get(details['type'])\n            if fallback_type:\n                mappings[name]['type'] = fallback_type\n                field_or_multi_field_datatype_defaults(mappings[name])\n        if 'properties' in details:\n            es6_type_fallback(details['properties'])\n", "path": "scripts/generators/es_template.py"}], "after_files": [{"content": "import copy\nimport json\nimport sys\n\nfrom os.path import join\n\nfrom generators import ecs_helpers\nfrom schema.cleaner import field_or_multi_field_datatype_defaults\n\n\nTYPE_FALLBACKS = {\n    'constant_keyword': 'keyword',\n    'wildcard': 'keyword',\n    'version': 'keyword',\n    'match_only_text': 'text'\n}\n\n# Composable Template\n\n\ndef generate(ecs_nested, ecs_version, out_dir, mapping_settings_file):\n    \"\"\"This generates all artifacts for the composable template approach\"\"\"\n    all_component_templates(ecs_nested, ecs_version, out_dir)\n    component_names = component_name_convention(ecs_version, ecs_nested)\n    save_composable_template(ecs_version, component_names, out_dir, mapping_settings_file)\n\n\ndef save_composable_template(ecs_version, component_names, out_dir, mapping_settings_file):\n    \"\"\"Generate the master sample composable template\"\"\"\n    template = {\n        \"index_patterns\": [\"try-ecs-*\"],\n        \"composed_of\": component_names,\n        \"priority\": 1,  # Very low, as this is a sample template\n        \"_meta\": {\n            \"ecs_version\": ecs_version,\n            \"description\": \"Sample composable template that includes all ECS fields\"\n        },\n        \"template\": {\n            \"settings\": {\n                \"index\": {\n                    \"mapping\": {\n                        \"total_fields\": {\n                            \"limit\": 2000\n                        }\n                    }\n                }\n            },\n            \"mappings\": mapping_settings(mapping_settings_file)\n        }\n    }\n    filename = join(out_dir, \"elasticsearch/template.json\")\n    save_json(filename, template)\n\n\ndef all_component_templates(ecs_nested, ecs_version, out_dir):\n    \"\"\"Generate one component template per field set\"\"\"\n    component_dir = join(out_dir, 'elasticsearch/component')\n    ecs_helpers.make_dirs(component_dir)\n\n    for (fieldset_name, fieldset) in candidate_components(ecs_nested).items():\n        field_mappings = {}\n        for (flat_name, field) in fieldset['fields'].items():\n            name_parts = flat_name.split('.')\n            dict_add_nested(field_mappings, name_parts, entry_for(field))\n\n        save_component_template(fieldset_name, ecs_version, component_dir, field_mappings)\n\n\ndef save_component_template(template_name, ecs_version, out_dir, field_mappings):\n    filename = join(out_dir, template_name) + \".json\"\n    reference_url = \"https://www.elastic.co/guide/en/ecs/current/ecs-{}.html\".format(template_name)\n\n    template = {\n        'template': {'mappings': {'properties': field_mappings}},\n        '_meta': {\n            'ecs_version': ecs_version,\n            'documentation': reference_url\n        }\n    }\n    save_json(filename, template)\n\n\ndef component_name_convention(ecs_version, ecs_nested):\n    version = ecs_version.replace('+', '-')\n    names = []\n    for (fieldset_name, fieldset) in candidate_components(ecs_nested).items():\n        names.append(\"ecs_{}_{}\".format(version, fieldset_name.lower()))\n    return names\n\n\ndef candidate_components(ecs_nested):\n    \"\"\"Returns same structure as ecs_nested, but skips all field sets with reusable.top_level: False\"\"\"\n    components = {}\n    for (fieldset_name, fieldset) in ecs_nested.items():\n        if fieldset.get('reusable', None):\n            if not fieldset['reusable']['top_level']:\n                continue\n        components[fieldset_name] = fieldset\n    return components\n\n\n# Legacy template\n\n\ndef generate_legacy(ecs_flat, ecs_version, out_dir, template_settings_file, mapping_settings_file):\n    \"\"\"Generate the legacy index template\"\"\"\n    field_mappings = {}\n    for flat_name in sorted(ecs_flat):\n        field = ecs_flat[flat_name]\n        name_parts = flat_name.split('.')\n        dict_add_nested(field_mappings, name_parts, entry_for(field))\n\n    mappings_section = mapping_settings(mapping_settings_file)\n    mappings_section['properties'] = field_mappings\n\n    generate_legacy_template_version(6, ecs_version, mappings_section, out_dir, template_settings_file)\n    generate_legacy_template_version(7, ecs_version, mappings_section, out_dir, template_settings_file)\n\n\ndef generate_legacy_template_version(es_version, ecs_version, mappings_section, out_dir, template_settings_file):\n    ecs_helpers.make_dirs(join(out_dir, 'elasticsearch', str(es_version)))\n    template = template_settings(es_version, ecs_version, mappings_section, template_settings_file)\n\n    filename = join(out_dir, \"elasticsearch/{}/template.json\".format(es_version))\n    save_json(filename, template)\n\n\n# Common helpers\n\n\ndef dict_add_nested(dct, name_parts, value):\n    current_nesting = name_parts[0]\n    rest_name_parts = name_parts[1:]\n    if len(rest_name_parts) > 0:\n        dct.setdefault(current_nesting, {})\n        dct[current_nesting].setdefault('properties', {})\n\n        dict_add_nested(\n            dct[current_nesting]['properties'],\n            rest_name_parts,\n            value)\n\n    else:\n        if current_nesting in dct and 'type' in value and 'object' == value['type']:\n            return\n        dct[current_nesting] = value\n\n\ndef entry_for(field):\n    field_entry = {'type': field['type']}\n    try:\n        if field['type'] == 'object' or field['type'] == 'nested':\n            if 'enabled' in field and not field['enabled']:\n                ecs_helpers.dict_copy_existing_keys(field, field_entry, ['enabled'])\n        # the index field is only valid for field types that are not object and nested\n        elif 'index' in field and not field['index']:\n            ecs_helpers.dict_copy_existing_keys(field, field_entry, ['index', 'doc_values'])\n\n        if field['type'] == 'keyword':\n            ecs_helpers.dict_copy_existing_keys(field, field_entry, ['ignore_above'])\n        elif field['type'] == 'constant_keyword':\n            ecs_helpers.dict_copy_existing_keys(field, field_entry, ['value'])\n        elif field['type'] == 'text':\n            ecs_helpers.dict_copy_existing_keys(field, field_entry, ['norms'])\n        elif field['type'] == 'alias':\n            ecs_helpers.dict_copy_existing_keys(field, field_entry, ['path'])\n        elif field['type'] == 'scaled_float':\n            ecs_helpers.dict_copy_existing_keys(field, field_entry, ['scaling_factor'])\n\n        if 'multi_fields' in field:\n            field_entry['fields'] = {}\n            for mf in field['multi_fields']:\n                mf_type = mf['type']\n                mf_entry = {'type': mf_type}\n                if mf_type == 'keyword':\n                    ecs_helpers.dict_copy_existing_keys(mf, mf_entry, ['normalizer', 'ignore_above'])\n                elif mf_type == 'text':\n                    ecs_helpers.dict_copy_existing_keys(mf, mf_entry, ['norms'])\n                field_entry['fields'][mf['name']] = mf_entry\n\n    except KeyError as ex:\n        print(\"Exception {} occurred for field {}\".format(ex, field))\n        raise ex\n    return field_entry\n\n\ndef mapping_settings(mapping_settings_file):\n    if mapping_settings_file:\n        with open(mapping_settings_file) as f:\n            mappings = json.load(f)\n    else:\n        mappings = default_mapping_settings()\n    return mappings\n\n\ndef template_settings(es_version, ecs_version, mappings_section, template_settings_file):\n    if template_settings_file:\n        with open(template_settings_file) as f:\n            template = json.load(f)\n    else:\n        template = default_template_settings(ecs_version)\n\n    if es_version == 6:\n        mappings_section = copy.deepcopy(mappings_section)\n        es6_type_fallback(mappings_section['properties'])\n\n        # error.stack_trace needs special handling to set\n        # index: false and doc_values: false if the field\n        # is present in the mappings\n        try:\n            error_stack_trace_mappings = mappings_section['properties']['error']['properties']['stack_trace']\n            error_stack_trace_mappings.setdefault('index', False)\n            error_stack_trace_mappings.setdefault('doc_values', False)\n        except KeyError:\n            pass\n\n        template['mappings'] = {'_doc': mappings_section}\n    else:\n        template['mappings'] = mappings_section\n\n    # _meta can't be at template root in legacy templates, so moving back to mappings section\n    # if present\n    if '_meta' in template:\n        mappings_section['_meta'] = template.pop('_meta')\n\n    return template\n\n\ndef save_json(file, data):\n    open_mode = \"wb\"\n    if sys.version_info >= (3, 0):\n        open_mode = \"w\"\n    with open(file, open_mode) as jsonfile:\n        jsonfile.write(json.dumps(data, indent=2, sort_keys=True))\n\n\ndef default_template_settings(ecs_version):\n    return {\n        \"index_patterns\": [\"try-ecs-*\"],\n        \"_meta\": {\"version\": ecs_version},\n        \"order\": 1,\n        \"settings\": {\n            \"index\": {\n                \"mapping\": {\n                    \"total_fields\": {\n                        \"limit\": 10000\n                    }\n                },\n                \"refresh_interval\": \"5s\"\n            }\n        }\n    }\n\n\ndef default_mapping_settings():\n    return {\n        \"date_detection\": False,\n        \"dynamic_templates\": [\n            {\n                \"strings_as_keyword\": {\n                    \"mapping\": {\n                        \"ignore_above\": 1024,\n                        \"type\": \"keyword\"\n                    },\n                    \"match_mapping_type\": \"string\"\n                }\n            }\n        ]\n    }\n\n\ndef es6_type_fallback(mappings):\n    \"\"\"\n    Visits each leaf in mappings object and fallback to an\n    Elasticsearch 6.x supported type.\n\n    Since a field like `wildcard` won't have the same defaults as\n    a `keyword` field, we must add any missing defaults.\n    \"\"\"\n\n    for (name, details) in mappings.items():\n        if 'type' in details:\n            fallback_type = TYPE_FALLBACKS.get(details['type'])\n            if fallback_type:\n                mappings[name]['type'] = fallback_type\n                field_or_multi_field_datatype_defaults(mappings[name])\n        # support multi-fields\n        if 'fields' in details:\n            # potentially multiple multi-fields\n            for field_name, field_value in details['fields'].items():\n                fallback_type = TYPE_FALLBACKS.get(field_value['type'])\n                if fallback_type:\n                    mappings[name]['fields'][field_name]['type'] = fallback_type\n                    field_or_multi_field_datatype_defaults(mappings[name]['fields'][field_name])\n        if 'properties' in details:\n            es6_type_fallback(details['properties'])\n", "path": "scripts/generators/es_template.py"}]}