Введение.
Эта статья подойдет для тех, кто имеет по крайней мере базовые знания и навык работы с git. Она разработана для модели ветвления Git Flow.
Формат версии конфигурации 1С это A.B.C.[h], где:
- A – главный номер версии (major version number).
- B – вспомогательный номер версии (minor version number).
- C – номер сборки, номер логической итерации по работе над функционалом версии A.B (build number).
- h – Немедленные изменения/исправления конфигурации (hotfix branches), при которых конфигурация обновляется вручную (незапланированно), и не приводят к автоматическому обновлению конфигурации.
Представленный механизм так же подойдет для интеграции в среду Jenkins, но выходит за пределы темы статьи и рассматриваться не будет.
Итак, начнем!
В конфигурацию нужно добавить http-сервис, с корневым url - api. Добавим ему шаблон "/v1/{action}/" с методом GET. Обработчик у метода будет следующий:
Action = НРег(Запрос.ПараметрыURL["action"]);
Если Action = "version" Тогда
HTTPОтвет = Новый HTTPСервисОтвет(200);
HTTPОтвет.УстановитьТелоИзСтроки(Метаданные.Версия);
Возврат HTTPОтвет;
ИначеЕсли Action = "telegram" И Не Запрос.Заголовки.Получить("Text") = Неопределено Тогда
HTTPСоединение = Новый HTTPСоединение("api.telegram.org", 443, , , , , Новый ЗащищенноеСоединениеOpenSSL);
ПараметрыЗапроса = Новый Соответствие;
Текст = Запрос.Заголовки.Получить("Text");
ПараметрыЗапроса.Вставить("Content-type", "application/json");
TelegramBotID = Константы.Update_TelegramBotID.Получить();
TelegramChatID = Константы.Update_TelegramChatID.Получить();
Если ЗначениеЗаполнено(TelegramBotID) И ЗначениеЗаполнено(TelegramChatID) Тогда
МассивЧатов = СтрРазделить(TelegramChatID, ";");
Для Каждого ЧатID Из МассивЧатов Цикл
HTTPЗапрос = Новый HTTPЗапрос(TelegramBotID + "/sendMessage?chat_id=" + ЧатID
+ "&parse_mode=html&text=" + Текст, ПараметрыЗапроса);
HTTPСоединение.Получить(HTTPЗапрос);
КонецЦикла;
КонецЕсли;
HTTPОтвет = Новый HTTPСервисОтвет(200);
Возврат HTTPОтвет;
ИначеЕсли Action = "disconnect" Тогда
Port = Запрос.Заголовки.Получить("Port");
MainPort = Запрос.Заголовки.Получить("MainPort");
AgentSrv = Запрос.Заголовки.Получить("AgentSrv");
BaseName = Запрос.Заголовки.Получить("BaseName");
login = Запрос.Заголовки.Получить("login");
password = Запрос.Заголовки.Получить("password");
СистемнаяИнфо = Новый СистемнаяИнформация;
ПодстрокиВерсии = СтрРазделить(СистемнаяИнфо.ВерсияПриложения, ".");
COMСоединитель = Новый COMОбъект("v" + ПодстрокиВерсии[0] + ПодстрокиВерсии[1] + ".COMConnector");
СтрокаСоединенияСАгентомСервера = "tcp://" + AgentSrv + ":" + Port;
СоединениеСАгентомСервера = COMСоединитель.ConnectAgent(СтрокаСоединенияСАгентомСервера);
ПеремКластер = Неопределено;
Для Каждого Кластер Из СоединениеСАгентомСервера.GetClusters() Цикл
Если Формат(Кластер.MainPort, "ЧГ=0;") = MainPort Тогда
СоединениеСАгентомСервера.Authenticate(Кластер, login, password);
ПеремКластер = Кластер;
Прервать;
КонецЕсли;
КонецЦикла;
Если ПеремКластер = Неопределено Тогда
HTTPОтвет = Новый HTTPСервисОтвет(400);
HTTPОтвет.УстановитьТелоИзСтроки("ERROR: Не удалось найти кластер");
Возврат HTTPОтвет;
КонецЕсли;
ПеремОписаниеИнформационнойБазы = Неопределено;
Для Каждого ОписаниеИнформационнойБазы Из СоединениеСАгентомСервера.GetInfoBases(ПеремКластер) Цикл
Если НРег(ОписаниеИнформационнойБазы.Name) = НРег(BaseName) Тогда
ПеремОписаниеИнформационнойБазы = ОписаниеИнформационнойБазы;
Прервать;
КонецЕсли;
КонецЦикла;
Если ПеремОписаниеИнформационнойБазы = Неопределено Тогда
HTTPОтвет = Новый HTTPСервисОтвет(400);
HTTPОтвет.УстановитьТелоИзСтроки("ERROR: Не удалось найти базу");
Возврат HTTPОтвет;
КонецЕсли;
ВсеПользователи = Истина;
Сеансы = СоединениеСАгентомСервера.GetInfoBaseSessions(ПеремКластер, ПеремОписаниеИнформационнойБазы);
Для Каждого Сеанс Из Сеансы Цикл
Попытка
СоединениеСАгентомСервера.TerminateSession(ПеремКластер, Сеанс);
Исключение
ВсеПользователи = Ложь;
КонецПопытки;
КонецЦикла;
Если Не ВсеПользователи Тогда
HTTPОтвет = Новый HTTPСервисОтвет(400);
HTTPОтвет.УстановитьТелоИзСтроки("ERROR: Не удалось отключить всех пользователей");
Возврат HTTPОтвет;
КонецЕсли;
HTTPОтвет = Новый HTTPСервисОтвет(200);
Возврат HTTPОтвет;
ИначеЕсли Action = "check" Тогда
HTTPОтвет = Новый HTTPСервисОтвет(?(Константы.Update_ЗапретАвтообновления1С.Получить(), 400, 200));
Возврат HTTPОтвет;
КонецЕсли;
HTTPОтвет = Новый HTTPСервисОтвет(200);
Возврат HTTPОтвет;
далее короткое описание для каждого Action в методе:
- version - Основной метод, возвращает текущую версию конфигурации
- telegram - метод, который оповещает о процессе обновления, с помощью bota. Можно заменить на оповещения по электронной почте, смс и тд.
- disconnect - метод, который завершает работу пользователей перед обновлением (позволяет отключать пользователей в конфигурациях без БСП)
- check - метод, в который можно добавить нужные проверки перед обновлением. Например, проверку создания backup обновляемой базы, дня недели, выполнение важных регламентных заданий при которых нельзя обновлять конфигурацию и тд
Следующим пунктом опишем скрипт, который выполняет обновление баз. Он запускается с помощью скриптового языка OneScript.
Файл update1C.os
#Использовать irac
#Использовать cmdline
#Использовать 1commands
// Формируется список настроек для обновления
//
// Возвращаемое значение:
// - Массив из Структура - Необходимые параметры для обновления конфигурации
Функция ПолучитьМассивБазДляОбновления()
МассивНастроеек = Новый Массив();
//TEST 1
МассивБазДляОбновления = Новый Массив();
СтруктураНастроеек = Новый Структура();
//Расположение проекта
СтруктураНастроеек.Вставить("КаталогПроекта", "C:\Users\admin\git\test_base_1");
//Файлы конфигурации проекта edt
СтруктураНастроеек.Вставить("PROJECT", "C:\Users\admin\git\test_base_1\conf");
//Порт агента сервера
СтруктураНастроеек.Вставить("Port", "1540");
//Порт менеджера кластрера
СтруктураНастроеек.Вставить("MainPort", "1541");
//Логин кластера сервера 1С
СтруктураНастроеек.Вставить("ClustersLogin", "log");
//Пароль кластера сервера 1С
СтруктураНастроеек.Вставить("ClustersPassword", "pswd");
БазаДанных = Новый Структура();
//Заголовок для сообщения в телеграмме
БазаДанных.Вставить("Заголовок", " База (test1)");
//Сервер публикации информационной базы
БазаДанных.Вставить("СерверHttp", "localhost");
//URL http-сервиса
БазаДанных.Вставить("АдресHttp", "test1/hs/api/v1/");
//Сервер БД
БазаДанных.Вставить("СерверБазыДанных", "localhost\SQL");
//Кластер сервера
БазаДанных.Вставить("СерверКластера", "localhost");
//Имя базы данных
БазаДанных.Вставить("ИмяБазыДанных", "TEST_1");
//Пользователь БД
БазаДанных.Вставить("ПользовательБазыДанных", "sql_login");
//Пароль пользователя БД
БазаДанных.Вставить("ПарольБазыДанных", "sql_password");
//Пользователь 1С
БазаДанных.Вставить("Пользователь1С", "update1C");
//Пароль пользоватлея 1С
БазаДанных.Вставить("Пароль1С", "update1C");
МассивБазДляОбновления.Добавить(БазаДанных);
БазаДанных = Новый Структура();
БазаДанных.Вставить("Заголовок", " База (test2)");
БазаДанных.Вставить("СерверHttp", "localhost");
БазаДанных.Вставить("АдресHttp", "test1/hs/api/v1/");
БазаДанных.Вставить("СерверБазыДанных", "localhost\SQL");
БазаДанных.Вставить("СерверКластера", "localhost");
БазаДанных.Вставить("ИмяБазыДанных", "TEST_2");
БазаДанных.Вставить("ПользовательБазыДанных", "sql_login");
БазаДанных.Вставить("ПарольБазыДанных", "sql_password");
БазаДанных.Вставить("Пользователь1С", "update1C");
БазаДанных.Вставить("Пароль1С", "update1C");
МассивБазДляОбновления.Добавить(БазаДанных);
СтруктураНастроеек.Вставить("МассивБазДляОбновления", МассивБазДляОбновления);
МассивНастроеек.Добавить(СтруктураНастроеек);
//TEST 2
МассивБазДляОбновления = Новый Массив();
СтруктураНастроеек = Новый Структура();
СтруктураНастроеек.Вставить("КаталогПроекта", "C:\Users\admin\git\test_base_2");
СтруктураНастроеек.Вставить("PROJECT", "C:\Users\admin\git\test_base_2\conf");
СтруктураНастроеек.Вставить("Port", "1540");
СтруктураНастроеек.Вставить("MainPort", "1541");
СтруктураНастроеек.Вставить("ClustersLogin", "log");
СтруктураНастроеек.Вставить("ClustersPassword", "pswd");
БазаДанных = Новый Структура();
БазаДанных.Вставить("Заголовок", " База (test3)");
БазаДанных.Вставить("СерверHttp", "localhost");
БазаДанных.Вставить("АдресHttp", "test3/hs/api/v1/");
БазаДанных.Вставить("СерверБазыДанных", "localhost\SQL");
БазаДанных.Вставить("СерверКластера", "localhost");
БазаДанных.Вставить("ИмяБазыДанных", "TEST_3");
БазаДанных.Вставить("ПользовательБазыДанных", "sql_login");
БазаДанных.Вставить("ПарольБазыДанных", "sql_password");
БазаДанных.Вставить("Пользователь1С", "update1C");
БазаДанных.Вставить("Пароль1С", "update1C");
МассивБазДляОбновления.Добавить(БазаДанных);
СтруктураНастроеек.Вставить("МассивБазДляОбновления", МассивБазДляОбновления);
МассивНастроеек.Добавить(СтруктураНастроеек);
Возврат МассивНастроеек;
КонецФункции
// Преобразует строку версии в число, сокращая до 3-го разряда
//
// Параметры:
// ВерсияСтрокой - Строка - Версия в формате ХХ.ХХ.ХХ.ХХ
//
// Возвращаемое значение:
// - Число - Версия в числовом эквиваленте
Функция ВерсияЧислом(Знач ВерсияСтрокой)
Разрядность = СтрНайти(ВерсияСтрокой, ".", , , 3);
Если Разрядность > 0 Тогда
ВерсияСтрокой = Лев(ВерсияСтрокой, Разрядность-1);
КонецЕсли;
Если ПустаяСтрока(ВерсияСтрокой) Или ВерсияСтрокой = "0.0.0.0" Тогда
Возврат 0;
КонецЕсли;
Результат = 0;
ОписаниеТипаЧисло = Новый ОписаниеТипов("Число");
Остаток = ВерсияСтрокой;
ПозицияТочки = СтрНайти(Остаток, ".");
Пока ПозицияТочки > 0 Цикл
ЧислоСтрокой = Лев(Остаток, ПозицияТочки - 1);
Число = ОписаниеТипаЧисло.ПривестиЗначение(ЧислоСтрокой);
Результат = Результат * 1000 + Число;
Остаток = Сред(Остаток, ПозицияТочки + 1);
ПозицияТочки = СтрНайти(Остаток, ".");
КонецЦикла;
Число = ОписаниеТипаЧисло.ПривестиЗначение(Остаток);
Результат = Результат * 1000 + Число;
Возврат Результат;
КонецФункции
//Глобальные переменные (общие для всех баз)
СМАЙЛ_ЗЕЛЕНЫЙ_КРУГ = "🟢";
СМАЙЛ_КРАСНЫЙ_КРУГ = "🔴";
СМАЙЛ_ИТОГ_ЗЕЛЕНЫЙ = "c89;";
СМАЙЛ_ИТОГ_КРАСНЫЙ = "c40;";
WORKSPACE = "C:\Users\admin\git\WS";
XML = "C:\Users\admin\git\XML";
LOGFILE="C:\Users\admin\git\log.txt";
LOGCOMMIT="C:\Users\admin\git\log_commit.txt";
ibcmd = "C:\Program Files\1cv8\8.3.xx.xxxx\bin\ibcmd.exe"; //xx.xxxx заменить на версию 1С (например 21.1508)
МассивБаз = ПолучитьМассивБазДляОбновления();
Для Каждого СтруктураОбновления ИЗ МассивБаз Цикл
Команда = Новый Команда;
//Переходи в кактлог проекта и загружаем коммиты, скачиваем все удаленные ветки
Команда.УстановитьСтрокуЗапуска("cd " + СтруктураОбновления.КаталогПроекта + " & git fetch & git branch -r");
КодВозврата = Команда.Исполнить();
//Получаем актуальную версию конфигурации
Заголовки = Новый Соответствие();
HTTPЗапрос = Новый HTTPЗапрос(СтруктураОбновления.АдресHttp + "/version", Заголовки);
Результат = HTTPСоединение.Получить(HTTPЗапрос);
АктуальнаяВерсия = Результат.ПолучитьТелоКакСтроку();
АктуальнаяВерсияЧислом = ВерсияЧислом(АктуальнаяВерсия);
//Находим все доступные релизные ветки
МассивРелизов = НайтиФайлы(СтруктураОбновления.КаталогПроекта + "\.git\refs\remotes\origin\release\","*.*");
//Для отображения коммитов которые вошли в релиз
Релиз = "0.0.0";
НачалоЛога = "";
ОкончаниеЛога = "";
Для Каждого Файл Из МассивРелизов Цикл
ПроверяемаяВерсия = ВерсияЧислом(Файл.Имя);
Если ПроверяемаяВерсия > АктуальнаяВерсияЧислом Тогда
ОкончаниеЛога = "release/"+Файл.Имя;
Релиз = Файл.Имя;
ИначеЕсли ПроверяемаяВерсия = АктуальнаяВерсияЧислом Тогда
НачалоЛога = "release/"+Файл.Имя;
КонецЕсли;
КонецЦикла;
//проверка что релиз обновился
Если НЕ Релиз = "0.0.0" Тогда
//Начинаем формировать сообщения для отправки в теелграмм
СообщениеТелеграм = " Релиз: " + Релиз + "</b>";
//Переключаемся на релизную ветку и обновляем ее с сервера
Команда.УстановитьСтрокуЗапуска("cd " + СтруктураОбновления.КаталогПроекта + " & git checkout release\" + Релиз + " & git pull");
КодВозврата_checkout = Команда.Исполнить();
СообщениеТелеграм = СообщениеТелеграм + Символы.ПС + ?(КодВозврата_checkout = 1, СМАЙЛ_ЗЕЛЕНЫЙ_КРУГ, СМАЙЛ_КРАСНЫЙ_КРУГ) + " получение данных с GitHub";
//Выводим шапку в файл лога
Команда.УстановитьСтрокуЗапуска("echo %date% >> """+LOGFILE+""" 2>&1");
КодВозврата = Команда.Исполнить();
//Формируем сообщение коммитов вошедших в релиз
СообщениеЛогаКоммитов = "";
Если ЗначениеЗаполнено(НачалоЛога) И ЗначениеЗаполнено(ОкончаниеЛога) Тогда
Команда.УстановитьСтрокуЗапуска("cd " + СтруктураОбновления.КаталогПроекта + " & git log --pretty=format:""%an: %s"" --no-merges "+НачалоЛога+".."+ОкончаниеЛога+" > """ + LOGCOMMIT + """ 2>&1");
КодВозврата_log = Команда.Исполнить();
Коммиты = Новый ТекстовыйДокумент;
Коммиты.Прочитать(LOGCOMMIT);
СообщениеЛогаКоммитов = Символы.ПС + "<code>Коммиты в релизе:" + Символы.ПС + Коммиты.ПолучитьТекст() + "</code>";
КонецЕсли;
//Очищаем каталоги для сборки. Удалив их и создав заново
Команда.УстановитьСтрокуЗапуска("rd /S /Q """ + WORKSPACE + """ & rd /S /Q """ + XML+ """");
КодВозврата_rd = Команда.Исполнить();
Команда.УстановитьСтрокуЗапуска("md """ + WORKSPACE + """ & md """ + XML+ """");
КодВозврата_md = Команда.Исполнить();
СообщениеТелеграм = СообщениеТелеграм + Символы.ПС + ?(КодВозврата_rd+КодВозврата_md = 0, СМАЙЛ_ЗЕЛЕНЫЙ_КРУГ, СМАЙЛ_КРАСНЫЙ_КРУГ) + " подготовка структуры каталогов";
//Экспорт проекта в xml-выгрузку конфигурации
//xxxx.x.x заменить на версию edt (например 2022.1.2)
Команда.УстановитьСтрокуЗапуска("ring edt@xxxx.x.x:x86_64 workspace export --project """+ СтруктураОбновления.PROJECT +""" --configuration-files """+XML+""" --workspace-location """+WORKSPACE+""" >> """+LOGFILE+""" 2>&1");
КодВозврата_export = Команда.Исполнить();
СообщениеТелеграм = СообщениеТелеграм + Символы.ПС + ?(КодВозврата_export = 0, СМАЙЛ_ЗЕЛЕНЫЙ_КРУГ, СМАЙЛ_КРАСНЫЙ_КРУГ) + " выгрузка конфигурации в XML";
Для Каждого База ИЗ СтруктураОбновления.МассивБазДляОбновления Цикл
//Проверка в базе на доступность ее обновить
HTTPСоединение = Новый HTTPСоединение(База.СерверHttp);
Заголовки = Новый Соответствие();
HTTPЗапрос = Новый HTTPЗапрос(База.АдресHttp + "/check",Заголовки);
Результат = HTTPСоединение.Получить(HTTPЗапрос);
Если НЕ Результат.КодСостояния = 200 Тогда
Продолжить;
КонецЕсли;
//Завершение работы активных пользователей
Заголовки = Новый Соответствие();
Заголовки.Вставить("Port", СтруктураОбновления.Port);
Заголовки.Вставить("MainPort", СтруктураОбновления.MainPort);
Заголовки.Вставить("BaseName", База.ИмяБазыДанных);
Заголовки.Вставить("login", СтруктураОбновления.ClustersLogin);
Заголовки.Вставить("password", СтруктураОбновления.ClustersPassword);
Заголовки.Вставить("AgentSrv", База.СерверКластера);
HTTPЗапрос = Новый HTTPЗапрос(СтруктураОбновления.АдресHttp + "/disconnect",Заголовки);
Результат = HTTPСоединение.Получить(HTTPЗапрос);
СообщениеТелеграмБаза = СообщениеТелеграм + Символы.ПС + ?(Результат.КодСостояния = 200, СМАЙЛ_ЗЕЛЕНЫЙ_КРУГ, СМАЙЛ_КРАСНЫЙ_КРУГ) + " завершение активных сеансов";
//Импорт конфигурации из XML с помощью утилиты ibcmd
Команда.УстановитьСтрокуЗапуска(""""+ibcmd+""" infobase config import --dbms=MSSQLServer --db-server="+База.СерверБазыДанных+" --db-name="+База.ИмяБазыДанных+" --db-user="+База.ПользовательБазыДанных+" --db-pwd="""+База.ПарольБазыДанных+""" --user="+База.Пользователь1С+" --password="+База.Пароль1С+" """+XML+""" >> """+LOGFILE+""" 2>&1");
КодВозврата_import = Команда.Исполнить();
СообщениеТелеграмБаза = СообщениеТелеграмБаза + Символы.ПС + ?(КодВозврата_import = 0, СМАЙЛ_ЗЕЛЕНЫЙ_КРУГ, СМАЙЛ_КРАСНЫЙ_КРУГ) + " загрузка конфигурации в базу из XML";
//Обновление конфигурации
Команда.УстановитьСтрокуЗапуска(""""+ibcmd+""" infobase config apply --dbms=MSSQLServer --db-server="+База.СерверБазыДанных+" --db-name="+База.ИмяБазыДанных+" --db-user="+База.ПользовательБазыДанных+" --db-pwd="""+База.ПарольБазыДанных+""" --user="+База.Пользователь1С+" --password="+База.Пароль1С+" --force >> """+LOGFILE+""" 2>&1");
КодВозврата_apply = Команда.Исполнить();
СообщениеТелеграмБаза = СообщениеТелеграмБаза + Символы.ПС + ?(КодВозврата_apply = 0, СМАЙЛ_ЗЕЛЕНЫЙ_КРУГ, СМАЙЛ_КРАСНЫЙ_КРУГ) + " обновление конфигурации";
СообщениеТелеграмБаза = "<b>" + ?(КодВозврата_md+КодВозврата_export+КодВозврата_import+КодВозврата_apply = 0, СМАЙЛ_ИТОГ_ЗЕЛЕНЫЙ, СМАЙЛ_ИТОГ_КРАСНЫЙ) + База.Заголовок + СообщениеТелеграмБаза + СообщениеЛогаКоммитов;
//Отправка сообщения в телеграмм
Заголовки = Новый Соответствие();
Заголовки.Вставить("Text", КодироватьСтроку(СообщениеТелеграмБаза, СпособКодированияСтроки.КодировкаURL));
HTTPЗапрос = Новый HTTPЗапрос(СтруктураОбновления.АдресHttp + "/telegram",Заголовки);
Результат = HTTPСоединение.Получить(HTTPЗапрос);
КонецЦикла;
КонецЕсли;
КонецЦикла;
Краткое описание работы скрипта.
Сначала формируется список параметров и массив баз для обновления. В дальнейшем что бы добавить или удалить базу редактируем в файле функцию ПолучитьМассивБазДляОбновления. Далее обновляем из git-а (который подключен по ssh) ветки и коммиты. Получаем релиз базы, и сравниваем его с релизными ветками (release/*). Если есть новый релиз, тогда переключаемся на ветку релиза и скачиваем изменения. Подготавливаем структуру каталогов и экспортируем в нее проект edt в виде xml файлов. Далее в каждую конфигурацию, из массива мы загружаем данные проекта из xml-файлов, и обновляем ее. В самом конце отправляем сообщение из 1С в зависимости от настроек.
Пример.
Посмотрим, что получается в итоге. На примере, перехода с релиза 0.1.9 на 0.1.10 тестовой конфигурации. В каждой релизной ветке повышаем релиз (версию конфигурации) на следующий. На скриншоте видно, что после релиза 0.1.9 у нас было несколько коммитов, а также в релизной ветке мы изменили версию конфигурации на 0.1.10.
Запускаем выполнение скрипта командой из cmd или вешаем на планировщик заданий в ОС Windows. Например, запуск из командной строке выглядит так
oscript C:\scripts\update1C.os
После выполнения скрипта получаем сообщение в чат с ботом и обновленную конфигурацию до 0.1.10
Если что-то не работает, смотрим логи в файле %LOGFILE%
Ссылки: