Конфигурационные файлы и переменные окружения¶
Файлы app.json¶
Различные настройки сервисов и приложений определяются в конфигурационных файлах app.json
.
Note
Файл app.json
объявлен в формате JSON. Он должен быть в кодировке UTF-8 (с опциональным BOM).
Файлы app.json
используются для компонентов:
- сервисов
chronos
,jinni
,monitor
,web
,webbi
; - консольной утилиты
tadmin
; - проектов с тестами NUnit (
Tessa.Test.*
); - desktop-приложений
TessaClient
,TessaAdmin
,TessaAppManager
,TessaHost
; - сервисов и приложений .NET, разработанных в проектных решениях с использованием API TESSA.
Комментарии¶
В конфигурационных файлах app.json
можно использовать комментарии - строки, начинающиеся с //
, которые игнорируются при чтении файла:
{
"Settings": {
// mail sending mode: SMTP, Exchange, Disabled
// ExchangeVersion specify as 'Exchange2013', if you have Exchange 2013 or newer
"NoticeMailer.Mode": "Disabled",
"NoticeMailer.ExchangeOAuthToken": ""
}
}
Ключи верхнего уровня¶
На верхнем уровне JSON-объекта в файле могут располагаться следующие ключи:
-
ключи-директивы, начинающиеся с символа точки
.
- определяют специальные действия, выполняемые при загрузке конфигурационного файла. В каждом файле может быть не более одной директивы каждого типа на верхнем уровне, но директивы, позволяющие несколько объявлений или действий, задаются как массивы[ ... ]
. После обработки действий, определённых в директиве, ключ с директивой удаляется из объекта конфигурации, т.е. он недоступен приложению; -
Settings
- ключи с произвольными настройками приложения;{ "Settings": { "ServerCode": "platform", "LicenseFile": "@*.?lic" } }
-
ConnectionStrings
- строки подключения. По дочернему ключу указывается имя строки подключения (default
для строки по умолчанию). Значение - либо строка подключения MS SQL Server, либо массив, в котором первый элемент - строка подключения, второй элемент - имя провайдера базы данных из блокаDataProviders
(для PostgreSQL укажите"Npgsql"
);{ "ConnectionStrings": { "default": [ "Host=localhost; Database=tessa; User ID=postgres; Password=Master1234; Pooling=true; MaxPoolSize=100; MaxAutoPrepare=50; AutoPrepareMinUsages=20", "Npgsql" ], "migration": "Server=.\\SQLEXPRESS; Database=tessa; Integrated Security=false; User ID=sa; Password=Master1234; Connect Timeout=200; Pooling=true; Max Pool Size=200; Trust Server Certificate=true" } }
-
DataProviders
- провайдеры данных для указания вConnectionStrings
. Дочерний ключ - алиас провайдера, который можно передать вConnectionStrings
вторым элементом массива; значение - имя класса .NET для провайдера данных с указанием сборки через запятую.{ "DataProviders": { "Npgsql": "Npgsql.NpgsqlFactory, Npgsql" } }
-
другие ключи верхнего уровня не учитываются системой конфигурации, но при разработке собственных приложений могут быть получены из структуры объекта с конфигурацией.
Tip
Используйте команду tadmin PrintJson app.json -i
, чтобы вывести на консоль содержимое конфигурации в JSON-форме после подстановки всех символов и обработки всех директив, в т.ч. после включения содержимого других файлов директивой .include
. Здесь app.json
- полный или относительный путь до выводимого файла.
Директива .include¶
Директива .include
(ключ .include
на верхнем уровне конфигурационного файла) позволяет к уже обработанному содержимому включить содержимое из другого файла, т.е. добавить или объединить значения ключей.
- Для указания единственного значения достаточно задать по ключу директивы строку, а для указания нескольких значений - использовать массив строк.
- Если передан относительный путь, то он рассчитывается от папки с текущим конфигурационным файлом.
- Можно указывать маскированный путь, где символ
*
определяет ноль или более любых символов, а символ?
- ровно один любой символ. При поиске файлов полные пути к ним (включая имена и названия папок) сортируются по алфавиту с учётом регистра. - Разделители путей
/
и\
приводятся в соответствии с текущей ОС. Так, на ОС Windows все символы/
в пути заменяются на\
, а на ОС Linux -\
заменяются на/
. Следует учитывать, что в соответствии с синтаксисом JSON для указания символа\
его необходимо дублировать\\
. - Если по указанному пути (маскированному или не маскированному) файл не найден, то он пропускается без генерации ошибок.
- Если пути к файлам повторяются, то повторное включение файла не выполняется.
- Файлы включаются в порядке указания.
Включить файлы, подходящие по маске app*.json
в текущей папке:
{
".include": "app*.json"
}
Note
Если текущий файл называется app.json
, или один из ранее обработанных файлов также подходил по маске app*.json
, то его повторное включение не выполняется.
Другой пример:
{
".include": [
"localization.json",
"patch*.json",
"app-*.json",
"config/app-*.json",
{
".loader.type": "FullNamespace.UriExampleLoaderClass, UriExampleLibrary",
"uri": "https://my.configuration.server/files/app1.json"
},
{
".loader.type": [ "FullNamespace.UriExampleLoaderClass2", "extensions/UriExampleLibrary2.dll" ],
"uri": "https://my.configuration.server/files/app2.json"
}
]
}
Здесь формируется следующий список файлов на включение:
- Файл с именем
localization.json
в папке с текущим файлом. - Файлы, подходящие по маске
patch*.json
в папке с текущим файлом. Например,patch.json
,patch-crm.json
и др. - Файлы, подходящие по маске
app-*.json
в папке с текущим файлом. Например,app-db.json
,app-ext.json
и др. - Файлы в подпапке
config
, подходящие по маскеapp-*.json
. Например,config/app-web.json
,config/app-dev.json
и др. - Загружается файл, используя кастомизированный загрузчик с указанным в свойстве
".loader.type"
полным квалифицированным именем типа: именем классаUriExampleLoaderClass
с пространством имёнFullNamespace
в сборке .NET по имениUriExampleLibrary
, которая может не иметь ссылок на библиотеки платформы.- Приложение должно ссылаться на сборку с именем
UriExampleLibrary
, чтобы она гарантированно могла быть загружена. - Поддерживается загрузка для сервисов .NET, не подключающих расширения (
jinni
,monitor
). Не поддерживается для сервисаwebbi
(который не является сервисом .NET). - Класс загрузчика получает любые параметры в json-объекте (в примере - свойство
"uri"
), чтобы по ним выполнить загрузку и включение файла конфигурации известным этому загрузчику способом (например, выполнив запрос к сервису хранения конфигурации по заданному адресу или расшифровав файл по указанному пути). - В платформе указанный в свойстве
".loader.type"
загрузчик отсутствует, здесь он приведён для примера расширяемости системы конфигурации.
- Приложение должно ссылаться на сборку с именем
- Загружается файл, используя кастомизированный загрузчик с указанным в свойстве
".loader.type"
, содержащий полное имя класса с пространством имёнFullNamespace.UriExampleLoaderClass2
в сборке .NET по путиextensions/UriExampleLibrary2.dll
, которая может не иметь ссылок на библиотеки платформы.- Сборка должна быть расположена в файле
UriExampleLibrary2.dll
в подпапкеextensions
относительно папки с конфигурационными файлами, которая определяется переменной окруженияTESSA_CONFIG_ROOT
(по умолчанию соответствует папке с приложением). При этом приложение может не ссылаться на сборку с этим именем. - В остальном обработка аналогична п.5.
- Сборка должна быть расположена в файле
Tip
За подробной информацией о программных расширениях алгоритма обработки конфигурационных файлов обратитесь в раздел руководства разработчика Расширения для обработки конфигурационных файлов.
Процесс обработки каждого конфигурационного файла выполняется следующим образом:
- Обрабатываются все директивы в файле - известные ключи верхнего уровня, начинающиеся с
.
.- В процессе обработки директивы
.include
формируется список файлов на включение, которые в этом порядке добавляются в конец очереди на включение.
- В процессе обработки директивы
- Ранее полученное (при обработке предыдущих файлов) JSON-содержимое объединяется с содержимым текущего файла.
- Для объектов
{ ... }
объединяются все значения по текущим и новым ключам. - При совпадении по ключу атомарного значения (строки, числа или логического значения) - значение из включаемого файла заменяет значение исходного файла.
- При совпадении по ключу значений-массивов
[ ... ]
, значения из включаемого файла добавляются в конец массива из ранее полученного объекта. - При совпадении по ключу значений-объектов они объединяются по таким же правилам.
- Для объектов
- Из очереди файлов на включение удаляется первый файл, и далее для него повторяется процесс с п.1, в т.ч. с добавлением новых включаемых файлов в конец очереди.
- Если же очередь пуста, то обработка завершается.
Для первого обрабатываемого файла (с именем app.json
в папке приложения, или по пути, указанному в переменной окружения TESSA_APP_JSON
) ранее полученное JSON-содержимое пустое { }
(т.к. предыдущего файла не было), и очередь на включение также пустая. После обработки этого файла наполняется JSON-содержимое, которое передаётся в следующий по очереди файл на включение (если он есть). И так для всех файлов.
Например, есть файл app.json
:
{
".include": "app2.json",
"Settings": {
"ServerCode": "platform",
"Numbers": [
1,
2
],
"WebServer": {
"HttpsRedirect": "Disabled",
"Http2Disabled": true
}
}
}
В той же папке расположен файл app2.json
:
{
"Settings": {
"ServerCode": "tessa",
"WinAuthIsEnabled": false,
"Numbers": [
3
],
"WebServer": {
"Http2Disabled": false
}
}
}
После обработки файла app.json
выполняется включение файла app2.json
. В результате объединённый объект выглядит так:
{
".include": "app*.json",
"Settings": {
"ServerCode": "tessa",
"WinAuthIsEnabled": false,
"Numbers": [
1,
2,
3
],
"WebServer": {
"HttpsRedirect": "Disabled",
"Http2Disabled": false
}
}
}
В процессе объединения были произведены действия:
- Значение по совпадающему ключу
Settings
является объектом{ ... }
, поэтому оно объединяется.- Значение по совпадающему ключу
ServerCode
изменено на"tessa"
. - Добавлено значение по ключу
WinAuthIsEnabled
, которое отсутствовало в ранее обработанном файле. - Значение по совпадающему ключу
Numbers
является массивом[ ... ]
, поэтому число3
добавлено в конец массива, т.е. после чисел1, 2
. - Значение по совпадающему ключу
WebServer
является объектом{ ... }
, поэтому оно объединяется.- Значение по совпадающему ключу
Http2Disabled
изменено наfalse
.
- Значение по совпадающему ключу
- Значение по совпадающему ключу
Файлы app.json
и app2.json
оба подходят под маскированный путь app*.json
, но они уже были обработаны, поэтому их включение повторно не выполняется (и не приводит к зацикливанию алгоритма).
Переопределение ключей при включении¶
Для того, чтобы по совпадающим ключам значения-массивы [ ... ]
и значения-объекты { ... }
переопределялись (полностью заменялись) из включаемого директивой .include
файла, допишите к имени ключа два восклицательных знака key!!
.
- В результирующем объекте будет ключ
key
без восклицательных знаков. - Наличие или отсутствие в ранее полученном объекте (перед объединением) ключа
key
не играет роли. В результате всё так же будет ключkey
с содержимым из включаемого файла. - Если значение по ключу
key!!
является атомарным (не массивом или объектом, а строкой, числом или логическим значением), то он также заменяет содержимое по ключуkey
, как если бы восклицательные знаки!!
отсутствовали в его имени.
Пусть в папке расположен файл app3.json
, который подходит под маскированный путь app*.json
, поэтому будет включён после обработки файла app2.json
.
{
"Settings": {
"Numbers!!": [
4,
5,
6
],
"WebServer!!": {
"HttpsRedirect": "Enabled"
}
}
}
В результате объединённый объект выглядит так:
{
"Settings": {
"ServerCode": "tessa",
"WinAuthIsEnabled": false,
"Numbers": [
4,
5,
6
],
"WebServer": {
"HttpsRedirect": "Enabled"
}
}
}
В процессе объединения были произведены действия:
- Значение по совпадающему ключу
Settings
является объектом{ ... }
, поэтому оно объединяется.- Значение по совпадающему ключу
Numbers!!
является массивом[ ... ]
, но ключ завершается на!!
, поэтому его содержимое4, 5, 6
заменяет полученный ранее массив1, 2, 3
. - Значение по совпадающему ключу
WebServer!!
является объектом{ ... }
, поэтому его значение заменяет все ранее указанные ключи.Http2Disabled
отсутствует в переопределяемом объекте, поэтому он удалён.
- Значение по совпадающему ключу
Tip
Для переопределения строк подключения к базе данных используйте переопределение имени строки, если она указана как массив (для PostgreSQL). Иначе, если ранее указанное значение также было массивом, то новое значение будет добавлено в конец массива, как третий и четвёртый элементы, которые игнорируются при фактическом подключении к базе данных.
{
"ConnectionStrings": {
"default!!": [ "Host=localhost; Database=tessa; User ID=postgres; Password=Master1234", "Npgsql" ]
}
}
Important
При использовании совместно с директивой .if необходимо учитывать особенности, описанные в подразделе ниже.
Директива .define¶
Директива .define
(ключ .define
на верхнем уровне конфигурационного файла) позволяет определить один или несколько символов, которые могут использоваться в проверках .if
или при вставке переменных %symbol%
для строковых значений.
Символ имеет имя и значение (строка), а также может быть не определён. При ссылках на символы для переопределения ранее определённого символа, а также удаления или использования в других директивах, проверка имени выполняется без учёта регистра.
Значение по ключу может быть строкой для определения одного символа, или массивом для определения нескольких:
Определить символ symbol1
со значением "true"
:
{
".define": "symbol1"
}
Определить символы symbol1
и symbol2
со значениями "true"
:
{
".define": [
"symbol1",
"symbol2"
]
}
Определить символы SERVER_CODE
и MAGIC_NUMBER
с указанными значениями через знак равенства =
:
{
".define": [
"SERVERCODE=tessa",
"MAGIC_NUMBER=42"
]
}
При указании значения символа как пустой строки ему назначается значение по умолчанию "true"
(что аналогично указанию имени без знака равенства =
):
{
".define": "symbol1="
}
Возможно удалить символ, предварив его имя восклицательным знаком. Что полезно, если символ был определён в другом файле, из которого текущий файл включается по директиве .include
:
{
".define": "!symbol1"
}
В системе определены стандартные символы, которые либо не определены, либо определены со значением "true"
, в зависимости от среды запуска. Ниже перечислены такие символы и условия, при которых они определены:
windows
- приложение запущено на ОС Windows;linux
- приложение запущено на ОС Linux;wine
- приложение запущено на ОС Linux в окружении Wine, которое имитирует Windows;x64
- приложение запущено в 64-битном процессе на 64-битной ОС;x86
- приложение запущено в 32-битном процессе на 32-битной или 64-битной ОС.
Также система определяет для каждой переменной окружения символ с соответствующим именем и значением. Так, если определена переменная окружения ASPNETCORE_ENVIRONMENT
со значением DevelopmentHot
, то это аналогично объявлению символа:
{
".define": "ASPNETCORE_ENVIRONMENT=DevelopmentHot"
}
Note
Если в конфигуционном файле объявлен символ с тем же именем, что и переменная окружения (без учёта регистра), то при его обработке используется значение объявленного символа, а не значение переменной.
Символы для переменных окружения можно удалить, как и любые другие символы:
{
".define": [
"!ASPNETCORE_ENVIRONMENT",
"!CID_FILE_PATH",
"SERVERCODE=tessa"
]
}
Директива .if¶
Директива .if
(ключ .if
на верхнем уровне конфигурационного файла) позволяет включить при выполнении или невыполнении условия объединить содержимое указанного объекта с содержимым файла верхнего уровня по тем же правилам, что и включение других файлов директивой .include
.
По ключу .if
всегда определяется массив, который содержит следующие элементы в указанной последовательности:
- Одна или несколько строк или массивов с условиями, объединяемыми по “ИЛИ” (т.н. блок “condition”).
- Объект
{ ... }
, который объединяется с содержимым конфигурационного файла при выполнении условия (т.н. блок “then”). - Опционально объект
{ ... }
, который объединяется с содержимым конфигурационного файла при невыполнении условия (т.н. блок “else”). - Далее алгоритм переходит к п.1, где перечисляются одна или несколько строк или массивов с условиями для следующей проверки, и т.д. до завершения массива
.if
.
Условие в массиве if
может быть строкой или массивом строк. Проверки внутри массива объединяются по “И”.
Строка условия (или одна из строк в массиве условия) может быть:
- именем символа
Symbol
- проверяет, что символ с указанным именемSymbol
объявлен (в соответствии с директивой.define
, переменными окружения и объявленными по умолчанию символами, такими какlinux
); - именем символа с предваряющим восклицательным знаком
!Symbol
- проверяет, что символ с указанным именемSymbol
не объявлен; - именем символа со значением через знак равенства
Symbol=value
- проверяет, что символ с указанным именемSymbol
объявлен, и его значение равно строкеvalue
без учёта регистра; - именем символа с предваряющим восклицательным знаком со значением через знак равенства
!Symbol=value
- проверяет, что или символ с указанным именемSymbol
не объявлен, или его значение не равно строкеvalue
без учёта регистра.
Таким образом, в блоке “condition” возможно определять логические выражение в форме СДНФ (совершенная дизъюнктивная нормальная форма).
Рассмотрим конфигурационный файл:
{
".if": [
[ "A", "B" ],
[ "C", "D" ],
{
"Settings": {
"Result": "then"
}
},
{
"Settings": {
"Result": "else"
}
}
]
}
При его обработки проверяется, что если либо объявлены символы A
и B
, либо объявлены символы C
и D
, то в блоке "Settings"
конфигурационного файла определяется ключ "Result"
со значением "then
. После обработки директивы .if
объект конфигурации выглядит так:
{
"Settings": {
"Result": "then"
}
}
Иначе, если условие не выполняется, то производится аналогичное определение ключа со значением "else"
. В результате объект конфигурации выглядит так:
{
"Settings": {
"Result": "else"
}
}
Если четвёртый элемент массива с блоком “else” отсутствовал бы, то при невыполнении условия содержимое конфигурационного файла бы не изменялось (в этом примере оно осталось бы пустым).
Также блок “then” можно указать как пустой объект { }
, тогда при выполнении условия не будет изменений, а при невыполнении - выполнится объединение с блоком “else”.
Рассмотрим пример файла app.json
, где если символ windows
определён, то значение по ключу "WinAuthIsEnabled"
меняется на true
.
{
".if": [
"windows",
{
"Settings": {
"WinAuthIsEnabled": true
}
}
],
"Settings": {
"ServerCode": "tessa",
"WinAuthIsEnabled": false
}
}
Если символ windows
объявлен (этот символ автоматически объявляется при выполнении на ОС Windows), то содержимое блока “then” объединяется с тем содержимым, что указано вне директивы .if
. Значение по совпадающему ключу "WinAuthIsEnabled"
заменяется, а другой ключ ServerCode
остаётся без изменений. В результате обработки будет следующий объект конфигурации:
{
"Settings": {
"ServerCode": "tessa",
"WinAuthIsEnabled": true
}
}
Если символ windows
не объявлен (например, при выполнении на ОС Linux), то объект конфигурации не изменяется:
{
"Settings": {
"ServerCode": "tessa",
"WinAuthIsEnabled": false
}
}
Рассмотрим пример с проверкой значения символа ASPNETCORE_ENVIRONMENT
(напоминаем, что символ может быть объявлен как переменная окружения):
{
".if": [
"ASPNETCORE_ENVIRONMENT=Development",
"ASPNETCORE_ENVIRONMENT=DevelopmentHot",
"ASPNETCORE_ENVIRONMENT=SdkHot",
{
"Settings": {
"GuyFawkesAuth": "",
"WinAuthIsEnabled": false,
"SessionsSyncIsLocal": true,
"Discovery.MonitorIntegrationEnabled": false,
"WebServer": {
"HttpsRedirect": "Disabled",
"Http2Disabled": true
}
}
}
]
}
Если символ с именем ASPNETCORE_ENVIRONMENT
(без учёта регистра) объявлен, и его значение равно (без учёта регистра) либо строке Development
, либо DevelopmentHot
, либо SdkHot
, то в результирующем объекте конфигурации будут настройки из блока “then”:
{
"Settings": {
"GuyFawkesAuth": "",
"WinAuthIsEnabled": false,
"SessionsSyncIsLocal": true,
"Discovery.MonitorIntegrationEnabled": false,
"WebServer": {
"HttpsRedirect": "Disabled",
"Http2Disabled": true
}
}
}
Если же символ не объявлен или его значение не совпадает ни с одним из перечисленных, то результирующий объект конфигурации пуст (т.к. блок “else” отсутствует, и снаружи директивы .if
нет других объявлений):
{
}
Рассмотрим пример с несколькими условиями в файле app.json
:
{
"ConnectionStrings": {
"default": [ "Host=prod.server.com; Database=tessa_prod; User ID=tessa_user; Password=jfdH321", "Npgsql" ],
"migration": "Server=.\\SQLEXPRESS; Database=tessa; Integrated Security=true"
},
".if": [
"LOCAL_DATABASE",
{
"ConnectionStrings": {
"default!!": [ "Host=localhost; Database=tessa; User ID=postgres; Password=Master1234", "Npgsql" ]
}
},
"linux",
{
"Settings": {
"Webbi": {
"MaintenanceConfig": {
"Mode": "nginx"
}
}
}
},
{
"Settings": {
"Webbi": {
"MaintenanceConfig": {
"Mode": "iis"
}
}
}
}
],
"Settings": {
"Webbi": {
"Port": 19857
}
}
}
Note
Как и для любых файлов формата JSON, порядок объявления ключей внутри объектов не играет роли. Поэтому директива .if
может быть расположена между других ключей верхнего уровня. Местоположение директивы не влияет на её выполнение, пока она объявлена на верхнем уровне.
В файле определено следующее:
- Ключ верхнего уровня
ConnectionStrings
объявляет строку подключения к базе данных. - Ключ верхнего уровня
Settings
включает группу настроекWebbi
, в которой указана настройкаPort
. - Директива
.if
содержит два условия:- Если объявлен символ
LOCAL_DATABASE
(например, как переменная окруженияLOCAL_DATABASE
со значением1
или любым непустым значением), то строка подключения"default"
будет изменена. Поскольку для подключения к PostgreSQL строка указывается как массив, то чтобы происходило не объединение с массивом из ключа верхнего уровняConnectionStrings
, а его замена, то ключ для массива указан с двумя восклицательными знаками"default!!"
. Строка"migration"
отсутствует в блоке “then”, и весь блок со строками не переопределяется двумя восклицательными знакамиConnectionStrings!!
, поэтому она остаётся без изменений. - Если объявлен символ
linux
(обычно, если выполнение производится на ОС Linux), то в блокWebbi
(внутри блока верхнего уровняSettings
) добавляется вложенный объектMaintenanceConfig
с ключомMode
, равнымnginx
. Если символlinux
не объявлен (например, выполнение производится на ОС Windows), то добавляется аналогичный объект с ключомMode
, равнымiis
.
- Если объявлен символ
Рассмотрим содержимое объекта конфигурации в ситуации, когда объявлен символ LOCAL_DATABASE
и объявлен символ linux
.
{
"ConnectionStrings": {
"default": [ "Host=localhost; Database=tessa; User ID=postgres; Password=Master1234", "Npgsql" ],
"migration": "Server=.\\SQLEXPRESS; Database=tessa; Integrated Security=true"
},
"Settings": {
"Webbi": {
"MaintenanceConfig": {
"Mode": "nginx"
},
"Port": 19857
}
}
}
Покажем содержимое объекта конфигурации, когда не объявлен символ LOCAL_DATABASE
и не объявлен символ linux
:
{
"ConnectionStrings": {
"default": [ "Host=prod.server.com; Database=tessa_prod; User ID=tessa_user; Password=jfdH321", "Npgsql" ],
"migration": "Server=.\\SQLEXPRESS; Database=tessa; Integrated Security=true"
},
"Settings": {
"Webbi": {
"MaintenanceConfig": {
"Mode": "iis"
},
"Port": 19857
}
}
}
Сочетание директив в файле¶
Для директивы .if
в блоках “then” и “else” возможно указание любых вложенных директив .if
, .define
, .include
(без ограничения вложенности .if
друг в друга).
Алгоритм обработки единственного конфигурационного файла с учётом всех директив следующий:
- Обрабатывается директива
.include
, при этом добавляются указанные в ней конфигурационные файлы в очередь на включение. Фактическое их включение в результирующий объект конфигурации производится только после полной обработки текущего файла. После обработки ключ с именем директивы удаляется. - Обрабатывается директива
.define
, при этом добавляются, изменяются или удаляются значения для указанных символов. После обработки ключ с именем директивы удаляется. - Обрабатывается директива
.if
, причём перед обработкой ключ с именем директивы удаляется. Выполнение производится по описанному выше алгоритму, где сначала для первого условия в массиве блок “then” или “else” объединяется с текущий объектом конфигурации (начиная ключей верхнего уровня), далее для второго условия и т.д., пока все условия не обработаны. - Если в результате обработки директивы
.if
на верхнем уровне объекта конфигурации содержится хотя бы одна директива, то алгоритм переходит к п.1, и заново обрабатывает оставшиеся директивы (пополняя очередь файлов на включение, изменяя символы и/или обрабатывая условия).
В результате обработки файла определяется объект конфигурации, с которым объединяются файлы из очереди на включение, сформированной директивами .include
, где каждый из файлов перед включением обрабатывается по алгоритму выше (т.е. он может содержать свои директивы).
Рассмотрим пример файла app.json
для демонстрации работы алгоритма:
{
".if": [
"EMULATE_WINE",
{
".include": "app-wine.json",
".define": [ "!linux", "windows", "wine" ],
"Settings": {
"PlatformDependencies": "Tessa.Platform.WineTessaPlatformDependencies, Tessa",
"DefaultUILanguage": "ru"
},
".if": [
"RENDERING_PROBLEMS",
{
"Settings": {
"FadeAllowed": false,
"MaxPreviewInstances": 1,
"SoftwareRendering": true
}
},
"TESSA_LANGUAGE=English",
{
"Settings": {
"DefaultUILanguage": "en"
}
}
]
}
]
}
Содержимое объекта конфигурации после обработки файла определяется следующим образом:
- Если объявлен символ
EMULATE_WINE
, то:- Содержимое
Settings
из блока.if
переносится на верхний уровень объекта конфигурации. - Добавляется файл с именем
app-wine.json
в очередь на включение; - Удаляется определение символа с именем
linux
, и добавляется объявление символовwindows
иwine
со значениемtrue
. - Если объявлен символ
RENDERING_PROBLEMS
, то включаются указанные в блоке “then” настройкиFadeAllowed
и др. - Если символ
TESSA_LANGUAGE
объявлен со значениемEnglish
(без учёта регистра), то добавляется настройкаDefaultUILanguage
со значениемen
. - Иначе, если символ
TESSA_LANGUAGE
не объявлен или объявлен с отличающимся отEnglish
значением, то настройкаDefaultUILanguage
не изменяется, а значит имеет значениеru
.
- Содержимое
- Иначе, если символ
EMULATE_WINE
не объявлен, то никаких действий не производится, объект конфигурации будет пустым, очередь файлов на включение также не дополняется.
Пусть объявлен символ linux
(т.к. выполнение производится на ОС Linux), а также символы EMULATE_WINE
, RENDERING_PROBLEMS
, и символ TESSA_LANGUAGE
равен строке "english"
. Тогда объект конфигурации после обработки выглядит так:
{
"Settings": {
"PlatformDependencies": "Tessa.Platform.WineTessaPlatformDependencies, Tessa",
"DefaultUILanguage": "en",
"FadeAllowed": false,
"MaxPreviewInstances": 1,
"SoftwareRendering": true
}
}
Также после обработки объявленными считаются символы windows
, wine
, EMULATE_WINE
, RENDERING_PROBLEMS
, TESSA_LANGUAGE
.
Далее будет включено содержимое файла app-wine.json
по аналогичному алгоритму (если файл существует). В этом файле могут быть проверки, связанные с символами, объявление которых изменено в текущем файле.
Переопределение ключей при включении в директиву .if¶
Как описано выше, добавление к имени ключа двух восклицательных знаков "key!!"
позволяет переопределить значение этого ключа без объединения с предыдущим содержимым, когда значение является объектом { ... }
или массивом [ ... ]
. При этом из имени ключа удаляются эти восклицательные знаки, т.е. ключ теперь называется "key"
.
Особенность использования синтаксиса "key!!"
внутри директивы .if
заключается в том, что объединение (при успешном выполнении условия) выполняется с текущим содержимым конфигурационного файла. Если файл не является основным конфигурационным файлом app.json
, а подключается через директиву .include из другого файла, то в процессе объединения с этим файлом оно выполняется, когда двух восклицательных знаков в имени ключа "key"
уже нет.
Продемонстрируем это поведение на следующем примере. Пусть есть основной файл app.json
со следующим содержимым:
{
".include": "app-*.json",
"ConnectionStrings": {
"default": [ "Host=localhost; Database=tessa; User ID=postgres; Password=Master1234", "Npgsql" ]
}
}
Рядом с ним располагается конфигурационный файл app-other.json
:
{
".if": [
"USE_OTHER_DB",
{
"ConnectionStrings!!": {
"default": [ "Host=localhost; Database=tessa_other; User ID=postgres; Password=Master1234", "Npgsql" ]
}
}
]
}
Ожидаемое поведение следующее: если объявлен символ (переменная окружения) с именем USE_OTHER_DB
, то вместо базы данных tessa
используется база данных tessa_other
.
Однако, результирующий объект конфигурации будет содержать обе строки подключения по ключу "default"
, что не будет распознано как корректная строка подключения:
{
"ConnectionStrings": {
"default": [
"Host=localhost; Database=tessa; User ID=postgres; Password=Master1234", "Npgsql",
"Host=localhost; Database=tessa_other; User ID=postgres; Password=Master1234", "Npgsql"
]
}
}
Это связано с тем, что при обработке файла app-other.json
выполняется директива .if
, которая, при наличии символа (переменной окружения) USE_OTHER_DB
преобразует файл таким образом:
{
"ConnectionStrings": {
"default": [ "Host=localhost; Database=tessa_other; User ID=postgres; Password=Master1234", "Npgsql" ]
}
}
После этого содержимое app-other.json
объединяется с содержимым app.json
(при выполнении директивы .include
), и, поскольку ключ "ConnectionStrings"
уже не содержит двух восклицательных знаков на конце, то это приводит к объединению его значения-объекта { ... }
, где массив по ключу "default"
будет содержать значения из обоих конфигурационных файлов.
Чтобы решить эту проблему, укажите в файла app-other.json
четыре оконечных восклицательных знака для ключа "ConnectionStrings"
:
{
".if": [
"USE_OTHER_DB",
{
"ConnectionStrings!!!!": {
"default": [ "Host=localhost; Database=tessa_other; User ID=postgres; Password=Master1234", "Npgsql" ]
}
}
]
}
После обработки директивы .if
файл app-other.json
будет представлен так:
{
"ConnectionStrings!!": {
"default": [ "Host=localhost; Database=tessa_other; User ID=postgres; Password=Master1234", "Npgsql" ]
}
}
И после его объединения с файлом app.json
по директиве .include
объект конфигурации будет такой, какой и требовался:
{
"ConnectionStrings": {
"default": [ "Host=localhost; Database=tessa_other; User ID=postgres; Password=Master1234", "Npgsql" ]
}
}
Note
Даже если файл, в котором расположена директива .if
и ключ с четырьмя восклицательными знаками, подключается по цепочке директив .include
из разных файлов (например, app.json
включает файл app2.json
, который включает файл app-other.json
), то это не меняет количество объединений объектов конфигурации. Потому что директива .include
формирует очередь файлов на объединение, но каждый из таких файлов всегда объединяется с конечным объектом конфигурации один раз.
Таким образом, четырёх восклицательных знаков в имени ключа внутри директивы .if
всегда достаточно, если это не основной файл app.json
(тогда восклицательных знаков должно быть два).
Подстановка символов в строку¶
Символы, как объявленные в конфигурационном файле, так и полученные из переменных окружения, могут подставляться в строковые значения JSON на любых уровнях вложенности (для значений-объектов { ... }
и массивов [ ... ]
), используя синтаксис %ИмяСимвола%
.
{
"ConnectionStrings": {
"default": [ "Host=localhost; Database=%DATABASE_NAME%; User ID=postgres; Password=%DATABASE_PASSWORD%", "Npgsql" ]
},
"Settings": {
"ServerCode": "%TESSA_SERVER_CODE%"
}
}
Здесь в строку подключения default
подставляется символ DATABASE_NAME
как имя базы данных, символ DATABASE_PASSWORD
как пароль, и TESSA_SERVER_CODE
как код сервера.
Если подставляемый символ не объявлен (или он был удалён объявлением "!ИмяСимвола"
), то на эту позицию подставляется пустая строка.
Для указания символа процента %
(т.е. для эскейпинга) его необходимо задвоить:
{
"Settings": {
"RecognitionAccuracy": "50%%"
}
}
Здесь значением настройки RecognitionAccuracy
будет строка "50%"
.
Подстановка текущей папки в строку¶
Для того, чтобы подставить полный путь до папки с текущим обрабатываемым конфигурационным файлом в начало любого строкового ключа, начните эту строку с символа @
:
{
"Settings": {
"LicenseFile": "@*.?lic",
"WebServer": {
"CertificateFile": "@server_name.cer",
}
}
}
Если обработка выполняется для ОС Linux и текущий конфигурационный файл расположен по пути /home/tessa/configs
, то в результате его обработки приложение получит следующие значения ключей:
{
"Settings": {
"LicenseFile": "/home/tessa/configs/*.?lic",
"WebServer": {
"CertificateFile": "/home/tessa/configs/server_name.cer",
}
}
}
Если обработка выполняется для ОС Windows и текущий конфигурационный файл расположен по пути C:\Tessa\Configs
, то в результате будет:
{
"Settings": {
"LicenseFile": "C:\\Tessa\\Configs\\*.?lic",
"WebServer": {
"CertificateFile": "C:\\Tessa\\Configs\\server_name.cer",
}
}
}
В соответствии с логикой приложения, далее оно будет выполнять поиск файла или файлов по указанным абсолютным путям.
Note
Символ \
дублирован в соответствии с синтаксисом JSON-файлов. Действительное значение, которое используется приложение, будет содержать символы \
без дублирования.
При необходимости указать строку, действительно начинающуюся с символа @
, его необходимо дублировать. Например:
{
"Settings": {
"SecretPassword": "@@K*&123"
}
}
Значение по ключу SecretPassword
будет прочитано как @K*&123
.
Если строка равна одиночному символу "@"
, то его можно как дублировать, так и оставить без изменений - он не будет заменён.
При подстановке символа (в т.ч. из переменной окружения) через синтаксис %symbol%
сначала выполняется его подстановка, а потом применение синтаксиса @
.
{
"Settings": {
"LicenseFile": "@%LICENSE_FILE%"
}
}
Если символ LICENSE_FILE
равен ../Partner.jlic
, то сначала выполняется замена @../Partner.jlic
, а потом, поскольку значение начинается с @
, то вставляется путь до папки с текущим конфигурационным файлом вида /home/tessa/configs/../Partner.jlic
, что в свою очередь соответствует пути /home/tessa/Partner.jlic
.
Important
Если строка начинается с @
и далее уже содержит полный путь к файлу или папке (в соответствии с синтаксисом на ОС Linux или ОС Windows), то начальный символ удаляется без замены на путь к папке с конфигурационным файлом.
Например, символ LICENSE_FILE
равен /var/license/*.?lic
, и обработка выполняется на ОС Linux, где это - полный путь (который может быть или не быть маскированным путём). Тогда значение "@%LICENSE_FILE%"
будет заменено на /var/license/*.?lic
.
Примеры¶
Пусть в app.json
указан ключ ServerCode
со включением символа:
{
".include": "app-*.json",
"Settings": {
"ServerCode": "%TESSA_SERVER_CODE%"
}
}
Вместо указания этого значения через переменную среды, рядом возможно создать такой файл app-vars.json
(имя файла любое, которое будет включаться директивами .include
):
{
".define": [
"TESSA_SERVER_CODE=platform123"
]
}
Значения по умолчанию можно добавить через директиву .if
в файле app.json
:
{
".if": [
"!TESSA_SERVER_CODE",
{
"Settings": {
"ServerCode": "platform"
}
}
],
"Settings": {
"ServerCode": "%TESSA_SERVER_CODE%"
}
}
Или пойти от обратного: только когда объявлена переменная - подставляем её, заменяя значение по умолчанию.
{
".if": [
"TESSA_SERVER_CODE",
{
"Settings": {
"ServerCode": "%TESSA_SERVER_CODE%"
}
}
],
"Settings": {
"ServerCode": "platform"
}
}
Другой пример: если объявлены обе переменные (или оба символа), то изменяем строку подключения с их учётом:
{
".if": [
[ "DATABASE_NAME", "DATABASE_PASSWORD" ],
{
"ConnectionStrings": {
"default!!": [ "Host=localhost; Database=%DATABASE_NAME%; User ID=postgres; Password=%DATABASE_PASSWORD%", "Npgsql" ]
}
}
],
".include": "app-*.json"
}
Аналогично вместо переменных возможно использовать символы из отдельного файла app-vars.json
:
{
".define": [
"DATABASE_NAME=tessa",
"DATABASE_PASSWORD=Master1234"
]
}
Файлы extensions.xml¶
Поиск и загрузка файлов сборок .dll
с регистраторами расширений .NET конфигурируется в файлах extensions.xml
.
Note
Файл extensions.xml
объявлен в формате XML. Он должен быть в кодировке UTF-8 (с опциональным BOM).
<?xml version="1.0" encoding="utf-8" ?>
<extensions>
...
</extensions>
Типы регистраторов расширений, загрузка которых определяется этим конфигурационным файлом:
- Стандартные расширения: регистраторы с атрибутом
[Registrator]
. - Консольные команды: регистраторы с атрибутом
[ConsoleRegistrator]
. - Консольные скрипты, вызываемые командой
tadmin Script
: регистраторы с атрибутом[ConsoleScript]
. - Регистраторы ASP.NET Core для веб-сервисов: регистраторы с атрибутом
[WebRegistrator]
.
Тег reference¶
Загружает сборку .NET по указанному пути. Других действий со сборкой не выполняется.
<reference file="LibraryName.dll" />
file
- путь до файла сборки.dll
, который загружается. Относительные пути рассчитываются от расположения текущего файлаextensions.xml
.clientOnly="true"
- сборка загружается только для клиентских приложений (в т.ч. клиентские команды tadmin и клиентские тесты).serverOnly="true"
- сборка загружается только для серверных приложений (в т.ч. серверные команды tadmin, тесты, сервисыchronos
,web
).
Tip
Используйте, чтобы загрузить зависимость (стороннюю библиотеку), используемую в регистраторах расширений (в т.ч. опосредованно через статические конструкторы), перед тем, как будет загружена сборка с расширениями, но только если она расположена в папке, не добавленной в настройке ProbingPath
.
Тег include¶
Загружает сборку .NET по указанному пути и выполняет сканирование в ней классов регистраторов для поиска расширений.
<include file="ExtensionsLibrary.dll" />
file
- путь до файла сборки.dll
, в которой выполняется сканирование классов регистраторов для поиска расширений. Относительные пути рассчитываются от расположения текущего файлаextensions.xml
.clientOnly="true"
- сборка загружается и сканируется только для клиентских приложений (в т.ч. клиентские команды tadmin и клиентские тесты).serverOnly="true"
- сборка загружается и сканируется только для серверных приложений (в т.ч. серверные команды tadmin, тесты, сервисыchronos
,web
).
Тег scan¶
Сканирует указанную папку (и опционально её подпапки) на наличие файлов extensions.xml
, которые, аналогично текущему конфигурационному файлу, обрабатываются для загрузки сборок.
<scan path="folder/path" />
path
- путь до папки, в которой выполняется поиск файловextensions.xml
для загрузки сборок. Если папка не существует или в ней отсутствует файлextensions.xml
, то игнорируется. Относительные пути рассчитываются от расположения текущего файлаextensions.xml
.subfolders="false"
- сканировать только указанную вpath
папку. Если не указано (по умолчанию), то в дополнение к ней сканируются вложенные папки верхнего уровня в папку изpath
, и далее в каждой из таких подпапок, в которой есть файлextensions.xml
, продолжается загрузка.
Important
Избегайте циклических зависимостей между папками, которые могут привести к зависанию приложения. Например, укажите <scan path=".." subfolders="false" />
, чтобы сканировать папку уровнем выше, но не выполнять поиск extensions.xml
в подпапках, где уже лежит текущий файл.
Примеры¶
<?xml version="1.0" encoding="utf-8" ?>
<extensions>
<include file="Tessa.Extensions.PostgreSql.Server.dll" />
<scan path="platform" />
<scan path="extensions" />
</extensions>
- Загружаются расширения из файла
Tessa.Extensions.PostgreSql.Server.dll
в текущей папке. - В подпапке
platform
, вложенной в папку с текущим файломextensions.xml
, выполняется поиск файлаextensions.xml
для загрузки расширений. Далее файл ищется в каждой из подпапок верхнего уровня, вложенных вplatform
. - Аналогично сканируется подпапка
extensions
.
Пример для сборки с тестами, некоторые из которых могут быть серверными, а некоторые клиентскими (для клиентских тестов поднимается и клиентская, и серверная часть, поэтому выполняются все расширения, но для разных DI-контейнеров).
<?xml version="1.0" encoding="utf-8" ?>
<extensions>
<include file="Tessa.Extensions.Default.Shared.dll" />
<include file="Tessa.Extensions.Shared.dll" />
<include file="Tessa.Extensions.Default.Client.dll" clientOnly="true" />
<include file="Tessa.Extensions.Client.dll" clientOnly="true" />
<include file="Tessa.Extensions.Default.Server.dll" serverOnly="true" />
<include file="Tessa.Extensions.Server.dll" serverOnly="true" />
<include file="Tessa.Extensions.Default.Server.Web.dll" serverOnly="true" />
<include file="Tessa.Extensions.Server.Web.dll" serverOnly="true" />
<include file="Tessa.Extensions.PostgreSql.Server.dll" serverOnly="true" />
</extensions>
Файлы plugins.xml¶
Файлы сборок .dll
для загрузки плагинов Chronos определяются в конфигурационных файлах plugins.xml
.
Note
Файл plugins.xml
объявлен в формате XML. Он должен быть в кодировке UTF-8 (с опциональным BOM).
<?xml version="1.0" encoding="utf-8" ?>
<plugins>
...
</plugins>
Тег include¶
<include file="PluginsLibrary.dll" />
file
- путь до файла сборки.dll
, в которой выполняется сканирование классов плагинов[Plugin]
. Относительные пути рассчитываются от расположения текущего файлаplugins.xml
.
Примеры¶
<?xml version="1.0" encoding="utf-8" ?>
<plugins>
<include file="Tessa.Chronos.dll" />
<include file="Tessa.Chronos.DocumentsLoad.dll" />
<include file="Tessa.Extensions.Default.Chronos.dll" />
</plugins>
Последовательно загружает плагины из указанных файлов, расположенных в папке с файлом plugins.xml
.
Файлы NLog.config¶
Настройки логирования определяются в конфигурационных файлах NLog.config
.
Note
Файл NLog.config
объявлен в формате XML. Он должен быть в кодировке UTF-8 (с опциональным BOM).
Файлы NLog.config
используются для компонентов:
- сервисов
chronos
,jinni
,monitor
,web
; - консольной утилиты
tadmin
; - проектов с тестами NUnit (
Tessa.Test.*
); - desktop-приложений
TessaAppLauncher
,TessaClient
,TessaAdmin
,TessaAppManager
,TessaHost
; - сервисов и приложений .NET, разработанных в проектных решениях с использованием API TESSA.
Note
Логирование для сервиса webbi
и приложения-ассистента web-клиента Deski
настраивается отдельно. Обратитесь к соответствующим разделам по описанию этих компонентов.
Поиск файла NLog.config
выполняется в папке приложения, или в папке с плагинами для плагинов Chronos (если в папке с плагинами нет файла, то логирование для плагина выполняется по правилам в папке сервиса chronos
).
Также путь к файлу можно переопределить, используя переменную окружения TESSA_NLOG_CONFIG.
Подробная информация по формату файла NLog.config
и доступным настройкам приведена в документации по библиотеке NLog.
Tip
Можно настроить по своим правилам, например, чтобы логируемые сообщения записывались в отдельные файлы каждый день с созданием подпапок с номером месяца и года.
Файлы тем интерфейса¶
Темы интерфейса для web-клиента и для desktop-клиента определяются файлами тем themeName.json
(где themeName
- любое имя, обычно соответствует имени определяемой темы).
Их подробное описание приведено в разделе Создание/Редактирование тем оформления и настройка фоновых изображений.
Note
Файл темы themeName.json
объявлен в формате JSON. Он должен быть в кодировке UTF-8 (с опциональным BOM).
Tip
Используйте команду tadmin PrintJson Theme themeFile.json -i
, чтобы вывести на консоль содержимое темы в JSON-форме после обработки всех директив, в т.ч. после включения содержимого других файлов директивами .include
и .override
. Здесь themeFile.json
- полный или относительный путь до выводимого файла.
Переменные окружения¶
Через переменные окружения возможно переопределить пути к конфигурационным файлам и папкам для запущенного процесса приложения, а также указать иные настройки.
-
TESSA_CONFIG_ROOT
- базовый путь к папке для всех прочих настроек, если для них не заданы абсолютные пути. Переменная не задана - используется текущая папка приложения. -
TESSA_EXTENSIONS_ROOT
- папка, начиная с которой выполняется сканирование файлов расширенийextensions.xml
(всех видов, включая консольные команды, скрипты, регистраторы ASP.NET Core и т.п). Переменная не задана - используется значение изTESSA_CONFIG_ROOT
. -
TESSA_APP_JSON
- путь к файлуapp.json
, из которого загружается конфигурация. Если этот файл содержит включение других конфигурационных файлов по директивам.include
, то они загружаются относительно его расположения. Действуют прежние правила:- если в конфигурационном файле значение строки начинается с
@
(например,@localhost.cer
), то путь считается от папки, в который лежит этот конфигурационный файл; - если путь относительный, но без символа
@
(например,./wwwroot/themes
), то расчёт идёт от папки в переменнойTESSA_CONFIG_ROOT
(если не указана - от папки приложения).
- если в конфигурационном файле значение строки начинается с
-
TESSA_NLOG_CONFIG
- путь к файлуNLog.config
(по умолчанию - одноимённый файл из папки приложения, или из переменнойTESSA_CONFIG_ROOT
соответственно). -
TESSA_CID
- путь к файлу.cid
, который будет создан приложением для записи туда имени компонента для подсистемы мониторинга и обнаружения компонентов, а также для его чтения при повторных запусках приложения. -
TESSA_RANDOMIZE_CID
- непустая строка, если требуется дополнительно рандомизировать имя компонента, хранимое в файле.cid
, чтобы исключить возможные коллизии имён. Этот файл будет создан приложением для записи туда имени компонента для подсистемы мониторинга и обнаружения компонентов, а также для его чтения при повторных запусках приложения.Если файл отсутствует, имя файла не указано в переменной
TESSA_CID
и в параметре командной строки-cid
, а также компонент не создаёт его всегда (как это делаютchronos
,jinni
иwebbi
), то имя компонента уже рандомизировано и дополнительная рандомизация не выполняется. -
TESSA_AUTHORIZED_KEYS
- путь к папкеauthorized_keys
, в которой лежат приватные ключи для подсистемы мониторинга и обнаружения компонентов.
Note
Всё это позволяет иметь, например, одну папку с бинарными файлами сервиса, и в юните-файле System D через переменные запустить несколько процессов с различными конфигурационными файлами. Причём в файлах app.json
можно вставлять через директиву .include
общие части из других конфигурационных файлов, и доопределять какие-то настройки в конфиге под экземпляр сервиса. В т.ч. настраивать отдельно логи, компоненты мониторинга и др.
Tip
При разворачивании в Docker возможно выполнять маппинг конфигурационных файлов на общие с другими контейнерами томы volume
, или хранить конфигурационные файлы в host-машине.