Так сложилось, что в нашем коллективе добрых, милых и отзывчивых математиков (ДМОиМ) в качестве языка общего назначения используется Ceylon. Будучи отзывчивыми, мы не только используем этот язык, но и участвуем в его развитии, преимущественно багрепортами. С пулреквестами хуже, и первая причина тому: отсутствие в офисе прямого доступа в интернет, только через прокси-сервер. (Нулевая, конечно, нехватка времени.)

Ceylon-контейнер

Под катом подробности о том, какие именно проблемы возникли при сборке проекта Ceylon из исходного кода и как они были решены. В конце так же несколько слов о её конечной цели.

Что вообще за слон такой


Для начала несколько слов о Ceylon, как о проекте. (Как о языке программирования, кому интересно, читайте тут.) Двумя базовыми подпроектами (помимо такого инструментария, как плагины к IDE, собственный репозиторий) в нём являются ceylon и ceylon-sdk. Первый включает в себя непосредственно компилятор и набор консольных утилит, написан на Java. Второй — набор базовых библиотек, написанных на самом Ceylon. Сборка каждого из проектов осуществляется с помощью Apache Ant, так же, конечно, необходима установленная JDK.

Компилятор собирается командой ant clean dist, после чего его из каталога dist/dist можно скопировать в /usr/local/share/ceylon или куда-нибудь ещё по вкусу и сделать ссылку на исполняемый файл в каталоге, который виден в $PATH. Библиотеки собираются и копируются куда надо командой ant clean publish.

При наличии прямого доступа в интернет (вкупе с нужными версиями Java, Ant и исходников Ceylon) сборка проходит без каких-либо затруднений.

Проблема по курсу


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

При сборке на открытой палубе голом железе рабочей станции компилятор собрался спокойно (может, все зависимости включены в проект, может, помогли ранее сделанные настройки), а вот набор библиотек никак не хотел собираться. Казалось бы, и шлюз-прокси есть, и резервуары с библиотеками (Ceylon Herd, Maven Nexus), но чего-то не хватает. Наверно, «солёных брызг, порывов бешеного ветра…» ? Бараш.

Ошибка была такая:
compile-jvm:
[ceylon-compile] /home/akopilov/workspace/docker/CeylonBuilding/ceylon-sources/ceylon-sdk/source/ceylon/interop/spring/module.ceylon:25: error: cannot find module artifact 'maven:org.springframework.data:spring-data-commons-1.13.6.RELEASE.car'
[ceylon-compile] shared import maven:org.springframework.data:"spring-data-commons" "1.13.6.RELEASE";
[ceylon-compile] ^
[ceylon-compile] - dependency tree: 'ceylon.interop.spring/1.3.4-SNAPSHOT' -> 'org.springframework.data:spring-data-commons/1.13.6.RELEASE'
[ceylon-compile] /home/akopilov/workspace/docker/CeylonBuilding/ceylon-sources/ceylon-sdk/source/ceylon/interop/spring/module.ceylon:26: error: cannot find module artifact 'maven:org.springframework.data:spring-data-jpa-1.11.6.RELEASE.car'
[ceylon-compile] shared import maven:org.springframework.data:"spring-data-jpa" "1.11.6.RELEASE";
[ceylon-compile] ^
[ceylon-compile] - dependency tree: 'ceylon.interop.spring/1.3.4-SNAPSHOT' -> 'org.springframework.data:spring-data-jpa/1.11.6.RELEASE'
[ceylon-compile] /home/akopilov/workspace/docker/CeylonBuilding/ceylon-sources/ceylon-sdk/source/ceylon/interop/spring/module.ceylon:27: error: cannot find module artifact 'maven:org.springframework:spring-tx-4.3.10.RELEASE.car'
[ceylon-compile] shared import maven:org.springframework:"spring-tx" "4.3.10.RELEASE";
[ceylon-compile] ^
[ceylon-compile] - dependency tree: 'ceylon.interop.spring/1.3.4-SNAPSHOT' -> 'org.springframework:spring-tx/4.3.10.RELEASE'
[ceylon-compile] ceylon compile: There were 3 errors

BUILD FAILED
/home/akopilov/workspace/docker/CeylonBuilding/ceylon-sources/ceylon-sdk/build.xml:224: While executing command
/home/akopilov/.sdkman/candidates/ceylon/current/bin/../bin/ceylon
--cwd=/home/akopilov/workspace/docker/CeylonBuilding/ceylon-sources/ceylon-sdk
--define=ant.file.type.Ceylon SDK=file
--define=ant.file.type=file
--define=ant.file=/home/akopilov/workspace/docker/CeylonBuilding/ceylon-sources/ceylon-sdk/build.xml
--define=ant.file.Ceylon SDK=/home/akopilov/workspace/docker/CeylonBuilding/ceylon-sources/ceylon-sdk/build.xml
--define=ant.project.name=Ceylon SDK
--define=ant.project.default-target=test
--define=ant.project.invoked-targets=clean,publish
--define=ceylon.terminal.usecolors=yes
compile
--out
/home/akopilov/workspace/docker/CeylonBuilding/ceylon-sources/ceylon-sdk/modules
--encoding
UTF-8
--source
/home/akopilov/workspace/docker/CeylonBuilding/ceylon-sources/ceylon-sdk/source
--resource
/home/akopilov/workspace/docker/CeylonBuilding/ceylon-sources/ceylon-sdk/resource
--pack200
ceylon.buffer
ceylon.collection
ceylon.dbc
ceylon.decimal
ceylon.file
ceylon.html
ceylon.interop.java
ceylon.interop.persistence
ceylon.interop.spring
ceylon.io
ceylon.json
ceylon.locale
ceylon.logging
ceylon.math
ceylon.http.common
ceylon.http.client
ceylon.http.server
ceylon.uri
ceylon.numeric
ceylon.process
ceylon.promise
ceylon.random
ceylon.regex
ceylon.test
ceylon.time
ceylon.toml
ceylon.transaction
ceylon.unicode
ceylon.whole
com.redhat.ceylon.war
Compile failed; see the compiler error output for details.

Самое смешное, что система сборки требует Java-зависимость из Maven в формате Ceylon (car), которой там быть не должно в принципе.

Упаковка сборки в контейнер


Что такое контейнер, местная аудитория должна быть в курсе. Для проходящих мимо — ссылка.

Итак, не добившись успеха, я решил: соберу Ceylon и Ceylon SDK в открытом море на компьютере с прямым доступом в интернет и в контейнере доставлю эту сборку в офисную сеть. По идее, контейнер будет содержать в себе все зависимости, после чего можно будет править код для собственных нужд и пулреквестов и запускать пересборку.

В качестве промежуточного уровня был подготовлен образ с JDK.

Dockerfile kopilov/java8
FROM ubuntu:latest

RUN apt-get update -y && apt-get install -y software-properties-common

RUN   echo oracle-java8-installer shared/accepted-oracle-license-v1-1 select true | debconf-set-selections &&   add-apt-repository -y ppa:webupd8team/java &&   apt-get -y update  &&   apt-get install -y oracle-java8-installer &&   rm -rf /var/cache/oracle/jdk8/installer

RUN apt-get install -y maven

#RUN apt-get install -y locales && #    locale-gen "ru_RU.UTF-8" && #    echo "LANG=ru_RU.UTF-8" >> /etc/default/locale

#ENV LANG=ru_RU.UTF-8 #    LANGUAGE=ru_RU.UTF-8 #    LC_ALL=ru_RU.UTF-8

RUN apt-get clean &&     rm -rf /var/lib/apt/lists/*


Dockerfile был позаимствован из корпоративных конфигураций, для обобщения закомментировано создание русской локали. Основная его задача: установить Oracle JDK (через вспомогательную программу, в силу лицензии Oracle) и Maven, так же в начале обновляются, а в конце чистятся данные для APT. Ant тоже присутствует, видимо, как зависимость.

Следующий этап: делаем образ со сборкой Ceylon. При создании образа рядом с Dockerfile должнен быть каталог ceylon-sources, а в нём — проекты ceylon и ceylon-sdk. Сперва хотел засунуть git clone прямо в создание образа, но редактировать исходники мы будем локально, а клонировать два раза смысла нет.

Dockerfile kopilov/ceylon_build:1.3.4-SNAPSHOT


FROM kopilov/java8:latest

#Именно эту версию мы будем собирать
ENV CEYLON_VERSION 1.3.4-SNAPSHOT

#Повторно скачаем данные APT (их удаляли, чтобы образ был тоньше),
#установим git (он каким-то образом участвует в сборке)
#и netcat (он потребуется немного позже) 
RUN apt-get update -y &&     apt-get install -y git &&     apt-get install netcat-traditional

#Копируем исходники с рабочей станции. Теоретически,
#тут может быть git clone, но это много трафика, который уже выкачан.
WORKDIR /usr/src/ceylon
ADD ceylon-sources /usr/src/ceylon

#Собираем компилятор, ставим в систему
WORKDIR /usr/src/ceylon/ceylon
RUN ant clean dist &&     cp -a dist/dist /usr/local/share/ceylon-${CEYLON_VERSION} &&     ln -s /usr/local/share/ceylon-${CEYLON_VERSION}/bin/ceylon /usr/local/bin

#Собираем библиотеки
WORKDIR /usr/src/ceylon/ceylon-sdk
RUN ant clean publish

#Удаляем ненужное
RUN apt-get clean && rm -rf /var/lib/apt/lists/*

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

Пересборка с закрытым каналом


Ожидается, что контейнер с готовой сборкой включает все зависимости, и интернет больше не потребуется. Запускаем docker run -it kopilov/ceylon_build, затем ant clean publish — как бы не так.

Ошибка на этот раз:
[ceylon-compile] /usr/src/ceylon/ceylon-sdk/source/ceylon/interop/spring/CeylonRepositoryImpl.java:12: error: Ceylon backend error: package org.springframework.transaction.annotation does not exist
[ceylon-compile] import org.springframework.transaction.annotation.Transactional;
[ceylon-compile] ^
[ceylon-compile] /usr/src/ceylon/ceylon-sdk/source/ceylon/interop/spring/CeylonRepositoryImpl.java:29: error: Ceylon backend error: cannot find symbol
[ceylon-compile] @Transactional(readOnly = true)
[ceylon-compile] ^
[ceylon-compile] symbol: class Transactional
[ceylon-compile] /usr/src/ceylon/ceylon-sdk/source/ceylon/interop/spring/CeylonRepositoryImpl.java:44: error: Ceylon backend error: cannot find symbol
[ceylon-compile] @Override @Ignore @Transactional
[ceylon-compile] ^


Такая же ошибка на компьютере, где был создан образ, если отключить доступ в интернет. Чего же не хватает ещё? Без акулы трафика не разобраться.

При выключенном доступе в интернет трафик из Docker выглядит в Wireshark так:



После нескольких неудачных попыток определить IP сервера repo1.maven.org отображается вышеуказанная ошибка. А вот что происходит, если подключение восстановить:



Парадокс: система делает GET-запрос, чтобы получить ответ с ошибкой 404, после чего спокойно продолжает сборку. А если ей этот запрос не выполнить, пользователю выдаётся, вроде бы, абсолютно ортогональная тому GET-запросу ошибка. Ниже можно заметить запросы на modules.ceylon-lang.org (aka Herd) по HTTPS, но сперва попробуем разобраться с первым.

Первое, что решено было сделать: добавить строку «127.0.0.1 repo1.maven.org» в файл /etc/hosts. Теперь надо как-то сымитировать ответ «404 NOT FOUND». Недавний хабрасерфинг показал, что в роли простейшего веб-сервера может выступить netcat (пруф). Перед запуском сборки (но после запуска контейнера) набираю в параллельном терминале

docker container ls
#скопировать название_контейнера
docker exec -it название_контейнера bash
nc -lp 80

После этого запускаю сборку (ant), дожидаюсь появления в терминале с netcat GET-запроса, печатаю в ответ
HTTP/1.1 404 NOT FOUND
Server: nc


Вуаля! Сборка пошла дальше! Потом система делает ещё один точно такой же запрос (при сборке под JavaScript), и процесс успешно завершён.

Автоматизация вышеописанного и доработка библиотеки


Подготовленный образ включал сборку оригинальной библиотеки Ceylon SDK, а конечной целью было собрать доработанную. Поэтому был был сделан ещё один Dockerfile, замещающий исходники:

FROM kopilov/ceylon_build:1.3.4-SNAPSHOT

ENV CEYLON_VERSION 1.3.4-SNAPSHOT

WORKDIR /usr/src/ceylon/ceylon-sdk
RUN rm -rf * 
ADD ceylon-sources/ceylon-sdk .

Он должен был быть максимально простым (ведь образ пересоздаётся для каждой тестовой пересборки — почти для каждой правки исходников), именно поэтому установка netcat была выполнена заранее. Трюк с netcat был завёрнут в следующий скрипт (plug.sh):

#!/bin/bash

IMAGE_NAME="kopilov/ceylon_patch_src"
CONTAINER_ID=$(docker container ls | grep "${IMAGE_NAME}" | sed 's/ .*//')
docker exec -i $CONTAINER_ID bash << END
echo "127.0.0.1 repo1.maven.org" >> /etc/hosts

echo "HTTP/1.1 404 NOT FOUND" > /tmp/notfound
echo "Server: nc" >> /tmp/notfound
echo "" >> /tmp/notfound

nc -lp 80 < /tmp/notfound
nc -lp 80 < /tmp/notfound
END

Ещё скрипт, чтобы достать сборку из контейнера (get_built_ceylon.sh):

#!/bin/bash

CONTAINER_ID=$(docker container ls -a | grep  kopilov/ceylon_patch_src | sed 's/ .*//')

rm -r ~/.sdkman/candidates/ceylon/1.3.4-SNAPSHOT/
docker cp $CONTAINER_ID:/usr/local/share/ceylon-1.3.4-SNAPSHOT .
mv ceylon-1.3.4-SNAPSHOT /home/akopilov/.sdkman/candidates/ceylon/1.3.4-SNAPSHOT

rm -r ~/.ceylon/repo/
docker cp $CONTAINER_ID:/root/.ceylon/repo ~/.ceylon

Дальше тяга к рационализации иссякла, оставалось действовать вручную. После каждой правки исходников сперва в одной вкладке терминала запускать docker build -t kopilov/ceylon_patch_src . && docker run -it kopilov/ceylon_patch_src, потом в соседней ./plug.sh, потом опять в первой ant clean publish. И, если сборка прошла без ошибок (и если уже есть, чего тестировать) — ./get_built_ceylon.sh.

Результаты и истоки


Главным результатом проделанной работы «на перспективу» стала возможность нашей (а может, и не только) команды отправлять предварительно протестированные пулреквесты в апстрим проекта. На данный момент лично мной отправлен этот, написанный по ходу дела: github.com/ceylon/ceylon-sdk/pull/688

Образы Docker kopilov/java8 и kopilov/ceylon_build доступны на hub.docker.com, если вдруг кому-нибудь нужно.

А началось всё с того, что на прошедшей неделе у меня было на редкость мало срочных, и даже не очень срочных задач, даже начальник отдела куда-то уехал. И, уезжая, сказал: «Подумай, что ещё полезное можно прикрутить к твоему проекту.» (Проект находится на стадии прототипа.) И захотелось мне прикрутить интернационализацию, и не костылями, а готовым решением.

P.S.
Источник фона КДПВ

Комментарии (3)


  1. toklian
    18.09.2017 23:04

    Я прошу прощения, но ant.apache.org/manual/proxy.html и wiki.archlinux.org/index.php/proxy_settings вроде должны решать ваши проблемы… Либо я что-то упустил…


    1. Kopilov Автор
      18.09.2017 23:23

      Настройка рабочих станций включает локальный squid (раньше был cntlm), осуществляющий NTLM-авторизацию на корпоративном, в него по лупбэку стучится весь остальной софт. Прописаны переменные окружения http_proxy и https_proxy=http://127.0.0.1/ в оболочке и аналогичные параметры везде где только можно. Но бывают программы, не предусматривающие прокси в принципе.

      ant.apache.org/manual/proxy.html надо будет попробовать, спасибо. Поскольку ни один из наших проектов не использует Ant, мы могли это пропустить.


      1. toklian
        19.09.2017 00:12

        Практически все билд-системы для джавы умеют работыть с репозиториями по «разным» протоколам (см. wagon для мавена, как наиболее иллюстративный пример) со всем спектром их возможностей. Just FYI)