«Атака клонов» или пишем макрос для клонирования репозитория GitLab внутрь XWiki

—

от автора

Наконец-то я «раздобыл» немного свободного времени, а значит пришла пора продолжить серию туториалов по XWiki.

После публикации одной моих из статей MaxK82 спросил у меня, можно ли как-то в XWiki подключить документацию из git репозитория, так чтобы наладить её версионирование.
К сожалению эта статья не ответит на его вопрос, но возможно укажет направление, в котором стоит «копать».

Поэтому сегодня мы с вами:

Cразу дам дисклеймер — я техпис, а не программист.
Поэтому не стоит ожидать в статье, хороших примеров кода и удачных решений.
Это скорее материал для других технических писателей или может быть docops, которым выпадет честь работать с XWiki.

Уделим пару минут очевидному:

  1. Для туториала нам понадобится аккаунт в Gitlab (не важно коммерческий или на вашем сервере).

  2. Нужно будет получить access token к аккаунту.

  3. Нужен будет репозиторий, я использую «Learn GitLab» (не помню откуда он у меня взялся). Но вам подойдет любой репозиторий в котором есть Readme.md (или другой markdown файл). Логика будет похожей.

  4. В своей статье я использовал docker версию XWiki 13.10.5 со Standart Flavor, но работать будет и в XWiki 12.X.

  5. Нам понадобится установить расширение GitAPI.

Ну и самое главное, нам нужно будет установить расширение поддержки синтаксиса Markdown.

.

Вообще у Xwiki есть разные варианты интеграции с git, например импорт с GitHub wiki или вот просто подключение к GitHub через его API.
Возможно Вам удастся найти готовые примеры и решения.

Сразу скажу, что в этот раз мы сделаем очень топорный макрос, его прообраз мне был нужен для того чтобы коллегам не пришлось добавлять домен с XWiki в настройки CORS нашего GitLab.

Прошу вас рассматривайте этот туториал скорее как демонстрацию, а не как четкое руководство к действию, потому что клонировать целый репозиторий на сервер XWiki ради 1 markdown файла может быть не лучшей идеей.

Пару слов о том, что такое макрос.
Макрос в Xwiki — это набор инструкций, который может быть включен в контент страницы. Если вы использовали Conflunce, то вам это знакомо.

Создать свой макрос в XWiki очень просто, для этого надо следовать инструкции.

Для начала создадим новую страницу, можно прямо в корне.
Название страницы не критично, контент можно не размещать.

Создание пустой страницы для макроса
Создание пустой страницы для макроса

Но если вы возьметесь за дело всерьез, то я бы рекомендовал размещать макросы в каком-то одном месте (например, внутри страницы «Macros»).

Далее перейдите в редактор объектов. Можно просто нажать латинскую клавишу «O» на странице после её создания
(если чудо не произошло, посмотрите как включить продвинутый режим редактора в одной из прошлых статей).

Страница станет макросом, как только мы добавим ей объект WikiMacroClass

Это по сути и есть сам макрос, но прежде, чем перейти к его детальному описанию, добавим еще несколько объектов с переменными для макросаWikiMacroParameterClass на страницу.

Создайте 6 объектов с переменными, они позволят нам управлять макросом из пользовательского интерфейса.

Первый разберем подробнее, а остальные я дам скриншотами ниже.

Мы задаем путь к репозиторию в git.

  • Название параметра (GIT_URL), то как будет называться поле в UI пользователя.

  • Описание параметра — текст подсказки к параметру (ссылки работать не будут).

  • Обязательность параметра — если да, то макрос не создаться без ввода значения. В первом случае параметр обязательный.

  • Значение по умолчанию, если задать, то поле будет предзаполненно.

  • Тип параметра — формат переменной, я так понимаю основные типы JAVA работают нормально, в данном случае String. Если честно, я уже точно не помню, как это работает, поэтому оставим так.

Скриншоты с другими параметрами я спрячу под спойлер, чтобы не раздувать статью.

Остальные параметры
Адрес размещения репозитория на сервере XWiki
Адрес размещения репозитория на сервере XWiki
Путь к вставляемому файлу, включая его имя.
Путь к вставляемому файлу, включая его имя.
Формат в котором выводится текст (вдруг мы захотим не только markdown)
Формат в котором выводится текст (вдруг мы захотим не только markdown)
Ветка в которой ищем файл.
Ветка в которой ищем файл.
Иногда бывают глюки, поэтому мы сделаем возможность очистки папки перед клонированием.
Иногда бывают глюки, поэтому мы сделаем возможность очистки папки перед клонированием.

Если боитесь, что что-то забыли можно проверить себя на скриншоте UI макроса в редакторе страницы.
Так же страничку можно забрать из GitHub (вы можете её импортировать через панель администратора в разделе «Контент»)

Вернемся к Макросу

