Как стать автором
Обновить

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

Время на прочтение8 мин
Количество просмотров1.7K

Наконец-то я "раздобыл" немного свободного времени, а значит пришла пора продолжить серию туториалов по 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 напишите об этом в комментариях.

Теги:
Хабы:
+3
Комментарии0

Публикации