Project

General

Profile

« Previous | Next » 

Revision 0c3375cd

Added by Félix DALLIDET over 6 years ago

Fixes #11148: set variables technique

View differences:

maintained-techniques
systemSettings/misc/genericCommandVariableDefinition/3.0
systemSettings/misc/genericVariableDefinition/2.0
systemSettings/misc/partitionSizeMonitoring/3.0
systemSettings/misc/setVars/1.0
systemSettings/misc/variableFromJsonFile/1.0
systemSettings/networking/dnsConfiguration/3.0
systemSettings/networking/hostsConfiguration/2.0
techniques/systemSettings/misc/setVars/1.0/metadata.xml
<TECHNIQUE name="Variables (any)">
<DESCRIPTION>This technique loads rudder variables of type string or dict. They can then be used with a given specific prefix.
<!--
Usage: in directives use ${my_prefix.my_name}, in mustache {{{vars.my_prefix.my_name}}} for string type variable.
${my_prefix.my_name[my_key]} for Dict variable. See the documentation for mustache and dict variable usage: https://www.rudder-project.org/doc-4.1/_generic_methods.html#file_from_template_type
-->
Usage: in directives use ${my_prefix.my_name} for string type variables, ${my_prefix.my_name[my_key]} for Dict variables.
In mustache template see the documentation: https://www.rudder-project.org/doc-4.1/_generic_methods.html#file_from_template_type .
An unrecognized variable may come from a misordering of your rules executions, the execution follows an alphabetical order, based on directives names.
</DESCRIPTION>
<MULTIINSTANCE>true</MULTIINSTANCE>
<TMLS>
<TML name="set-vars"/>
</TMLS>
<BUNDLES>
<NAME>set_vars</NAME>
</BUNDLES>
<TRACKINGVARIABLE>
<SAMESIZEAS>SET_VARS_NAME</SAMESIZEAS>
</TRACKINGVARIABLE>
<SYSTEMVARS>
<NAME>SHARED_FILES_FOLDER</NAME>
</SYSTEMVARS>
<SECTIONS>
<SECTION name="Load variables" multivalued="true" component="false">
<SECTION name="Load rudder variables" multivalued="false" component="true" componentKey="SET_VARS_NAME">
<INPUT>
<NAME>SET_VARS_NAME</NAME>
<DESCRIPTION>Variable complete name (of the form my_prefix.my_name)</DESCRIPTION>
<LONGDESCRIPTION>Variable value can be used in other Directive parameters with the following syntax: ${my_prefix.my_name}</LONGDESCRIPTION>
<CONSTRAINT>
<MAYBEEMPTY>false</MAYBEEMPTY>
<TYPE>string</TYPE>
<DEFAULT>myPrefix.myName</DEFAULT>
<REGEX error="The variable name must be of the form my_prefix.my_name">^[\w]+\.[\w]+$</REGEX>
</CONSTRAINT>
</INPUT>
<SELECT1>
<NAME>SET_VARS_TYPE</NAME>
<DESCRIPTION>Variable type</DESCRIPTION>
<LONGDESCRIPTION>Variables can be String or Dict. In the dict case, your source must respect the Json syntax. Dict variables have sub keys. Its values are reached with ${my_prefix.my_name[my_key]}</LONGDESCRIPTION>
<ITEM>
<VALUE>string</VALUE>
<LABEL>String</LABEL>
</ITEM>
<ITEM>
<VALUE>dict</VALUE>
<LABEL>Dict</LABEL>
</ITEM>
<CONSTRAINT>
<DEFAULT>string</DEFAULT>
</CONSTRAINT>
</SELECT1>
<SELECT1>
<NAME>SET_VARS_SRC</NAME>
<DESCRIPTION>Variable source type</DESCRIPTION>
<LONGDESCRIPTION>Variables can be defined from a file, a text input or a command execution result. </LONGDESCRIPTION>
<ITEM>
<VALUE>local_file</VALUE>
<LABEL>Local file</LABEL>
</ITEM>
<ITEM>
<VALUE>remote_file</VALUE>
<LABEL>Remote file</LABEL>
</ITEM>
<ITEM>
<VALUE>command</VALUE>
<LABEL>Command result</LABEL>
</ITEM>
<ITEM>
<VALUE>input</VALUE>
<LABEL>Text input</LABEL>
</ITEM>
<CONSTRAINT>
<DEFAULT>string</DEFAULT>
<MAYBEEMPTY>false</MAYBEEMPTY>
</CONSTRAINT>
</SELECT1>
<INPUT>
<NAME>SET_VARS_FILE_NAME</NAME>
<DESCRIPTION>Source file path</DESCRIPTION>
<LONGDESCRIPTION>Absolute for a local source file, relative to /var/rudder/configuration-repository/shared-files/ for a remote one.</LONGDESCRIPTION>
<CONSTRAINT>
<MAYBEEMPTY>true</MAYBEEMPTY>
<TYPE>string</TYPE>
</CONSTRAINT>
</INPUT>
<INPUT>
<NAME>SET_VARS_STRING</NAME>
<DESCRIPTION>Variable value(s)/Command to execute</DESCRIPTION>
<LONGDESCRIPTION>Leave empty in case of file source type. Fill with a string, a command to execute in the command result case, or a
Json content in Dict type variable with text input.</LONGDESCRIPTION>
<CONSTRAINT>
<DEFAULT>myValue</DEFAULT>
<MAYBEEMPTY>true</MAYBEEMPTY>
<TYPE>textarea</TYPE>
</CONSTRAINT>
</INPUT>
</SECTION>
</SECTION>
</SECTIONS>
</TECHNIQUE>
techniques/systemSettings/misc/setVars/1.0/set-vars.st
# Define one or more variable on rudder
# -Variables can be defined by a Json file or a text input
# -Allowed types are string and dict
bundle agent set_vars {
vars:
#Tracking key
&TRACKINGKEY:{key | "trackingkey[&i&]" string => "&key&";}&
#Shared files
"directory_server" string => "&SHARED_FILES_FOLDER&";
"directory_agent" string => "/var/rudder/resources";
#Variable type : dict or string
&SET_VARS_TYPE: {type|"types[&i&]" string => "&type&";}&
#Variable name and prefix
&SET_VARS_NAME: {name|"complete_names[&i&]" string => "&name&";}&
"strTok[${index}]" slist => splitstring("${complete_names[${index}]}", "\.", "2");
"prefixes[${index}]" string => nth("strTok[${index}]", 0);
"names[${index}]" string => nth("strTok[${index}]", 1);
#Values src
&SET_VARS_SRC: {src|"src_types[&i&]" string => "&src&";}&
#Name of the Json file in case of a file src type
&SET_VARS_FILE_NAME: {file_name|"files_names[&i&]" string => "&file_name&";}&
#Entries in the text input case, can be a command to execute
&SET_VARS_STRING: {entry|"entries[&i&]" string => "&entry&";}&
#Index
"index" slist => getindices("complete_names");
#Values
"values[${index}]" string => "${entries[${index}]}",
ifvarclass => "textSrc_${index}";
"values[${index}]" string => execresult("${entries[${index}]}", "noshell"),
ifvarclass => "commandSrc_${index}";
#Canonified stuff
"files_names_canon[${index}]" string => canonify("${files_names[${index}]}");
"names_canon[${index}]" string => canonify("${names[${index}]}");
classes:
"pass3" expression => "pass2";
"pass2" expression => "pass1";
"pass1" expression => "any";
"dictType_${index}" expression => strcmp("${types[${index}]}", "dict");
"stringType_${index}" expression => strcmp("${types[${index}]}", "string");
"textSrc_${index}" expression => strcmp("${src_types[${index}]}", "input");
"remote_file_source_${index}" expression => strcmp("${src_types[${index}]}", "remote_file");
"local_file_source_${index}" expression => strcmp("${src_types[${index}]}", "local_file");
"fileSrc_${index}" expression => "local_file_source_${index}|remote_file_source_${index}";
"commandSrc_${index}" expression => strcmp("${src_types[${index}]}", "command");
methods:
#Downloading files from the server
"prepare" usebundle => directory_create("${directory_agent}");
"download_${index}"
usebundle =>file_copy_from_remote_source("${directory_server}/${files_names[${index}]}", "${directory_agent}/${files_names_canon[${index}]}"),
ifvarclass => "remote_file_source_${index}";
"download_${index}"
usebundle =>file_copy_from_local_source("${files_names[${index}]}", "${directory_agent}/${files_names_canon[${index}]}"),
ifvarclass => "local_file_source_${index}";
#String type variable case
"load_${names[${index}]}"
usebundle => variable_string("${prefixes[${index}]}", "${names[${index}]}", "${values[${index}]}"),
ifvarclass => "stringType_${index}.(textSrc_${index}|commandSrc_${index})";
"load_${names[${index}]}"
usebundle => variable_string_from_file("${prefixes[${index}]}", "${names[${index}]}", "${directory_agent}/${files_names_canon[${index}]}"),
ifvarclass => "stringType_${index}.fileSrc_${index}";
#Dict type variable case
"load_${names[${index}]}"
usebundle => variable_dict("${prefixes[${index}]}", "${names[${index}]}", "${values[${index}]}"),
ifvarclass => "dictType_${index}.(textSrc_${index}|commandSrc_${index})";
"load_${names[${index}]}"
usebundle => variable_dict_from_file("${prefixes[${index}]}", "${names[${index}]}", "${directory_agent}/${files_names_canon[${index}]}"),
ifvarclass => "dictType_${index}.fileSrc_${index}";
################################################################################
# REPORTING #
################################################################################
"any" usebundle => rudder_common_reports_generic("setVars", "variable_string_${names_canon[${index}]}", "${trackingkey[${index}]}", "Load rudder variables", "${complete_names[${index}]}", "Definition of string variable ${complete_names[${index}]}"),
ifvarclass => "stringType_${index}.(textSrc_${index}|commandSrc_${index})";
"any" usebundle => rudder_common_reports_generic("setVars", "variable_string_from_file_${names_canon[${index}]}", "${trackingkey[${index}]}", "Load rudder variables", "${complete_names[${index}]}", "Definition of string variable ${complete_names[${index}]}"),
ifvarclass => "stringType_${index}.fileSrc_${index}";
"any" usebundle => rudder_common_reports_generic("setVars", "variable_dict_${names_canon[${index}]}", "${trackingkey[${index}]}", "Load rudder variables", "${complete_names[${index}]}", "Definition of dict variable ${complete_names[${index}]}"),
ifvarclass => "dictType_${index}.(textSrc_${index}|commandSrc_${index})";
"any" usebundle => rudder_common_reports_generic("setVars", "variable_dict_from_file_${names_canon[${index}]}", "${trackingkey[${index}]}", "Load rudder variables", "${complete_names[${index}]}", "Definition of dict variable ${complete_names[${index}]}"),
ifvarclass => "dictType_${index}.fileSrc_${index}";
}
techniques/systemSettings/misc/setVars/1.0/tests/check_vars.json
{
"displayName": "ZZZ (1,2) (1,2)",
"enabled": true,
"longDescription": "",
"parameters": {
"section": {
"name": "sections",
"sections": [
{
"section": {
"name": "File to manage",
"sections": [
{
"section": {
"name": "Enforce content by section",
"vars": [
{
"var": {
"name": "GENERIC_FILE_CONTENT_SECTION_MANAGEMENT",
"value": "false"
}
},
{
"var": {
"name": "GENERIC_FILE_SECTION_CONTENT",
"value": ""
}
},
{
"var": {
"name": "GENERIC_FILE_SECTION_FOOTER",
"value": ""
}
},
{
"var": {
"name": "GENERIC_FILE_SECTION_HEADER",
"value": ""
}
}
]
}
},
{
"section": {
"name": "Enforce content only in zone",
"vars": [
{
"var": {
"name": "GENERIC_FILE_CONTENT_MIGRATE_TO_ZONE_BOOLEAN",
"value": "false"
}
},
{
"var": {
"name": "GENERIC_FILE_CONTENT_ZONE_FOOTER",
"value": "### End of section managed by Rudder"
}
},
{
"var": {
"name": "GENERIC_FILE_CONTENT_ZONE_HEADER",
"value": "### Beginning of section managed by Rudder"
}
}
]
}
},
{
"section": {
"name": "File",
"vars": [
{
"var": {
"name": "GENERIC_FILE_CONTENT_CREATE_BOOLEAN",
"value": "true"
}
},
{
"var": {
"name": "GENERIC_FILE_CONTENT_DELETION_BOOLEAN",
"value": "false"
}
},
{
"var": {
"name": "GENERIC_FILE_CONTENT_ENFORCE",
"value": "true"
}
},
{
"var": {
"name": "GENERIC_FILE_CONTENT_ENFORCE_CREATE_ONLY_BOOLEAN",
"value": "false"
}
},
{
"var": {
"name": "GENERIC_FILE_CONTENT_MODIFICATION_BOOLEAN",
"value": "false"
}
},
{
"var": {
"name": "GENERIC_FILE_CONTENT_ONLY_ZONE_BOOLEAN",
"value": "false"
}
},
{
"var": {
"name": "GENERIC_FILE_CONTENT_PATH",
"value": "/tmp/result"
}
}
]
}
},
{
"section": {
"name": "File content",
"vars": [
{
"var": {
"name": "GENERIC_FILE_CONTENT_PAYLOAD",
"value": "############String type###########\r\ntxt input = ${prefix1.str1}\r\ncommand = ${prefix2.cmd1}\r\nfile load = ${prefix3.fileStr}\r\nmust fail from file = ${prefix4.str2}\r\n\r\n#############Dict type############\r\ntxt input = ${prefix1.dict1[key1]} ${prefix1.dict1[key2]}\r\ncommand = ${prefix2.cmd2[key1]} ${prefix2.cmd2[key2]}\r\nremote file load = ${prefix3.fileDict[key1]} ${prefix3.fileDict[key2]}\r\nlocal file load = ${prefix11.localfile[key1]} ${prefix11.localfile[key2]}\r\nmust fail from string = ${prefix5.dict5[key1]} ${prefix5.dict5[key2]}\r\nmust fail from command = ${prefix6.dict2[key1]} ${prefix6.dict2[key2]}\r\nmust fail from file = ${prefix7.dict7[key1]} ${prefix7.dict7[key2]}"
}
}
]
}
},
{
"section": {
"name": "Line deletion regular expressions",
"vars": [
{
"var": {
"name": "GENERIC_FILE_CONTENT_DELETION_REGEXP",
"value": "deleteme.*"
}
}
]
}
},
{
"section": {
"name": "Line replacement regular expressions",
"vars": [
{
"var": {
"name": "GENERIC_FILE_CONTENT_MODIFICATION_DESTINATION",
"value": ""
}
},
{
"var": {
"name": "GENERIC_FILE_CONTENT_MODIFICATION_REGEXP",
"value": "replaceme.*"
}
}
]
}
},
{
"section": {
"name": "Permission adjustment",
"vars": [
{
"var": {
"name": "GENERIC_FILE_CONTENT_GROUP",
"value": "root"
}
},
{
"var": {
"name": "GENERIC_FILE_CONTENT_OWNER",
"value": "root"
}
},
{
"var": {
"name": "GENERIC_FILE_CONTENT_PERM",
"value": "644"
}
},
{
"var": {
"name": "GENERIC_FILE_CONTENT_PERMISSION_ADJUSTMENT",
"value": "false"
}
}
]
}
},
{
"section": {
"name": "Post-modification hook",
"vars": [
{
"var": {
"name": "GENERIC_FILE_CONTENT_POST_HOOK_COMMAND",
"value": ""
}
},
{
"var": {
"name": "GENERIC_FILE_CONTENT_POST_HOOK_RUN",
"value": "false"
}
}
]
}
}
]
}
}
]
}
},
"shortDescription": "",
"techniqueName": "checkGenericFileContent",
"techniqueVersion": "7.0"
}
techniques/systemSettings/misc/setVars/1.0/tests/json1.json
OK
techniques/systemSettings/misc/setVars/1.0/tests/json2.json
{ "key1": "test2", "key2": "is ok"}
techniques/systemSettings/misc/setVars/1.0/tests/json3.json
{ "key1": "test", "key2": "seems ok}
techniques/systemSettings/misc/setVars/1.0/tests/json4.json
{ "key1": "test4", "key2": "is ok"}
techniques/systemSettings/misc/setVars/1.0/tests/test_setVars.cf
#!/usr/local/bin/ncf -f
bundle agent init {
methods:
"any" usebundle => file_remove("/tmp/result");
"any" usebundle => file_enforce_content("/tmp/json4.json", "{ \"key1\": \"test4\", \"key2\": \"is ok\"}", "true");
}
techniques/systemSettings/misc/setVars/1.0/tests/test_setVars.json
{
"displayName": "00Variables (any) (1,1) (1,1) (1,1) (1,1)",
"enabled": true,
"longDescription": "",
"parameters": {
"section": {
"name": "sections",
"sections": [
{
"section": {
"name": "Load variables",
"sections": [
{
"section": {
"name": "Load rudder variables",
"vars": [
{
"var": {
"name": "SET_VARS_FILE_NAME",
"value": ""
}
},
{
"var": {
"name": "SET_VARS_NAME",
"value": "prefix1.str1"
}
},
{
"var": {
"name": "SET_VARS_SRC",
"value": "input"
}
},
{
"var": {
"name": "SET_VARS_STRING",
"value": "OK"
}
},
{
"var": {
"name": "SET_VARS_TYPE",
"value": "string"
}
}
]
}
}
]
}
},
{
"section": {
"name": "Load variables",
"sections": [
{
"section": {
"name": "Load rudder variables",
"vars": [
{
"var": {
"name": "SET_VARS_FILE_NAME",
"value": ""
}
},
{
"var": {
"name": "SET_VARS_NAME",
"value": "prefix2.cmd1"
}
},
{
"var": {
"name": "SET_VARS_SRC",
"value": "command"
}
},
{
"var": {
"name": "SET_VARS_STRING",
"value": "/bin/echo OK"
}
},
{
"var": {
"name": "SET_VARS_TYPE",
"value": "string"
}
}
]
}
}
]
}
},
{
"section": {
"name": "Load variables",
"sections": [
{
"section": {
"name": "Load rudder variables",
"vars": [
{
"var": {
"name": "SET_VARS_FILE_NAME",
"value": "json1.json"
}
},
{
"var": {
"name": "SET_VARS_NAME",
"value": "prefix3.fileStr"
}
},
{
"var": {
"name": "SET_VARS_SRC",
"value": "remote_file"
}
},
{
"var": {
"name": "SET_VARS_STRING",
"value": ""
}
},
{
"var": {
"name": "SET_VARS_TYPE",
"value": "string"
}
}
]
}
}
]
}
},
{
"section": {
"name": "Load variables",
"sections": [
{
"section": {
"name": "Load rudder variables",
"vars": [
{
"var": {
"name": "SET_VARS_FILE_NAME",
"value": "aFileThatDoesNotExist"
}
},
{
"var": {
"name": "SET_VARS_NAME",
"value": "prefix4.str2"
}
},
{
"var": {
"name": "SET_VARS_SRC",
"value": "local_file"
}
},
{
"var": {
"name": "SET_VARS_STRING",
"value": ""
}
},
{
"var": {
"name": "SET_VARS_TYPE",
"value": "string"
}
}
]
}
}
]
}
},
{
"section": {
"name": "Load variables",
"sections": [
{
"section": {
"name": "Load rudder variables",
"vars": [
{
"var": {
"name": "SET_VARS_FILE_NAME",
"value": ""
}
},
{
"var": {
"name": "SET_VARS_NAME",
"value": "prefix1.dict1"
}
},
{
"var": {
"name": "SET_VARS_SRC",
"value": "input"
}
},
{
"var": {
"name": "SET_VARS_STRING",
"value": "{ \"key1\": \"test\", \"key2\": \"seems ok\"}"
}
},
{
"var": {
"name": "SET_VARS_TYPE",
"value": "dict"
}
}
]
}
}
]
}
},
{
"section": {
"name": "Load variables",
"sections": [
{
"section": {
"name": "Load rudder variables",
"vars": [
{
"var": {
"name": "SET_VARS_FILE_NAME",
"value": ""
}
},
{
"var": {
"name": "SET_VARS_NAME",
"value": "prefix2.cmd2"
}
},
{
"var": {
"name": "SET_VARS_SRC",
"value": "command"
}
},
{
"var": {
"name": "SET_VARS_STRING",
"value": "/bin/echo '{ \"key1\": \"Sure\", \"key2\": \"OK\"}'"
}
},
{
"var": {
"name": "SET_VARS_TYPE",
"value": "dict"
}
}
]
}
}
]
}
},
{
"section": {
"name": "Load variables",
"sections": [
{
"section": {
"name": "Load rudder variables",
"vars": [
{
"var": {
"name": "SET_VARS_FILE_NAME",
"value": "json2.json"
}
},
{
"var": {
"name": "SET_VARS_NAME",
"value": "prefix3.fileDict"
}
},
{
"var": {
"name": "SET_VARS_SRC",
"value": "remote_file"
}
},
{
"var": {
"name": "SET_VARS_STRING",
"value": ""
}
},
{
"var": {
"name": "SET_VARS_TYPE",
"value": "dict"
}
}
]
}
}
]
}
},
{
"section": {
"name": "Load variables",
"sections": [
{
"section": {
"name": "Load rudder variables",
"vars": [
{
"var": {
"name": "SET_VARS_FILE_NAME",
"value": ""
}
},
{
"var": {
"name": "SET_VARS_NAME",
"value": "prefix5.dict5"
}
},
{
"var": {
"name": "SET_VARS_SRC",
"value": "input"
}
},
{
"var": {
"name": "SET_VARS_STRING",
"value": "OUI"
}
},
{
"var": {
"name": "SET_VARS_TYPE",
"value": "dict"
}
}
]
}
}
]
}
},
{
"section": {
"name": "Load variables",
"sections": [
{
"section": {
"name": "Load rudder variables",
"vars": [
{
"var": {
"name": "SET_VARS_FILE_NAME",
"value": ""
}
},
{
"var": {
"name": "SET_VARS_NAME",
"value": "prefix6.dict2"
}
},
{
"var": {
"name": "SET_VARS_SRC",
"value": "command"
}
},
{
"var": {
"name": "SET_VARS_STRING",
"value": "/bin/echo '{ \"key1\": \"Sure\", \"key2\": \"OK}'"
}
},
{
"var": {
"name": "SET_VARS_TYPE",
"value": "dict"
}
}
]
}
}
]
}
},
{
"section": {
"name": "Load variables",
"sections": [
{
"section": {
"name": "Load rudder variables",
"vars": [
{
"var": {
"name": "SET_VARS_FILE_NAME",
"value": "json3.json"
}
},
{
"var": {
"name": "SET_VARS_NAME",
"value": "prefix7.dict7"
}
},
{
"var": {
"name": "SET_VARS_SRC",
"value": "remote_file"
}
},
{
"var": {
"name": "SET_VARS_STRING",
"value": ""
}
},
{
"var": {
"name": "SET_VARS_TYPE",
"value": "dict"
}
}
]
}
}
]
}
},
{
"section": {
"name": "Load variables",
"sections": [
{
"section": {
"name": "Load rudder variables",
"vars": [
{
"var": {
"name": "SET_VARS_FILE_NAME",
"value": "/tmp/json4.json"
}
},
{
"var": {
"name": "SET_VARS_NAME",
"value": "prefix11.localfile"
}
},
{
"var": {
"name": "SET_VARS_SRC",
"value": "local_file"
}
},
{
"var": {
"name": "SET_VARS_STRING",
"value": "myValue"
}
},
{
"var": {
"name": "SET_VARS_TYPE",
"value": "dict"
}
}
]
}
}
]
}
}
]
}
},
"shortDescription": "",
"techniqueName": "setVars",
"techniqueVersion": "1.0"
}
techniques/systemSettings/misc/setVars/1.0/tests/test_setVars.metadata
[{
"inits": ["test_setVars.cf"],
"directives": ["test_setVars.json", "check_vars.json"],
"checks": ["test_setVars.rb"],
"sharedFiles": ["json1.json", "json2.json", "json3.json"],
"compliance": 76.47
}]
techniques/systemSettings/misc/setVars/1.0/tests/test_setVars.rb
require "spec_helper"
describe file("/var/rudder/resources") do
it {should be_directory}
it {should be_mode 755}
its(:content) { should match // }
end
describe file("/tmp/result") do
it {should be_file}
its(:content) { should match /^[\s\S]*?OK[\s\S]*?OK[\s\S]*?OK[\s\S]*?\${prefix4\.str2}[\s\S]*?test seems ok[\s\S]*?Sure OK[\s\S]*?test2 is ok[\s\S]*?test4 is ok[\s\S]*?\${prefix5.dict1\[key1\]} \${prefix5\.dict1\[key2\]}[\s\S]*?\${prefix6\.dict2\[key1\]} \${prefix6\.dict2\[key2\]}[\s\S]*?\${prefix7\.dict1\[key1\]} \${prefix7\.dict1\[key2\]}$/ }
end

Also available in: Unified diff