Настройки макроса
Настройки макроса
  • ID макроса — то как он будет называться в режиме редактора исходного кода.

  • Название макроса — то как будет отображаться в UI пользователя

  • Описание — подсказка для пользователя.

  • Категория — категория для фильтров по типам макросов.

  • Можно ли вставить в линию — если нет, то нужен будет отступ в редакторе исходного кода.

  • Видимость макроса — в каких границах можно использовать (у нас в рамках текущей вики)

  • Можно ли вставить контент в макрос — в нашем случае нет

  • Тип контента — выберите Wiki.

Осталось загрузить непосредственно код макроса.
На всякий случай сохраните ваш труд и вздохните с облегчением.

Добавим клонирование и удаление репозитория

Перейдем непосредственно к коду.

Полный код под спойлером.
{{groovy}} import org.apache.commons.io.* import org.eclipse.jgit.api.* import org.eclipse.jgit.lib.* import org.eclipse.jgit.revwalk.* import org.eclipse.jgit.storage.file.* import org.eclipse.jgit.transport.*; import org.xwiki.environment.*; import org.apache.commons.io.FilenameUtils; import groovy.io.*; import org.apache.commons.io.FileUtils;  Git git;   def CredentialsProvider getCredentialsProvider() {   return new UsernamePasswordCredentialsProvider("UXXXX", "glXXXt-XXXXXXXXXXXXXXXXXXXXX") }  def service = services.get("git");  def Repository getRepository(String repositoryURI, String localDirectoryName) {   Repository repository;   Environment environment = services.component.getInstance(Environment);   File permDir = environment.getPermanentDirectory();   File localGitDirectory = new File(permDir, "git")   File localDirectory = new File(localGitDirectory, localDirectoryName);   File gitDirectory = new File(localDirectory, ".git");  // println "Local Git repository is at [${gitDirectory}]"   FileRepositoryBuilder builder = new FileRepositoryBuilder();   try {     // Step 1: Initialize Git environment     repository = builder.setGitDir(gitDirectory)                         .readEnvironment()                         .findGitDir()                         .build();     Git git = new Git(repository);     // Step 2: Verify if the directory exists and isn't empty.     if (!gitDirectory.exists()) {       // Step 2.1: Need to clone the remote repository since it doesn't exist       git.cloneRepository()          .setCredentialsProvider(getCredentialsProvider())          .setDirectory(localDirectory)          .setURI(repositoryURI).setCloneAllBranches(false).setBranch(wikimacro.parameters.GIT_TREE)          .call();            }   } catch (Exception e) {     throw new RuntimeException(String.format("Failed to execute Git command in [%s]", gitDirectory), e);   }   return repository; }   def sourceRepoURL = wikimacro.parameters.GIT_URL; def sourceRepoName = org.apache.commons.io.FilenameUtils.getName("/" + wikimacro.parameters.GIT_LOCAL_PATH);  // delete folder before cloning, for clean pull.    if (wikimacro.parameters.DELETE_FOLDER)   {       def file = new File("../xwiki/data/git/"+sourceRepoName)   if (file.exists()){      FileUtils.deleteDirectory(file)   } }    def textFilePath = wikimacro.parameters.TEXT_FILE_PATH;  def shortFileName = org.apache.commons.io.FilenameUtils.getName(textFilePath)    def repo = getRepository(sourceRepoURL, sourceRepoName)    result = new Git(repo).pull().setCredentialsProvider(getCredentialsProvider()).call()  def baseUrl = repo.getWorkTree().toString(); def fileName =org.apache.commons.io.FilenameUtils.separatorsToSystem(baseUrl + "/" + textFilePath);  def String fileContents = new File(fileName).getText('UTF-8');   xcontext.put("fileContent", fileContents)  {{/groovy}}  {{velocity}} #set($fileText =$xcontext.get("fileContent"))   {{content syntax="$wikimacro.parameters.OUTFORMAT.trim()"}}      $fileText   {{/content}} {{/velocity}}

В целом я не на 100% понимаю этот код, так как часть решений заимствовал. Но постараюсь объяснить что смогу.

{{groovy}} import org.apache.commons.io.* import org.eclipse.jgit.api.* import org.eclipse.jgit.lib.* import org.eclipse.jgit.revwalk.* import org.eclipse.jgit.storage.file.* import org.eclipse.jgit.transport.*; import org.xwiki.environment.*; import org.apache.commons.io.FilenameUtils; import groovy.io.*; import org.apache.commons.io.FileUtils;  Git git;

Импортируем зависимости.

def CredentialsProvider getCredentialsProvider() {   return new UsernamePasswordCredentialsProvider("UXXXX", "glXXXt-XXXXXXXXXXXXXXXXXXXXX") }

Функция для авторизации в GitLab, замените UХХХХ на имя пользователя, а glXX-XXX на access token.

Позаимствованный кусок под спойлером
def service = services.get("git");  def Repository getRepository(String repositoryURI, String localDirectoryName) {   Repository repository;   Environment environment = services.component.getInstance(Environment);   File permDir = environment.getPermanentDirectory();   File localGitDirectory = new File(permDir, "git")   File localDirectory = new File(localGitDirectory, localDirectoryName);   File gitDirectory = new File(localDirectory, ".git");  // println "Local Git repository is at [${gitDirectory}]"   FileRepositoryBuilder builder = new FileRepositoryBuilder();   try {     // Step 1: Initialize Git environment     repository = builder.setGitDir(gitDirectory)                         .readEnvironment()                         .findGitDir()                         .build();     Git git = new Git(repository);     // Step 2: Verify if the directory exists and isn't empty.     if (!gitDirectory.exists()) {       // Step 2.1: Need to clone the remote repository since it doesn't exist       git.cloneRepository()          .setCredentialsProvider(getCredentialsProvider())          .setDirectory(localDirectory)          .setURI(repositoryURI).setCloneAllBranches(false).setBranch(wikimacro.parameters.GIT_TREE)          .call();            }   } catch (Exception e) {     throw new RuntimeException(String.format("Failed to execute Git command in [%s]", gitDirectory), e);   }   return repository; }  

Эта функция, как я понимаю непосредственно клонирует репозиторий.

def sourceRepoURL = wikimacro.parameters.GIT_URL; def sourceRepoName = org.apache.commons.io.FilenameUtils.getName("/" + wikimacro.parameters.GIT_LOCAL_PATH);

Сохраняем и обрабатываем параметры с URL из параметров макроса.

// delete folder before cloning, for clean pull.    if (wikimacro.parameters.DELETE_FOLDER)   {       def file = new File("../xwiki/data/git/"+sourceRepoName)   if (file.exists()){      FileUtils.deleteDirectory(file)   } }

Если включен параметр «Удалять папку», то мы ее удаляем.

../xwiki/data/git/ - я уже плохо помню, но скорее всего это относительный путь, куда Xwiki по умолчанию складывает репозитории.

def textFilePath = wikimacro.parameters.TEXT_FILE_PATH;  def shortFileName = org.apache.commons.io.FilenameUtils.getName(textFilePath)    def repo = getRepository(sourceRepoURL, sourceRepoName)    result = new Git(repo).pull().setCredentialsProvider(getCredentialsProvider()).call()  def baseUrl = repo.getWorkTree().toString(); def fileName =org.apache.commons.io.FilenameUtils.separatorsToSystem(baseUrl + "/" + textFilePath);  def String fileContents = new File(fileName).getText('UTF-8');

Получаем данные из репозитория и сохраняем их в переменную.

xcontext.put("fileContent", fileContents)  {{/groovy}}

Отправляем содержимое переменной в общий контекст, чтобы забрать из макроса на Velocity.
По идее это не обязательно, так как должен был сработать обмен переменными между языками, но у меня что-то пошло не так.

На этом кусок Groovy кода заканчивается.

И начинается небольшой кусочек кода на Velocity.

Код на Velocity нам нужен потому, что из него можно вызывать другие макросы XWiki, в данном случае макрос Content, который позволит нам отобразить наш текст в выбранном формате. Обратите внимание, как вставляется переменная макроса (OUTFORMAT) в Velocity.

Кстати переменные макроса было не обязательно писать с больших букв, это моя прихоть.

{{velocity}} #set($fileText =$xcontext.get("fileContent"))   {{content syntax="$wikimacro.parameters.OUTFORMAT.trim()"}}      $fileText   {{/content}} {{/velocity}}

Ну вот и всё.

Остается сохранить и проставить настройки как на скриншоте.

Наслаждаемся результатом

Создадим еще страницу и разместим на ней наш свеженький макрос.

Окно выбора макроса
Окно выбора макроса
Параметры макроса
Параметры макроса

Сохраняем результат и проверяем.

GitLab
GitLab
Xwiki
Xwiki

Обратите внимание, что все относительные пути в ссылках и у картинок, сломаются. Лучше использовать абсолютные.

Ну вот и всё. Я понимаю, что пример получился не самым лучшим, потому что я уже сам подзабыл как работает часть кода и полей, но он демонстрирует разные приемы работы с XWiki и надеюсь, что будет кому-то полезен в условиях недостатка документации на русском языке.

P.S. Изначально вместо этой статьи я хотел написать материал о моём субъективном опыте одновременного использования XWiki и Confluence в качестве базы знаний для команды разработки.
Однако, в итоге решил, что человечество к такому пока еще не готово.
Но если вы все же хотите почитать материал на тему Confluence vs Xwiki напишите об этом в комментариях.


ссылка на оригинал статьи https://habr.com/ru/post/667166/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *