Русский
Русский
English
Статистика
Реклама

Github

Перевод Шесть пасхалок GitHub

31.03.2021 18:20:59 | Автор: admin
В недрах кода GitHub скрыто немало пасхалок. Здесь мы поговорим о некоторых из них.



Кстати, вы знали о том, что фразу Easter egg (пасхальное яйцо, в просторечии пасхалка) придумал в 1979 году Стив Райт директор по разработке программного обеспечения Atari? Если вы смотрели фильм Первому игроку приготовиться значит вам всё уже должно быть понятно. Вот фрагмент фильма, где игрок находит первую в мире пасхалку, скрытую в классической игре Adventure.

1. Просто число


Полагаю, не существует такого языка программирования, в стандартной или математической библиотеке которого нет константы, хранящей значение числа . Но если случилось так, что поисковик Google упал, а то, чему учили на занятиях по математике, вылетело из головы, вспомнить значение числа можно, просто перейдя по адресу https://github.com/.

Откроется страница, на которой, в стиле ASCII-арта, будет показано число , записанное с точностью до 336 знака после запятой. Это очень удобно.


Число

Насколько я знаю, число , представленное другими способами, можно обнаружить, добавив к вышеприведённому адресу расширения файлов наподобие .json и .jpeg.


Вкусное число

2. Октокоты


Продолжим тему ASCII-арта. Знали ли вы о том, что в API GitHub есть конечная точка, ведущая к ASCII-изображению октокота Моны логотипа GitHub. Для того чтобы это изображение увидеть, нужно открыть в браузере адрес https://api.github.com/octocat (или воспользоваться curl).


Октокот

Облачко с текстом содержит частицу дзен-мудрости GitHub. Подробности об этом можно почитать здесь.

Обратите внимание на то, что некоторые образцы ASCII-арта представляют собой исполняемые файлы. Поэтому соблюдайте осторожность, загружая нечто подобное с помощью curl.

3. Всё есть дзен


Тот, кто весь долгий рабочий день глядит в тёмное окно терминала, пользуясь GitHub CLI, может позволить себе прогулку по дзен-саду своего репозитория, напоминающую старую текстовую игру. Для этого достаточно воспользоваться командой gh repo garden. По этому саду можно, в полном смысле этого слова, прогуляться, пользуясь навигационными клавишами, применяемыми в vi.

image

Дзен-сад

Каждый цветок в этом саду представлен первой буквой GitHub-имени пользователя, сделавшего коммит. Цвет цветка это первые 6 символов SHA-хеша коммита, воспринятых системой как шестнадцатеричный код цвета.

В результате, например, коммит b6b3d26ee50fc6540e1796d8bdc563d22da44ba5 будет представлен весьма приятным оттенком сиреневого цвета #b6b3d2.

4. Приукрашенные профили пользователей


То, о чём тут пойдёт речь, не такая уж и пасхалка. Это, скорее, неочевидная полезная возможность системы. Заключается она в добавлении в свой аккаунт репозитория, название которого совпадает с именем пользователя.

image

Особый репозиторий

Поместив в этот репозиторий немного Markdown-текста и пару картинок, можно рассказать о себе, о своих проектах, или о чём угодно другом.

Если вы хотите оснастить свой профиль интересным файлом README и ищете источник вдохновения взгляните на этот материал, где рассматривается десять достойных внимания примеров подобных файлов.

5. Жуть на панели Contributions


Раз в год панель Contributions выглядит гораздо страшнее, чем обычно. Для её раскрашивания, вместо оттенков зелёного цвета используются варианты цвета хэллоуинского (есть ведь такое слово?).


Хэллоуинская панель Contributions

6. Просмотр панели Contributions в стиле игр 1980-х


GitHub Skyline это, если кто не знает, инструмент для создания симпатичных трёхмерных визуализаций активности пользователя за указанный год. Вот, например, моя активность в 2020 году. То, что формирует GitHub Skyline, можно скачать в виде .stl-файла и напечатать на 3D-принтере (или заказать печать). Можно исследовать то, что получилось, в виртуальной реальности.

image

GitHub Skyline

А вот для того чтобы найти в GitHub Skyline пасхалку понадобится ввести код Konami ( B A). Тогда включится машина времени, которая унесёт вас далеко в прошлое. Вот твит того, кто нашёл эту интересную штуку.

Знаете какие-нибудь пасхалки в популярных сервисах вроде GitHub?
Подробнее..

Перевод Использование веб-компонентов при работе над GitHub

29.05.2021 14:11:58 | Автор: admin
Мы, сотрудники GitHub, гордимся тем, что наша платформа обеспечивает тем, кто ей пользуется, первоклассный опыт разработчика (Developer Experience, DX). Значительная часть наших усилий сосредоточена на фронтенде системы, который мы стремимся сделать настолько простым, быстрым и доступным, насколько это возможно. Для проекта таких масштабов, как GitHub, это та ещё задача. В кодовой базе нашего фронтенда, как и во многих других подобных кодовых базах, применяются компоненты независимые, изолированные фрагменты кода, пригодные для многократного использования. Они позволяют командам, которые занимаются проектом, создавать тщательно проработанные интерфейсы, делая своё дело быстро и эффективно, но при этом не отступая от высоких стандартов качества, принятых в GitHub.



Мы, в работе над GitHub, широко используем веб-компоненты. У нас имеется почти два десятка опенсорсных веб-компонентов, и ещё несколько десятков компонентов, код которых закрыт.

Как мы к этому пришли


Когда, больше десяти лет тому назад, платформа GitHub только появилась, наш фронтенд был представлен сравнительно небольшой кодовой базой, в которой, в основном, использовалась библиотека jQuery. После того, как прошло десять лет и было написано примерно 85000 строк кода, наш фронтенд дорос до таких размеров, когда начинают проявляться болезни роста. Мы, в итоге, ушли от jQuery (по причинам, о которых мы, в своё время, подробно рассказывали) и начали использовать новые технологии, которые могли бы помочь нам лучше решить стоящие перед нами задачи.

Мы начали пробовать свои силы в применении новых технологий, известных под названием веб-компоненты и представляющих собой набор стандартных возможностей браузеров, которые позволяют разрабатывать настраиваемые HTML-элементы, которые можно прогрессивно расширять с использованием JavaScript.

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

Два наших первых пользовательских элемента были выпущены в 2014 году. Это были <relative-time> и <local-time>, которые умели показывать время и дату в удобной форме, а так же <include-fragment>, который позволял нам выполнять отложенную загрузку HTML-фрагментов. Постепенно к нам пришло понимание того, какими мощными возможностями могут обладать подобные элементы. Тогда мы приступили к широкомасштабной замене существующих механизмов кодовой базы на новые. Например поменяли наше модальное диалоговое окно facebox на веб-компонент <details-dialog>. Теперь у нас были самые разные компоненты от весьма универсальных, многоцелевых сущностей, реализующих распространённые шаблоны поведения, вроде <remote-input>, до узкоспециализированных компонентов, таких, как элемент <markdown-toolbar> и родственные ему элементы.

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

Улучшение процесса создания компонентов


Для того чтобы помочь программистам эффективно создавать высококачественные веб-компоненты, и чтобы поощрить их к использованию лучших практик, мы работаем над несколькими инструментами, нацеленными на значительное упрощение процесса создания веб-компонентов.

Фреймворк ViewComponent


Мы переориентируем наш Rails-код на использование ViewComponent фреймворка для создания в Rails компонентов, пригодных для многократного использования. То, что создаётся с помощью ViewComponent, тесно связано с веб-компонентами. Дело в том, что между компонентами, разрабатываемыми в рамках этого фреймворка, и веб-компонентами может существовать прямая взаимосвязь. Это позволяет нашим программистам работать над одной и той же абстракцией, относящейся и к фронтенду, и к бэкенду.

Библиотека Catalyst


Catalyst наша опенсорсная библиотека, применение которой упрощает разработку веб-компонентов, стала той самой движущей силой, которая объединяет некоторые из наших лучших практик. Catalyst продвигает применение TypeScript-декораторов, что позволяет отказаться от больших объёмов шаблонного кода, который нужно писать в ходе разработки веб-компонентов.

Разработчики библиотеки Catalyst вдохновлялись замечательной библиотекой Stimulus и базовым классом LitElement от Google. Она спроектирована в расчёте на решение особого набора задач, стоящих перед нашими разработчиками. Наши внутренние DX-исследования показали, что применение Catalyst даёт, в сравнении со старыми подходами, существенные улучшения в деле написания кода компонентов.

Здесь можно почитать подробности о Catalyst о соглашениях, принятых в этой библиотеке, о паттернах и антипаттернах её использования.

Вспомогательные инструменты


Мы предлагаем разработчикам набор опенсорсных конфигураций линтера. Общие правила, применяемые при написании кода, выражены в плагине eslint-plugin-github. А для более глубокой проверки кода веб-компонентов мы используем плагин eslint-plugin-custom-elements. Оформление этих плагинов в виде опенсорсных проектов позволило нам убрать код из монолита, но при этом оставаться последовательными при работе над проектом.

У нас, кроме того, имеются внутренние тесты, которые направлены на проверку того, следуют ли программисты рекомендованным подходам к разработке, и того, что они больше не используют паттерны и модели, реализующие некие схемы поведения элементов системы, признанные устаревшими. Один из наших тестов направлен на контроль того, чтобы разработчики не применяли бы в новом коде паттерн facebox, признанный устаревшим. Этот тест предлагает использовать в качестве альтернативы facebox элемент <details-dialog>.

class FaceboxDeprecationTest < Test::Fast::TestCase  EXPECTED_NUMBER_OF_FACEBOXES = 44  # Find facebox triggers set with rel=facebox, in either HTML attributes or  # as part of hash assignment (for rails helpers)  REGEX_FOR_FACEBOX_BINDING = %r|rel\s*[=:]>?\s*["']?facebox|  REGEX_FOR_DATA_FACEBOX = %r|data-facebox\s*=>?\s*|  def test_limit_facebox    actual_rel_facebox = grep(REGEX_FOR_FACEBOX_BINDING, options: %w[-In], paths: %w[app/**/*.erb])    actual_data_facebox = grep(REGEX_FOR_DATA_FACEBOX, options: %w[-In], paths: %w[app/**/*.erb])    count = actual_rel_facebox.count("\n") + actual_data_facebox.count("\n")    assert_operator count, :<=, EXPECTED_NUMBER_OF_FACEBOXES, <<-EOLIt looks like you added a facebox. Please use <details-dialog> instead.If you must increment EXPECTED_NUMBER_OF_FACEBOXES in this test, please/cc @github/ui-frameworks-reviewers in your pull request, as we may be able to help! Thanks.EOL    assert_equal EXPECTED_NUMBER_OF_FACEBOXES, count, <<-EOLIt looks like you removed a facebox. YOU ARE AWESOME!Please decrement EXPECTED_NUMBER_OF_FACEBOXES in this test and treat yourself tosomething special.  You deserve it.EOL  endend

Новый жизненный цикл наших веб-компонентов


Путь от моделей поведения, реализованных во фронтенде и специфичных для нашего приложения, к опенсорсным веб-компонентам начинается с Catalyst-компонента, размещённого в монолитной кодовой базе. Компоненты, которые выглядят как достойные кандидаты на извлечение из монолита, перерабатываются в обобщённом виде, на их основе создаются надёжные, лишённые зависимостей веб-компоненты, реализующие ограниченные модели поведения.

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

Catalyst и начало жизненного цикла компонента


Мы рекомендуем разработчикам создавать Catalyst-компоненты при работе над пользовательским интерфейсом системы в рамках монолитной кодовой базы. Плюсы использования Catalyst с самого начала работы над компонентами заключаются в том, что эта библиотека позволяет обойти распространённые проблемы, связанные с созданием веб-компонентов, и при этом подталкивает программистов к применению рекомендованных приёмов разработки.

Для регистрации веб-компонента может понадобиться некоторый объём шаблонного кода, но мы упрощаем эту задачу за счёт применения особого соглашения об именовании сущностей и благодаря наличию некоторого количества TypeScript-декораторов. То, что в Catalyst называется Actions, позволяет решить задачу организации прослушивания событий проще, чем она решается через поддержку глобальных прослушивателей событий. Внесение изменений в то, что в Catalyst называется Targets, в применении к существующему HTML-коду, лучше, чем рендеринг HTML-шаблонов в браузере.

Извлечение компонента из монолита


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

Прежде чем извлекать из монолита Catalyst-компонент, мы убираем из него весь функционал, имеющий отношение к Catalyst, и преобразуем его в обычный веб-компонент. Зачем отключать от него библиотеку, которая упрощает задачу создания веб-компонентов? Дело в том, что хотя использование Catalyst и даёт разработчикам много хорошего, нам нужно, чтобы у наших компонентов не было бы зависимостей. Мы не хотим, чтобы разработчики, не являющиеся сотрудниками GitHub, вынуждены были бы, прежде чем сделать вклад в код какого-нибудь компонента, изучать Catalyst. Нам не нужны лишние сложности.

Реализация особых требований, предъявляемых к опенсорсным компонентам


Компоненты, входящие в состав монолита, могут быть тесно связаны с логикой конкретного приложения. У них могут быть зависимости, проверять их можно существующими тестами. А к опенсорсным компонентам мы предъявляем совсем другие требования. Количество зависимостей наших опенсорсных компонентов должно быть близко к 0, они не должны быть ориентированы на применение лишь в рамках некоей библиотеки или какого-то фреймворка, они должны быть нетребовательными к ресурсам, нестилизованными, отделёнными от любых других компонентов. Они должны решать лишь одну задачу и решать её они должны хорошо.

Хотя мы и стремимся к тому, чтобы наши компоненты были бы лишены зависимостей, нам нужно, чтобы они были такими же надёжными, как и компоненты, входящие в состав монолита. Сюда входит и надёжная работа с типами данных. Мы включаем в состав компонентов TypeScript-определения типов, оформляем код в виде ES-модулей, что облегчает использование наших компонентов бандлерами. Мы, кроме того, снабжаем компоненты полноценными наборами тестов и настройками линтера, используя наши стандартизированные конфигурации.

Отличным примером компонента, прошедшего все стадии опенсорсного жизненного цикла, является элемент <typing-effect>. Одна из команд GitHub недавно создала прототип элемента интерфейса, напоминающего терминал, текст в котором появлялся так, будто кто-то набирает этот текст на клавиатуре. Ранее мы пользовались весьма качественной и надёжной библиотекой typed.js, с помощью которой добивались анимированного эффекта ввода текста с клавиатуры. Изначально команда решила снова обратиться к этой библиотеке.

Те части интерфейса, которые уже были созданы, имели достаточно скромные размеры. А после анализа последствий подключения к странице typed.js, оказалось, что это увеличит размер бандла в пять раз. Команда инженеров, отвечающих за интерфейс, обратилась к команде, создавшей прототип элемента для анимированного вывода текста, с предложением рассмотреть какой-нибудь другой вариант реализации этого элемента. Оказалось, что необходимый нам функционал достаточно ограничен для того, чтобы стоило бы подумать о его реализации собственными силами.

Тогда была создана первая версия нашей собственной анимации ввода текста, предназначенная для нового интерфейса. Благодаря использованию Catalyst на это ушло меньше дня, а объём кода не превышал 40 строк. Поняв, что использование этого эффекта может заинтересовать и другие команды, мы решили вывести соответствующий элемент в опенсорс. Мы отрефакторили код компонента, избавив его от всех зависимостей, извлекли его из библиотеки Catalyst и сделали опенсорсным, назвав <typing-effect>.

Результаты


В целом мы в восторге от тех изменений, которые мы внесли во фронтенд GitHub с момента предыдущей публикации на схожую тему. В соответствии с проведённым нами внутренним опросом разработчиков, оказалось, что им приятно работать с Catalyst и ViewComponent!

Разработчикам нравится инкапсуляция ViewComponent, облегчение тестирования интерфейса и то, что разработчики теперь обладают большей уверенностью в качестве того, что они создают. Программисты воспринимают применение Catalyst как положительное изменение на пути от старого JavaScript-подхода к новым методам работы, которое не потребовало серьёзного переключения на новый фреймворк или перехода к новой парадигме разработки.

Что дальше?


Мы продолжаем выводить в опенсорс всё больше универсальных веб-компонентов, реализующих различные модели поведения. Всё это называется GitHub Elements. Коллекцию этих элементов можно найти в этом репозитории, она согласована с нашей страницей на webcomponents.org.

Мы с интересом смотрим в будущее веб-компонентов и наблюдаем за предложениями по изменениям спецификаций HTML. Нам сейчас особенно интересны два таких предложения Template Parts и Declarative Shadow DOM. Принятие этих предложений ещё сильнее облегчит процесс создания веб-компонентов и решит некоторые распространённые проблемы, касающиеся веб-компонентов, с которыми нам приходится бороться. Мы создали понифилл, реализующий минимальный работоспособный функционал предложения Template Parts. Он уже используем его в продакшне, и мы искренне заинтересованы в том, чтобы он нашёл бы применение в более широких кругах веб-разработчиков.

Пользуетесь ли вы веб-компонентами в своих проектах?


Подробнее..

Кто есть кто в кампании за отмену Столлмана

27.04.2021 20:11:49 | Автор: admin

Кампания "за отмену Столлмана", начавшаяся с публикации в Medium предоставляет нам множество интересных данных. Так как подписание открытых писем за отмену и в поддержку Столлмана осуществляется на гитхабе, мы можем проанализировать некоторые характеристики обеих сторон, используя статистические данные, которые доступны через API.

Этому помогает то, что на гитхабе затруднительно редактировать данные "задним числом" без потери новых подписей.

Следующие предположения можно проверить ("X" может быть как предложением отменить Столлмана, так и выражением его поддержки):

  • Противники X чаще ассоциированы с крупными компаниями чем сторонники

  • Сторонники X чаще и больше коммитят код и этим более полезны сообществу СПО.

  • Противники X значимо реже коммитят в репозитории со свободными лицензиями.

  • Противники X предпочитают Rust (или JS), сторонники предпочитают C (или C++, Python)

  • Противники X в большей степени социально активны, у них есть аккаунты в соц. сетях, твиттере, они часто пишут.

  • Противники X не коммитят код по выходным (работают только в рабочее время, не энтузиасты)

  • Большинство противников X зарегистрированы на гитхабе менее полугода назад

Мы попытались проверить некоторые из этих предположений и приглашаем всех, кто заинтересовался, проверить остальные предположения и вносить (и проверять) любые другие.

Мы создали репозиторий, в котором будет проходить работа. В нем же лежит эта статья, ее копия на Хабре будет актуализироваться по мере добавления пулл-реквестов. Присоединяйтесь к исследованию!

Далее будут детали.

Замечание о научной честности

Любые гипотезы и любые проверяемые подтверждения будут приняты и добавлены в статью. Мы не считаем возможным скрывать данные, которые противоречат нашей позиции. Все интерпретации будут также добавлены. Мы приглашаем к совместной работе сторонников обоих позиций (да, это возможно). Репозиторий для совместной работы.

Кампания за отмену Столлмана управляется из одного центра

Репозиторий противников Столлмана был создан 23 Mar 2021 10:42:36 AM PDT, сторонников - 23 Mar 2021 01:23:39 PM PDT. Видно, что репозиторий противников практически сразу начал активно набирать звезды. У репозитория сторонников был длительный период, когда звезды набирались медленно, но потом (видимо после публикации в соц сетях) процесс пошел много быстрее и количество звезд быстро обогнало противников.

Код
$ cat get-stars.sh#!/bin/bashset -uepage=1owner_repo=$1while true; do    curl -s -H "Authorization: token $GITHUB_OAUTH_" \\        -H "Accept: application/vnd.github.v3.star+json" \\        "<https://api.github.com/repos/$owner_repo/stargazers?per_page=100&page=$page>"| \\        jq -r .[].starred_at_ | grep . || break    ((page++)) || truedone$ echo "epoch,con" >con.stars.csv$ ./get-stars.sh 'rms-open-letter/rms-open-letter.github.io'|while read a; do date -d $a +%s; done|sort -n|cat -n|awk '{print $2","$1}' >>con.stars.csv$ echo "epoch,pro" >pro.stars.csv$ ./get-stars.sh 'rms-support-letter/rms-support-letter.github.io'|while read a; do date -d $a +%s; done|sort -n|cat -n|awk '{print $2","$1}' >>pro.stars.csv$ join -t, -e '' -o auto -a1 -a2 con.stars.csv pro.stars.csv >joined.stars.csv

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

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

Активность в репозиториях сторонников и противников Столлмана

На момент написания этой статьи было 1345 комиттеров противников и 5000+ коммиттеров сторонников. Скачиваем историю коммитов:

Код
$ cat get-commits.py#!/usr/bin/env pythonimport osimport requestsimport jsonimport sysrepo = sys.argv[1]headers = {'Authorization': 'token {}'.format(os.environ["GITHUB_OAUTH"])}commits = []page = 0while page < 300:    page += 1    data = requests.get('https://api.github.com/repos/{}/commits?per_page=100&page={}'.format(repo, page), headers=headers).json()    if len(data) == 0:        break    commits += dataprint(json.dumps(commits, indent=4))$ ./get-commits.py 'rms-open-letter/rms-open-letter.github.io' >con.commits.json$ ./get-commits.py 'rms-support-letter/rms-support-letter.github.io' >pro.commits.json

Посмотрим на изменение количества коммитов от времени с начала кампаний:

Код
$ jq -r .[].commit.author.date pro.commits.json|sort -u|cat -n|awk '{print $2","$1}'|sed -e 's/T/ *' -e 's/Z/*' >pro.commits.csv$ jq -r .[].commit.author.date con.commits.json|sort -u|cat -n|awk '{print $2","$1}'|sed -e 's/T/ *' -e 's/Z/*' >con.commits.csv$ join -t, -e '' -o auto -a1 -a2 con.commits.csv pro.commits.csv >joined.commits.csv

Видно, что репозиторий сторонников гораздо активнее. Коммитов в репозиторий противников за последнее время практически не было. Репозиторий сторонников продолжает обновляться.

Противники Столлмана ведут кампанию в основном в рабочие дни

Посмотрим на распределение коммитов по дням недели.

Код
$ jq -r .[].commit.author.date con.commits.json |./weekday-from-date.py >con.rms_commits.csv$ jq -r .[].commit.author.date pro.commits.json |./weekday-from-date.py >pro.rms_commits.csv$ join -t, con.rms_commits.csv pro.rms_commits.csv >joined.rms_commits.csv

Aктивность противников Столлмана сильно снижается на выходных, зато в среду мы видим пик. Это можно объяснить тем, что во многих компаниях среда это no meeting day.

Активность сторонников значительно менее вариативная. Коммиты совершаются во все дни недели

Противники Столлмана чаще имеют заполненные профили социальных сетей

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

Код
$ jq -r .[].author.login con.commits.json|sort -u >con.logins$ jq -r .[].author.login pro.commits.json|sort -u >pro.logins$ cat get-user-events-data.sh#!/bin/bashset -uescript_dir=$(dirname $(realpath $0))get_data() {    local data_dir=$script_dir/$1 userdata events    for x in $(cat $1.logins); do        userdata=$data_dir/$x.userdata        [ -r $userdata ] && continue        curl -s -H "Authorization: token $GITHUB_OAUTH" "<https://api.github.com/users/$x>" >$userdata        sleep 1        events=$data_dir/$x.events        [ -r $events ] && continue        curl -s -H "Authorization: token $GITHUB_OAUTH" "<https://api.github.com/users/$x/events?per_page=100>" >$events        sleep 1    done}get_data $1$ ./get-user-events-data.sh con$ ./get-user-events-data.sh pro

Пример данных юзера, выгруженных из гитхаба:

Код
{  "login": "zyxw59",  "id": 3157093,  "node_id": "MDQ6VXNlcjMxNTcwOTM=",  "avatar_url": "https://avatars.githubusercontent.com/u/3157093?v=4",  "gravatar_id": "",  "url": "https://api.github.com/users/zyxw59",  "html_url": "https://github.com/zyxw59",  "followers_url": "https://api.github.com/users/zyxw59/followers",  "following_url": "https://api.github.com/users/zyxw59/following{/other_user}",  "gists_url": "https://api.github.com/users/zyxw59/gists{/gist_id}",  "starred_url": "https://api.github.com/users/zyxw59/starred{/owner}{/repo}",  "subscriptions_url": "https://api.github.com/users/zyxw59/subscriptions",  "organizations_url": "https://api.github.com/users/zyxw59/orgs",  "repos_url": "https://api.github.com/users/zyxw59/repos",  "events_url": "https://api.github.com/users/zyxw59/events{/privacy}",  "received_events_url": "https://api.github.com/users/zyxw59/received_events",  "type": "User",  "site_admin": false,  "name": "Emily Crandall Fleischman",  "company": "Commure",  "blog": "",  "location": null,  "email": "emilycf@mit.edu",  "hireable": null,  "bio": null,  "twitter_username": null,  "public_repos": 24,  "public_gists": 0,  "followers": 2,  "following": 12,  "created_at": "2012-12-31T05:33:30Z",  "updated_at": "2021-03-14T01:53:51Z"}

В таблице ниже приводится процент пользователей, у которых заполнены поля twitter_username, company, bio и blog:

поле

противник

сторонник

twitter_username

31%

8%

company

48%

20%

bio

53%

31%

blog

63%

31%

Противники Столлмана кажутся более социально активными. Возможно сторонники Столлмана реже участвуют в социальной активности (ведут блог, твиттер, страничку в социальных сетях.

Противники Столлмана активнее на гитхабе

Посмотрим на поля public_repos, public_gists, followers и following:

поле

противник

стронник

среднее

медиана

среднее

медиана

public_repos

62

34

21

9

public_gists

18

4

4

0

followers

105

23

16

2

following

30

8

14

1

Противники Столлмана активнее сторонников на гитхабе. У них в среднем больше followers, публичных репозиториев, они также чаще фолловят другие репозитории. Также у противников соотношение followers / following больше 3, в то время как у сторонников оно составляет 1.1.

Противники Столлмана не коммитят в гитхаб на выходных

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

Теперь давайти посмотрим на действия юзеров. Данных скачано много и анализировать их можно множеством способов. Можно проверить активность юзеров по дням недели, чтобы проверить как эти данные коррелируют с активностью, специфичной для репозиториев "за" и "против" Столлмана.

Код
cat weekday-from-date.py#!/usr/bin/env python                                                                                                                                                                                                                                                                                          import datetime                                                                                                                                         import sys                                                                                                                                                                                                                                                                                                      out = [0] \* 7                                                                                                                                          total = 0                                                                                                                                                                                                                                                                                                       for line in sys.stdin.readlines():                                                                                                                          weekday = datetime.datetime.strptime(line.strip(), '%Y-%m-%dT%H:%M:%SZ').weekday()                                                                      out[weekday] += 1                                                                                                                                       total += 1                                                                                                                                                                                                                                                                                                  for day, count in enumerate(out):                                                                                                                           print("{},{}".format(day, count / total))                                                                                                                                                                                                                                                                   $ jq -r .[].created<sub>at</sub> con/\*.events|./weekday-from-date.py >con.event<sub>day.normalized.csv</sub>                                             $ jq -r .[].created<sub>at</sub> pro/\*.events|./weekday-from-date.py >pro.event<sub>day.normalized.csv</sub>                                             $ join -t, con.event<sub>day.normalized.csv</sub> pro.event<sub>day.normalized.csv</sub> 

Видно, что тренд сохранился: активность противников резко снижается на выходных. Можно предполагать, что они используют гитхаб на работе и, возможно, работают над open source проектами за зарплату. Если это предположение верно, их мнение может быть обусловлено отбором, который проводят компании, нанимающие программистов для работы над open source проектами.

Подробнее..

Перевод Как мы устранили редкую ошибку, из-за которой пришлось разлогинить всех пользователей Github

24.03.2021 10:15:26 | Автор: admin

8 марта мы рассказали о том, что из соображений безопасности разлогинили всех пользователей GitHub.com из-за редкой уязвимости защиты. Мы считаем, что прозрачность ключевой фактор сохранения доверия со стороны пользователей, поэтому решили рассказать об этом баге подробнее. В этом посте мы изложим технические детали данной уязвимости и объясним, как она возникла, как мы на неё отреагировали и какие мы предприняли шаги для того, чтобы подобное не повторилось.

Отчёты пользователей


2 марта 2021 через службу технической поддержки мы получили отчёт пользователя, который войдя на GitHub.com под собственными идентификационными данными, внезапно авторизировался как другой пользователь. Он немедленно вышел из аккаунта, но сообщил о проблеме нам, поскольку она обеспокоила его, и на то были все основания.

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

Исследование недавних изменений в инфраструктуре


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

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

Исследование недавних изменений в коде


Благодаря тому, что мы начали расследование с недавних изменений в инфраструктуре, нам удалось выяснить, что запросы, приводившие к возврату неправильной сессии, обрабатывались на одной и той же машине и в одном процессе. Мы используем многопроцессную схему с веб-сервером Unicorn Rack, выполняющим наше основное Rails-приложение. обрабатывающее такие задачи, как отображение issues и пул-реквестов.

После изучения логов мы выяснили, что тело HTTP-объекта в передаваемом нами ответе клиенту было правильным, ошибочным были только куки в ответе пользователю. Отправившие отчёты об ошибке пользователи получили куки сессии пользователя, запрос которого совсем незадолго до них обрабатывался внутри того же процесса. В одном из случаев два запроса обрабатывались последовательно, один за другим. Во втором случае между ними было ещё два запроса.

У нас появилась рабочая теория о том, что нечто каким-то образом приводило к утечке состояния между запросами, которые обрабатывались в одном процессе Ruby. Нам нужно было выяснить, как такое может происходить.

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

Безопасность потоков и отчёты об ошибках


Для понимания проблемы с безопасностью потоков необходим контекст. Основное приложение, обрабатывающее большинство браузерных взаимодействий на GitHub.com это приложение Ruby on Rails, известное тем, что оно имело компоненты, написанные без учёта возможности в нескольких потоках (т.е. они были непотокобезопасны). Обычно в прошлом непотокобезопасное поведение могло приводить к неправильному значению во внутренних отчётах об исключениях системы, но при этом пользователи не сталкивались с изменением поведения системы.

Потоки уже использовались в других частях этого приложения, однако новый фоновый поток создал новое и непредусмотренное взаимодействие с нашими процедурами обработки исключений. Когда исключения (например, истечение срока запроса) передавал фоновый поток, лог ошибок содержал информацию и от фонового потока, и от текущего выполняемого запроса, и это доказывало, что данные передаются между потоками.

Изначально мы думали, что это только внутренняя проблема отчётности, и что мы будем наблюдать в логах данные, зафиксированные от не относящегося к делу запроса в фоновом потоке. Несмотря на противоречивость, мы посчитали такое поведение безопасным, поскольку каждый запрос имел собственные данные запроса, а Rails-приложение создавало новый экземпляр объекта-контроллера для каждого запроса. Нам по-прежнему было непонятно, как это могло вызывать проблемы, с которыми мы столкнулись.

Многократно используемый объект


Наша команда совершила прорыв, обнаружив, что HTTP-сервер Unicorn Rack, используемый в нашем Rails-приложении, не создаёт новый и отдельный объект env для каждого запроса. Вместо этого он выделяет единственный Ruby Hash, который очищается (с помощью Hash#clear) между запросами, которые он обрабатывает. Благодаря этому мы поняли, что проблема с потокобезопасностью в логгинге исключений может привести не только к неправильности фиксируемых в исключениях данных, но и к передаче данных запросов на GitHub.com.

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


  1. В потоке обработки запросов запускается анонимный запрос (назовём его Request #1). Он регистрирует обратный вызов в текущем контексте для внутренней библиотеки отчётности об исключениях. Обратные вызовы содержат ссылки на текущий объект контроллера Rails, имеющий доступ к единому объекту среды запроса Rack, предоставляемого сервером Unicorn.
  2. В фоновом потоке возникает исключение. Сообщение об исключении копирует текущий контекст, чтобы включить его в отчёт. Этот контекст содержит обратные вызовы, зарегистрированные запросом Request #1, в том числе и ссылку на единую среду Rack.
  3. В основном потоке запускается новый запрос залогиненного пользователя (Request #2).
  4. В фоновом потоке система отчётности об исключениях обрабатывает обратные вызовы контекста. Один из обратных вызовов считывает идентификатор сессии пользователя, но поскольку запрос на момент контекста не имеет авторизации, эти данные ещё не считываются, и, следовательно, запускают новый вызов к системе авторизации через контроллер Rails из запроса Request #1. Этот контроллер пытается выполнить авторизацию и получает куки сессии из общей среды Rack. Так как среда Rack это общий объект для всех запросов, контроллер находит куки сессии запроса Request #2.
  5. В основном потоке запрос Request #2 завершается.
  6. Запускается ещё один запрос залогиненного пользователя (Request #3). В этот момент Request #3 завершает свой этап авторизации.
  7. В фоновом потоке контроллер завершает этап авторизации, записывая куки сессии в cookie jar, находящийся в среде Rack. На данном этапе это cookie jar для Request #3!
  8. Пользователь получает ответ на запрос Request #3. Но cookie jar был обновлён данными куки сессии Request #2, то есть пользователь теперь авторизован как пользователь из Request #2.

Подведём итог: если исключение возникло именно в нужный момент и если параллельная обработка запросов происходит именно в нужном порядке среди серии запросов, то в результате сессия одного запроса заменяется сессией более раннего запроса. Возврат неправильного куки происходит только для заголовка куки сессии, и, как говорилось выше, остальная часть тела ответа, например HTML, по-прежнему была связана с ранее авторизованным пользователем. Это поведение соответствовало с тем, что мы видели в логах запросов и мы смогли чётко идентифицировать все элементы, составлявшие первопричину состояния гонки.

Для возникновения этого бага требовались очень конкретные условия: фоновый поток, общий контекст исключения между основным потоком и фоновым потоком, обратные вызовы в контексте исключения, многократное использование объекта env между запросами и наша система авторизации. Подобная сложность является напоминанием о многих из пунктов, представленных в статье How Complex Systems Fail, и демонстрирует, что для возникновения подобного бага требуется множество различных сбоев.

Предпринимаем действия


Выявив первопричину, мы немедленно сделали основным приоритетом устранение двух условий, критически важных для появления этого бага. Во-первых, мы избавились от нового фонового потока, появившегося после вышеупомянутой модернизации архитектуры, призванной повысить производительность. Благодаря тому, что мы точно знали, что было добавлено в процессе этой работы, изменения легко можно было откатить. Изменение, в котором был удалён этот поток, вывели в продакшен 5 марта. После этого изменения мы знали, что условия, требуемые для возникновения условия гонки, больше не будут удовлетворяться, и что наша непосредственный риск возврата неверных сессий снижен.

После выпуска изменения с удалением фонового потока, мы создали патч для Unicorn, удаляющий возможность создания общей среды. Этот патч был выпущен 8 марта и ещё больше усилил изоляцию между запросами, даже на случай возникновения проблем с потокобезопасностью.

Кроме устранения бага мы предприняли действия для определения пользовательских сессий, на которые он повлиял. Мы изучили данные логгинга на предмет паттернов, соответствующих неправильно возвращаемых сессий. Затем мы вручную просмотрели соответствующие паттерну логи, чтобы определить, на самом ли деле сессия была неверно возвращена от одного пользователя другому.

В конце мы решили сделать ещё одно превентивное действие, чтобы гарантировать безопасность данных наших пользователей, и аннулировали все активные сессии пользователей на GitHub.com. Учитывая редкость условий, необходимых для возникновения этого условия гонки, мы знали, что вероятность возникновения бага очень мала. Хотя наш анализ логов, проведённый с 5 по 8 марта, подтвердил, что это была редкая проблема, мы не могли исключить вероятность того, что сессия была неверно возвращена, но никогда не использовалась. Мы не хотели идти на такой риск, учитывая потенциальный ущерб использования даже одной из таких неверно возвращённых сессий.

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

Продолжаем работу


Несмотря на устранение непосредственной угрозы, мы объединили усилия с мейнтейнером Unicorn, чтобы поднять изменения вверх по потоку и чтобы новые запросы выделяли собственные хэши среды. Если Unicorn будет использовать новые хэши для каждой среды, то это устранить возможность того, что один запрос ошибочно завладеет объектом, который может повлиять на следующий запрос. Это дополнительная защита, позволяющая предотвратить превращение подобных багов потокобезопасности в уязвимости защиты для других пользователей Unicorn. Хотим искренне поблагодарить мейнтейнеров Unicorn за сотрудничество и обсуждение данной проблемы.

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

Также мы хотим повысить потокобезопасность всей кодовой базы в таких областях, как обработка исключений и оснащение. Мы не хотим просто устранять конкретные баги, а стремимся обеспечить невозможность возникновения подобного класса проблем в будущем.

Подведём итог


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

Если взглянуть со стороны, то данный баг не только оказался сложным с технической точки зрения (ведь для его возникновения требуются сложные взаимодействия между несколькими потоками, отложенными обратными вызовами и совместным использованием объектов), но и стал проверкой способности нашей организации реагировать на проблему с неизвестной причиной и уровнем угрозы. Отлаженная работа команды реагирования на инциденты с безопасностью совместно со специалистами из отделов поддержки, безопасности и разработки позволила нам быстро исследовать, оценить и квалифицировать потенциальную опасность этой проблемы, а также повысить приоритет её решения во всей компании. Это повышение приоритета ускорило работу над анализом логов, изучением последних изменений в коде и архитектуре, а в конечном итоге позволило выявить глубинные проблемы, которые привели к возникновению этого бага.

Благодаря чёткому пониманию проблемы мы смогли сосредоточиться на принятии нужных мер для ограничения её воздействия на пользователей. Полученные нами в процессе этой работы знания позволили нам обсудить ситуацию с ответственными лицами из разных отделов компании и принять конфиденциальное решение о необходимости выхода из системы всех пользователей GitHub.com. Мы лучше разобрались в сложных системах компании и воспользуемся этой возможностью для создания мер предосторожности, предотвращающих возникновение подобных проблем в будущем.
Подробнее..

Перевод Как хакнуть Github и заработать 35000?

12.04.2021 18:13:34 | Автор: admin

Когда я нашёл эту уязвимость и сообщил о ней, она стала моим первым оплаченным баг-репортом на HackerOne. $35,000 это также самая высокая награда, которую я получил от HackerOne (и я считаю, что самая высокая оплата от GitHub на сегодня). Многие найденные ошибки, кажется, это удача и интуиция, вместе взятые. В этом посте я расскажу, как мыслил, приближаясь к цели.


Как началась история

Ковид ударил весной, в первый год старшей школы. От нечего делать между онлайн-занятиями я начал охоту за багами. Конкретно эта награда была за сообщение об уязвимости приватных страниц Github в рамках программы Баг Баунти. В частности, было и два бонуса CTF (capture the flag состязание по типу захвата флага, здесь в информационной безопасности):

  • $10000 чтение флага flag.private-org.github.io без взаимодействия с пользователем. Бонус в $5000 за то, что флаг читается с аккаунта внутри приватной организации.

  • $5000: чтение флага flag.private-org.github.io через взаимодействие с пользователем.

Поток аутентификации

Страницы GitHub размещаются в отдельном домене github.io, поэтому куки-файлы аутентификации github.com не отправляются на сервер приватных страниц. Таким образом, аутентификация частной страницы не имеет возможности определить личность пользователя без дополнительной интеграции с github.com, поэтому GitHub создал собственный поток аутентификации. И ввёл возможность существования багов. На момент отчёта этот поток выглядел так:

А теперь подробнее.

При посещении приватной страницы сервер проверяет, существует ли файл куки __Host-gh_pages_token. Если этот файл не установлен или установлен неправильно, сервер приватной страницы перенаправит на https://github.com/login. Этот начальный редирект также устанавливает nonce, который хранится в куки __Host-gh_pages_session.

Обратите внимание, что этот куки использует префикс куки __Host-, который, в теории, в качестве дополнительной защиты в глубину, предотвращает его установку из JS с родительского домена.

/login перенаправляет на /pages/auth?nonce=&page_id=&path=. Затем эта конечная точка генерирует временный куки-файл аутентификации, который она передаёт https://pages-auth.github.com/redirect в параметре token; nonce, page_id, и path присылаются аналогично.

/redirect просто перенаправляет на https://repo.org.github.io/__/auth. Эта последняя конечная точка затем устанавливает куки аутентификации для домена repo.org.github.io, __Host-gh_pages_token и __Host-gh_pages_id. Также вместе с ранее установленным __Host-gh_pages_session на валидность проверяется nonce.

На протяжении всего потока аутентификации такая информация, как исходный путь запроса и идентификатор страницы, хранится в параметрах запроса path и page_id соответственно. Nonce также передаётся в параметре nonce. Хотя поток аутентификации, возможно, немного изменился (отчасти из-за этого отчёта), общая идея остаётся прежней.

Эксплоит

CRLF возвращается

Первая уязвимость CRLF-инъекция в параметре page_id запроса https://repo.org.github.io/__/auth.

Возможно, лучший способ найти уязвимые места поиграть. Исследуя поток аутентификации, я заметил, что парсинг page_id, похоже, игнорировал пробельные символы. Интересно, что он также рендерил параметр непосредственно в заголовок Set-Cookie. К примеру, page_id=12345%20 вернул это:

Set-Cookie: __Host-gh_pages_id=12345 ; Secure; HttpOnly; path=/

Псевдокод предположительно такой:

page_id = query.page_iddo_page_lookup(to_int(page_id))set_page_id_cookie(page_id)

Другими словами, page_id преобразуется в целое число, а также отображается напрямую в заголовок Set-Cookie. Проблема была в том, что мы не можем отобразить текст напрямую. Несмотря на то, что у нас была классическая CRLF-инъекция, размещение любых непробельных символов привело к поломке парсинга целого. Мы могли бы прервать поток аутентификации, отправив page_id=12345%0d%0a%0d%0a, но, кроме интересного ответа, никакого немедленного воздействия не было.

; Secure; HttpOnly; path=/Cache-Control: privateLocation: https://83e02b43.near-dimension.github.io/X-GLB-L

Дополнительное примечание: заголовок Location: был добавлен после заголовка Set-Cookie, поэтому наш ответ Location выталкивает за пределы отправленных HTTP-заголовков. Это редирект на 302, но, несмотря на это, заголовок Location игнорируется и отображается содержимое тела.

Из грязи в князи

Немного просмотрев на GitHub Enterprise (который дал доступ к исходному коду), я заподозрил, что сервер приватных страниц реализован на Nginx OpenResty. Будучи относительно низкоуровневым, возможно, он имел проблемы с нулевыми байтами. А попытка не пытка, правильно?

Оказалось, что добавление нулевого байта прерывает парсинг целого. Другими словами, можно передать такую полезную нагрузку:

"?page_id=" + encodeURIComponent("\r\n\r\n\x00<script>alert(origin)</script>")

А вот и XSS!

Обратите внимание, что, если в заголовке есть нулевой байт, ответ будет отклонён. Таким образом, нулевой байт должен прийти в начале тела (это означает, что мы не можем выполнить атаку через инъекцию в заголовок).

Мы добились выполнения произвольного JavaScript на странице приватного домена. Единственная проблема нужен способ обойти nonce. Параметры page_id и path известны, а nonce не позволяет нам отправлять жертв вниз по потоку аутентификации с отравленным page_id. Нам нужно либо зафиксировать, либо спрогнозировать nonce.

Обходим nonce

Первое наблюдение поддомены одной организации могут устанавливать куки-файлы друг на друга. Так происходит потому, что *.github.io нет в публичном списке суффиксов. Таким образом установленные на private-org.github.io куки передаются на private-page.private-org.github.io.

Nonce обойти будет легко, если мы как-то сможем обойти защиту префикса __Host-. Просто установим фальшивый nonce на страницу поддомена того же уровня, значение будет передано ниже. К счастью, этот префикс не принудителен во всех браузерах...

Ну, ладно... неправильно говорить не во всех. Похоже, в смысле этого обхода уязвим только IE. Придётся постараться. А что, если атаковать сам nonce? генерация выглядит надёжной, и, если честно, криптография не моя сильная сторона. Найти обход применяемой в nonce энтропии, несмотря ни на что, это казалось маловероятным. Как же тогда зафиксировать nonce?

Вернёмся к истокам RFC. В конце концов, я наткнулся на интересную идею как нормализовать куки? В частности, как следует обращаться с куки с учётом верхнего регистра. "__HOST-" это то же самое, что "__Host-"? Легко убедиться, что в браузерах с куками разного регистра обращаются по-разному:

document.cookie = "__HOST-Test=1"; // worksdocument.cookie = "__Host-Test=1"; // fails

Оказывается, сервер приватных страниц GitHub при разборе куки-файлов игнорирует заглавные буквы. И у нас есть префиксный обход. А теперь собираем доказательство концепции атаки XSS!

<script>const id = location.search.substring("?id=".length)document.cookie = "__HOST-gh_pages_session=dea8c624-468f-4c5b-a4e6-9a32fe6b9b15; domain=.private-org.github.io";location = "https://github.com/pages/auth?nonce=dea8c624-468f-4c5b-a4e6-9a32fe6b9b15&page_id=" + id + "%0d%0a%0d%0a%00<script>alert(origin)%3c%2fscript>&path=Lw";</script>

Уже этого достаточно, чтобы заработать бонус в $5000. Но я хотел посмотреть, получится ли продвинуться дальше.

Отравление кэша

И вот ещё один недостаток дизайна выяснилось, что в ответ на конечную точку /__/auth? кэшировалось только целое значение page_id. Само по себе это безвредно: установленный этой конечной точкой токен скопирован на личную страницу и не имеет никаких других привилегий.

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

Независимо от этого, такое кэширование лёгкий способ усугубить атаку. Поскольку он делается на разобранном целочисленном значении, успешная атака на кэш с полезной нагрузкой XSS может повлиять на других пользователей, которые даже не взаимодействовали с вредоносной нагрузкой, как показано ниже:

Злоумышленник контролирует unprivileged.org.github.io и хочет добраться до privileged.org.github.io. Он атакует поток аутентификации unprivileged.org.github.io и полезная нагрузка XSS кэшируется.

Когда привилегированный пользователь посещает unprivileged.org.github.io, он подвергается XSS атаке на домен unprivileged.org.github.io. Куки могут устанавливаться на общем родительском домене org.github.io, поэтому теперь злоумышленник может атаковать privileged.org.github.io.

Эти действия позволяют любому злоумышленнику с разрешениями на чтение приватной страницы постоянно атаковать поток аутентификации этой страницы. Ё-моё!

Публичные и приватные страницы

Чтобы получить бонус 15000 долларов, нужно выполнить эту атаку с учётной записи пользователя, которого в организации нет. И нам везёт: мы можем злоупотребить тем, что кажется ещё одной ошибкой конфигурации разделением страниц на публичные и приватные.

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

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

В комбинации с изложенным выше получается, что непривилегированный пользователь вне организации может с опорой на приватно-публичную страницу скомпрометировать потоки аутентификации внутренних приватных страниц.

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

Так мы заработали максимальный бонус CTF. Устойчивость достигается отравлением кэша или другим техниками, которые мы оставим читателям в качестве упражнения.

Заключение

Этой уязвимости присвоили высокий приоритет, базовая выплата составила 20 000 долларов, а с бонусом CTF мы заработали 35 000 долларов. Довольно круто, что такая относительно малоизвестная уязвимость, как инъекция CRLF, обнаружилась на GitHub. Хотя большая часть кода написана на Ruby, некоторые компоненты, такие как аутентификация приватной страницы, написаны не на этом языке и могут быть уязвимы к низкоуровневым атакам. В общем, везде, где есть сложные взаимодействия, ошибки только ждут, чтобы мы их нашли.

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

Узнайте, как прокачаться и в других специальностях или освоить их с нуля:

Другие профессии и курсы
Подробнее..

Приглашаем вас на апрельскую виртуальную встречу ГитХаба на тему безопасности

14.04.2021 20:10:55 | Автор: admin

Наша встреча пройдет во вторник, 20 апреля, с 19:00 до 20:00 по Московскому времени. Наши ведущие Хабберы расскажут о новинках ГитХаба.

Алена Глобина (Продакт Менеджер в GitHub:Code scanning CodeQL) расскажет про Анализ безопасности кода с помощью CodeQL.

Артём Савельев (разработчик в GitHub:SecretScanning) расскажет о превентивной безопасности в цикле разработки.

В конце презентации наши ведущие и докладчики присоединятся к дискуссии в комментариях к YouTube каналу. У вас будет возможность познакомиться c ними, задать им вопросы, обсудить общие интересы русскоговорящего сообщества разработчиков и может быть выиграть подарки от ГитХаба.

До встречи 20 апреля в 19:00 по Московскому времени наhttps://youtube.com/github

Подробнее..

Google Sheets как разноплановый помощник для непростых задач или как я делал анализатор футбольный матчей

17.04.2021 14:23:59 | Автор: admin

Лежу я ночью, пытаюсь уснуть. И как обычно тысяча мыслей, и среди них я сумел зацепился за одну. А звучала она так: "почему бы не сделать анализатора футбольных матчей, где нужно будет лишь ввести участников игры и получить выборку из их статистики общей и какие-то описание, чего ждать в грядущем матче". Действительно, почему нет?!

На следующий день, когда я проснулся, я первым же делом пошел гуглить, есть ли такое сейчас, потому что что-то подсказывало, что в нашем мире это слишком банальная затея (та ладна!?). Но я то не просто хотел складывать и делить числа из статистики, мне хотелось учитывать различные факторы спортивного мероприятия, а их большое количество.

Гугл дал свой результат, впрочем как всегда. Я нашел кучу калькуляторов ставок, которые продается за 3-5к рублей, и прочие таблицы расчетов в свободном доступе. Я как бы и так помнил расчеты тоталов голов, но мне нужно было их улучшить и получить на выходе собственно целого "мага/колдуна/вангу" спортивных событий. Или хотя бы формулку, которая выдаст результат после ввода данных.

Это что, писать парсер?!

Мне не хотелось сильно углубляться в код. Во-первых, я не кодер, а скорее человек, который с ним постоянно сталкивается в работе, и совсем чуть-чуть в нем может разобраться. Во-вторых, мне просто было лень, я искал простые решения. И вспомнил, что чудо Google Sheets может парсить таблички, xml, html-страницы, и делается это прост формулами: IMPORTDATA, IMPORTFEED, IMPORTHTML, IMPORTXML. Вот ссылка на справку гугла, там все подробно описано, останавливаться на этом я пожалуй не буду.

Нашел источник футбольной статистики, что было очень сложно. Ведь мне нужно не только спарсить разок, а обновлять мои данные постоянно, поскольку футбольные матчи идут и идут, и данные нужно актуализировать. Остановился на зарубежном сборище футбольной инфы fbref.com, все в некрасивых таблицах 2002 года. "Как раз то, что мне нужно!", - вскрикнул я, после 3-его часа ресерча источника статистических данных. Ведь мне нужны были не простые, а всякие XG, XGa и прочие радости профессиональных футбольных "аналистов". Далее с помощью API Google Sheets и query запросов, по сути урезанным sql, я кидался данными из вкладки во вкладку, разбивая на те таблицы, которые мне будут нужны для расчетов.

Секунду, а как я инфу из Google Sheets на сайте смогу отобразить?!

Да, этот вопрос у меня появился после прекрасных дней ковыряния в данных и структурирования всей информации, которую я сумел спарсить. И я чутка приуныл, потому что помнил, что могу вывести айфреймом. Но, черт возьми, это так некрасиво и попахивает прошлым веком. Пришлось ковыряться дальше. Блог, куда я хотел это все засунуть, у меня стоит на обыкновенном Wordpress, но найти адекватный плагин, который выводил бы инфу в красивом виде на страницу ультра-сложно, чтобы ещё и работал нормально адекватно, конечно. В итоге, я нашел, даже с эстетикой выводимых таблиц я смирился. Взял плагин Inline Google Spreadsheet Viewer. Банальный до нельзя, но все же, мои таблички по крайней мере выглядели не совсем стыдно:

Пфф, я что не смогу найти скрипт для отправки данных в Google Sheets?

Не смогу. Потрачено кучу времени на поиск, весь стэкоферфлоу и гитхаб русскоязычный, англоязычный, все перерыл вдоль и поперёк. Думал я =). А оказалось, что я был рядом с решением моей проблемы. Проблема заключалась в следующем: нужно было дать возможность выбора футбольных команд пользователю, даже если это буду я (ибо трафика на блоге особо нет, да и я не парюсь), и при этом, отправить их в Google Sheets по API. Что оказалось не совсем легко.

Решение моей проблемы было у меня под носом. На одной из тысячи просмотренных мною страниц был заголовок "Отправка на почту через html форму, используя Google Apps". Но как и в других 999 страниц, я подумал, что это не имеет отношения ко мне, а ведь я был не прав.

В этой ветке было повествование о том, как отправить данные из формы на почту, при этом храня инфу в Google Sheets, что я каким-то образом упустил. И я решил попробовать обрезать в этом длинном пути к таблице почту, видь инфа с формы на почте мне точно не нужна, и куча инфы тем более. И я просто напросто так и сделал. И ничего не сломал, что было великолепным, ибо был запасной план создать левую почту для данных деяний, и засорять уже её спамом.

Ниже я добавлю весь основной код. Не знаю, могу ли публиковать не свой код, но под постом оставлю естественно ссылку на источник, ибо там есть ещё прекрасная доскональная инструкция. Да и весь код закоменчен, за что автору огромное спасибо. И Google Apps Script с этим всем справился на ура.

/****************************************************************************** * This tutorial is based on the work of Martin Hawksey twitter.com/mhawksey  * * But has been simplified and cleaned up to make it more beginner friendly   * * All credit still goes to Martin and any issues/complaints/questions to me. * ******************************************************************************/// if you want to store your email server-side (hidden), uncomment the next line// var TO_ADDRESS = "example@email.net";// spit out all the keys/values from the form in HTML for email// uses an array of keys if provided or the object to determine field orderfunction formatMailBody(obj, order) {  var result = "";  if (!order) {    order = Object.keys(obj);  }    // loop over all keys in the ordered form data  for (var idx in order) {    var key = order[idx];    result += "<h4 style='text-transform: capitalize; margin-bottom: 0'>" + key + "</h4><div>" + sanitizeInput(obj[key]) + "</div>";    // for every key, concatenate an `<h4 />`/`<div />` pairing of the key name and its value,     // and append it to the `result` string created at the start.  }  return result; // once the looping is done, `result` will be one long string to put in the email body}// sanitize content from the user - trust no one // ref: https://developers.google.com/apps-script/reference/html/html-output#appendUntrusted(String)function sanitizeInput(rawInput) {   var placeholder = HtmlService.createHtmlOutput(" ");   placeholder.appendUntrusted(rawInput);     return placeholder.getContent(); }function doPost(e) {  try {    Logger.log(e); // the Google Script version of console.log see: Class Logger    record_data(e);        // shorter name for form data    var mailData = e.parameters;    // names and order of form elements (if set)    var orderParameter = e.parameters.formDataNameOrder;    var dataOrder;    if (orderParameter) {      dataOrder = JSON.parse(orderParameter);    }        // determine recepient of the email    // if you have your email uncommented above, it uses that `TO_ADDRESS`    // otherwise, it defaults to the email provided by the form's data attribute    var sendEmailTo = (typeof TO_ADDRESS !== "undefined") ? TO_ADDRESS : mailData.formGoogleSendEmail;        // send email if to address is set    if (sendEmailTo) {      MailApp.sendEmail({        to: String(sendEmailTo),        subject: "Contact form submitted",        // replyTo: String(mailData.email), // This is optional and reliant on your form actually collecting a field named `email`        htmlBody: formatMailBody(mailData, dataOrder)      });    }    return ContentService    // return json success results          .createTextOutput(            JSON.stringify({"result":"success",                            "data": JSON.stringify(e.parameters) }))          .setMimeType(ContentService.MimeType.JSON);  } catch(error) { // if error return this    Logger.log(error);    return ContentService          .createTextOutput(JSON.stringify({"result":"error", "error": error}))          .setMimeType(ContentService.MimeType.JSON);  }}/** * record_data inserts the data received from the html form submission * e is the data received from the POST */function record_data(e) {  var lock = LockService.getDocumentLock();  lock.waitLock(30000); // hold off up to 30 sec to avoid concurrent writing    try {    Logger.log(JSON.stringify(e)); // log the POST data in case we need to debug it        // select the 'responses' sheet by default    var doc = SpreadsheetApp.getActiveSpreadsheet();    var sheetName = e.parameters.formGoogleSheetName || "responses";    var sheet = doc.getSheetByName(sheetName);        var oldHeader = sheet.getRange(1, 1, 1, sheet.getLastColumn()).getValues()[0];    var newHeader = oldHeader.slice();    var fieldsFromForm = getDataColumns(e.parameters);    var row = [new Date()]; // first element in the row should always be a timestamp        // loop through the header columns    for (var i = 1; i < oldHeader.length; i++) { // start at 1 to avoid Timestamp column      var field = oldHeader[i];      var output = getFieldFromData(field, e.parameters);      row.push(output);            // mark as stored by removing from form fields      var formIndex = fieldsFromForm.indexOf(field);      if (formIndex > -1) {        fieldsFromForm.splice(formIndex, 1);      }    }        // set any new fields in our form    for (var i = 0; i < fieldsFromForm.length; i++) {      var field = fieldsFromForm[i];      var output = getFieldFromData(field, e.parameters);      row.push(output);      newHeader.push(field);    }        // more efficient to set values as [][] array than individually    var nextRow = sheet.getLastRow() + 1; // get next row    sheet.getRange(nextRow, 1, 1, row.length).setValues([row]);    // update header row with any new data    if (newHeader.length > oldHeader.length) {      sheet.getRange(1, 1, 1, newHeader.length).setValues([newHeader]);    }  }  catch(error) {    Logger.log(error);  }  finally {    lock.releaseLock();    return;  }}function getDataColumns(data) {  return Object.keys(data).filter(function(column) {    return !(column === 'formDataNameOrder' || column === 'formGoogleSheetName' || column === 'formGoogleSendEmail' || column === 'honeypot');  });}function getFieldFromData(field, data) {  var values = data[field] || '';  var output = values.join ? values.join(', ') : values;  return output;}

Это просто спасло мне кучу времени и жизнь, ведь не реализовать как следует свою идею, это верх мучений. Как жить то потом?

Последнее, что мне оставалось, это сделать возможность автоматического обновления таблиц, дабы иметь актуальную информацию. Это уже было очень просто.

function update() {   var sheetName1 = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Tournament");   var sheetName2 = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("TheMeets");   var sheetName3 = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("TheMeets_sort");   var cellFunction1 = '=IMPORTHTML("https://fbref.com/en/comps/12/La-Liga-Stats","table",1)';  var cellFunction2 = '=sort({IMPORTHTML("https://fbref.com/en/comps/12/schedule/La-Liga-Scores-and-Fixtures","table",1);IMPORTHTML("https://fbref.com/en/comps/12/3239/schedule/2019-2020-La-Liga-Scores-and-Fixtures","table",1);IMPORTHTML("https://fbref.com/en/comps/12/1886/schedule/2018-2019-La-Liga-Scores-and-Fixtures","table",1);IMPORTHTML("https://fbref.com/en/comps/12/1652/schedule/2017-2018-La-Liga-Scores-and-Fixtures","table",1)},3,FALSE)';  var cellFunction3 = '=sort(IMPORTHTML("https://fbref.com/en/comps/12/schedule/La-Liga-Scores-and-Fixtures","table",1),3,TRUE)';      sheetName1.getRange('A1').setValue(cellFunction1);     sheetName2.getRange('A2').setValue(cellFunction2);    sheetName3.getRange('A1').setValue(cellFunction3);}

Выбираем таблицы и обновляем их. Так же добавляем в Google Apps Script триггер, на развертывание данного скрипта ежедневно. И вуаля!

УРА!

Я победил и смог довести дело до конца, сейчас все это выглядит в более ли менее адекватном виде:

Тут мы жмякаем на кнопку "Анализ статистики" и попадаем на страницу "Спасибо", которую я заюзал как задержку, чтобы дать возможность Google Sheets принять данные с формы, все посчитать, и выдать всю информацию. А на выходе получил красивую статистику матчей, и небольшие предсказания моей ванга-таблички.

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

Весь код, инструкция и как с помощью Google Apps Script складировать свои данные в Google Sheets здесь.

В двух словах на спортс.ру расписал, что я планировал учитывать в расчетах и как считать.

Подробнее..

От Планеты GitHub с любовью

08.06.2021 20:05:53 | Автор: admin

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

В ноябре прошлого года более 500 российских технологов присоединились к нашему первому GitHub Meetup на русском языке и сейчас наше коммюнити насчитывает уже больше 1000 человек. Недавно мы открыли Telegram канал. Для нас это большая честь - привлекать такое внимание, и мы всегда будем рады слушать, учить и вдохновлять русскоязычное сообщество на вашем родном языке.

Вот краткое изложение того, о чем мы рассказали на данный момент:

GitHub:

Открытый исходный код правит миром:

Безопасность - наша ответственность:

Программа GitHub Stars и ваша собственная Русская Звезда

Начало работы с InnerSource

  • Дмитрий Сугробов (разработчик и евангелист в Leroy Merlin, член InnerSource Commons Foundation) дал нам Введение в InnerSource.

Бонус-трек: развитие soft skills

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

Если вы хотите выступить в качестве приглашенного докладчика, отправьте нам предложение на http://github.co/cfp - не важно если это ваше первое или уже тысячное выступление. Мы приглашаем Вас присоединиться к нам и воспользоваться имеющимися возможностями.

Мы надеемся, что вы присоединитесь к нам 15 июня 2021 года в 19:00 по Москве для дальнейшего общения и новых сессий.

Далее: наши эксперты на июньском мероприятии:

  • Грачев Михаил: Взращивание культуры Open Source в компании

  • Антон Таяновский: Инфраструктурные шаблоны Pulumi на примере AWS Lambda и Sagemaker

RSVP:http://github.co/habr

Подробнее..

Пишем простейший GitHub Action на TypeScript

09.06.2021 16:12:52 | Автор: admin

Недавно я решил немного привести в порядок несколько своих .NET pet-проектов на GitHub, настроить для них нормальный CI/CD через GitHub Actions и вынести всё в отдельный репозиторий, чтобы все скрипты лежали в одном месте. Для этого пришлось немного поизучать документацию, примеры и существующие GitHub Actions, выложенные в Marketplace.

В данной статье я решил собрать самую основную информацию, которая нужна для того, чтобы написать простейший GitHub Action с использованием TypeScript.

Статья в первую очередь рассчитана на начинающих, тех, кто никогда не использовал GitHub Actions, но хотел бы быстро начать. Тем не менее, даже если у вас уже есть подобный опыт, но вы, например, не использовали ncc, то, возможно, и для вас в ней будет что-то полезное.

Краткое введение в GitHub Actions

В основе GitHub Actions лежит понятие Workflow - некий рабочий процесс, который запускается по определённым триггерам. В одном репозитории может быть как один рабочий процесс, так и несколько отдельных, срабатывающий при разных условиях.

Чтобы добавить рабочий процесс, нужно создать в репозитории один или несколько yml-файлов в папке .github/workflows. Простейший файл может выглядеть следующим образом:

name: Helloon: [push]jobs:  Hello:    runs-on: ubuntu-latest    steps:      - name: Checkout        uses: actions/checkout@v2      - name: Hello        run: echo "Hello, GitHub Actions!"

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

Сам рабочий процесс мы можем найти на вкладке Actions в интерфейсе GitHub и посмотреть детальную информацию по нему:

Каждый рабочий процесс состоит из одного или нескольких джобов. В данном случае джоб Hello. В общем случае джобы не зависят друг от друга и могут запускаться параллельно на нескольких машинах, но при необходимости между ними можно определить зависимости.

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

Сами действия представляют из себя отдельный кусок рабочего процесса, который можно повторно использовать в нескольких проектах. Они могут быть трёх видов: докер контейнер, действие JavaScript, или составные действия (которые по сути просто переиспользуемый набор других действий). Вариант с действием JavaScript мы как раз и рассмотрим в этой статье, но писать его будем на TypeScript для большего удобства.

(Более подробную информацию по возможностям рабочих процессов можно найти в документации)

Пара слов о месте размещения действий в репозитории

Действия можно разместить в репозитории в нескольких местах в зависимости от потребностей:

  • В подпапке .github/actions. Такой способ обычно используется когда вы хотите использовать эти действия из того же репозитория. В этом случае ссылаться на них необходимо по пути без указания ветки или тега:

    steps:  - uses: ./.github/actions/{path}
    
  • В произвольном месте репозитория. Например, можно разместить несколько действий в корне репозитория, каждое в своей подпапке. Такой способ хорошо подходит, если вы хотите сделать что-то вроде личной библиотеки с набором действий, которые собираетесь использовать из разных проектов. В этом случае ссылаться на такие действия нужно по названию репозитория, пути и ветке или тегу:

    steps:  - uses: {owner}/{repo}/{path}@{ref}
    
  • В корне репозитория. Такой способ позволяет разместить в одном репозитории только одно действие, и обычно используется если вы хотите позже опубликовать его в Marketplace. В этом случае ссылаться на такие действия нужно по названию репозитория и ветке или тегу:

    steps:  - uses: {owner}/{repo}@{ref}
    

Создаём действие на TypeScript

В качестве примера создадим очень простое действие, которое просто выводит сообщение Hello, GitHub Actions!. Для разработки действия нам потребуется установленная версия Node.js (я использовал v14.15.5).

Создадим в репозитории папку .github/actions. В ней создадим подпапку hello, в которой будем далее создавать все файлы, относящиеся к нашему действию. Нам потребуется создать всего четыре файла.

Создаём файл action.yml:

name: Hellodescription: Greet someoneruns:  using: node12  main: dist/index.jsinputs:  Name:    description: Who to greet    required: true

Этот файл содержит метаданные нашего действия. По его наличию GitHub понимает, что в папке расположен не случайный набор файлов, а именно какое-то действие, которое можно загрузить и выполнить.

Также мы указываем, что это именно JavaScript действие, а не докер контейнер и указываем точку входа: файл dist/index.js. Этого файла у нас пока нет, но он будет автоматически создан чуть позже.

Кроме этого мы можем указать в этом файле набор входных и выходных параметров. В данном случае мы передаём в качестве параметра имя для приветствия.

(Более подробную информацию по возможностям файла метаданных можно найти в документации)

Создаём файл package.json:

{  "private": true,  "scripts": {    "build": "npx ncc build ./src/main.ts"  },  "dependencies": {    "@actions/core": "^1.2.7",    "@actions/exec": "^1.0.4",    "@actions/github": "^4.0.0",    "@actions/glob": "^0.1.2",    "@types/node": "^14.14.41",    "@vercel/ncc": "^0.28.3",    "typescript": "^4.2.4"  }}

Это стандартный файл для Node.js. Чтобы не указывать бесполезные атрибуты типа автора, лицензии и т.п. можно просто указать, что пакет private. (При желании можно конечно всё это указать, кто я такой, чтобы вам это запрещать =)

В скриптах мы указываем один единственный скрипт сборки, который запускает утилиту ncc. Эта утилита на вход получает файл src/main.ts и создаёт файл dist/index.js, который является точкой входа для нашего действия. Я вернусь к этой утилите чуть позже.

В качестве зависимостей мы указываем typescript и @types/node для работы TypeScript. Зависимость @vercel/ncc нужна для работы ncc.

Зависимости @actions/* опциональны и являются частью GitHub Actions Toolkit - набора пакетов для разработки действий (я перечислил самые на мой взгляд полезные, но далеко не все):

  • Зависимость @actions/core понадобится вам вероятнее всего. Там содержится базовый функционал по выводу логов, чтению параметров действия и т.п. (документация)

  • Зависимость @actions/exec нужна для запуска других процессов, например dotnet. (документация)

  • Зависимость @actions/github нужна для взаимодействия с GitHub API. (документация)

  • Зависимость @actions/glob нужна для поиска файлов по маске. (документация)

Стоит также отметить, что формально, поскольку мы будем компилировать наше действие в один единственный файл dist/index.js через ncc, все зависимости у нас будут зависимостями времени разработки, т.е. их правильнее помещать не в dependencies, а в devDependencies. Но по большому счёту никакой разницы нет, т.к. эти зависимости вообще не будут использоваться во время выполнения.

Создаём файл tsconfig.json:

{  "compilerOptions": {    "target": "ES6",    "module": "CommonJS",    "moduleResolution": "Node",    "strict": true  }}

Тут всё достаточно просто. Это минимальный файл, с которым всё хорошо работает, включая нормальную подсветку синтаксиса и IntelliSense в Visual Studio Code.

Создаём файл src/main.ts:

import * as core from '@actions/core'async function main() {  try {    const name = core.getInput('Name');    core.info(`Hello, ${name}`);  } catch (error) {    core.setFailed(error.message)  }}main();

В качестве точки входа мы используем синхронную или асинхронную функцию main, которую вызываем в этом же файле. (Название функции также может быть любым).

Тело функции необходимо обернуть в блок try-catch, чтобы в случае любых ошибок сборка в GitHub прерывалась. Без этого она всегда будет считаться успешной.

В данном конкретном случае мы также используем пакет @actions/core для чтения параметров и вывода сообщения в лог.

Собираем действие при помощи ncc

После того, как все файлы созданы нам первым делом необходимо восстановить все пакеты из npm. Для этого нам нужно перейти в папку действия (не в корень репозитория) и выполнить команду:

npm install

А дальше возникает нюанс. Если прочитать официальную документацию, чтобы GitHub мог корректно загрузить и выполнить действие, все скрипты, включая зависимости должны находиться в том же репозитории. Это означает, что нам по сути предлагается закоммитить папку node_modules в репозиторий, что, на мой взгляд, не очень красивое решение.

В качестве альтернативы можно воспользоваться пакетом @vercel/ncc, который позволяет собрать js или ts-файлы в один единственный js-файл, который уже можно закоммитить в репозиторий.

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

npm run build

В результате мы получим файл dist/index.js, который нужно будет закоммитить в репозиторий вместе с остальными файлами. Папка node_modules при этом может быть спокойно отправлена в .gitignore.

Используем действие

Чтобы протестировать действие, создадим в папке .github/workflows файл рабочего процесса. Для разнообразия сделаем так, чтобы он запускался не по пушу, а вручную из интерфейса:

name: Helloon:  workflow_dispatch:    inputs:      Name:        description: Who to greet        required: true        default: 'GitHub Actions'jobs:  Hello:    runs-on: ubuntu-latest    steps:      - name: Checkout        uses: actions/checkout@v2      - name: Hello        uses: ./.github/actions/hello        with:          Name: ${{ github.event.inputs.Name }}

Здесь настройки workflow_dispatch описывают форму в интерфейсе GitHub в которую пользователь сможет ввести данные. Там у нас будет одно единственное поле для ввода имени для приветствия.

Данные, введённые в форму через событие передаются в действие, которое мы запускаем в рабочем процессе через параметр github.event.inputs.Name.

Кроме этого, поскольку действие находится в том же репозитории, что и рабочий процесс, нам обязательно необходимо сделать checkout, чтобы все скрипты были доступны в рабочей папке.

После того, как мы запушим наш рабочий процесс, мы можем перейти в интерфейс GitHub, на странице Actions выбрать наш рабочий процесс и запустить его выполнение, указав параметры:

После того, как мы запустим рабочий процесс, можем зайти в него и посмотреть, что получилось:

Настраиваем GitHooks для автоматической сборки

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

Каждый раз, когда мы будем изменять код действия нам нужно не забыть вызвать команду:

npm run build

По моему опыту такой ручной запуск постоянно будет приводить к ситуации "Я вроде поменял код и запушил его на сервер, но у меня ничего не поменялось. А, блин, я же забыл пересобрать файлы...".

Решить эту проблему можно несколькими способами. Можно не париться и действительно каждый раз запускать сборку руками. В некоторых проектах люди подходят к этому основательно и делают специальный рабочий процесс через тот же GitHub Actions, который на каждый коммит пытается пересобрать действие и коммитит изменения.

Я для себя себя решил проблему проще и просто добавил гит хук, который автоматически пересобирает все действия в репозитории на каждый коммит. Для этого необходимо создать в репозитории папку .githooks, а в ней файл pre-commit следующего вида:

#!/bin/shfor action in $(find ".github/actions" -name "action.yml"); do    action_path=$(dirname $action)    action_name=$(basename $action_path)    echo "Building \"$action_name\" action..."    pushd "$action_path" >/dev/null    npm run build    git add "dist/index.js"    popd >/dev/null    echodone

Здесь мы находим все файлы action.yml в папке .github/actions и для всех найденных файлов запускаем сборку из их папки. Теперь нам не нужно будет думать о явной пересборке наших действий, она будет делаться автоматически.

Чтобы хуки из папки .githooks запускались, нам необходимо поменять конфигурацию для текущего репозитория:

git config core.hooksPath .githooks

Или можно сделать это глобально (я сделал именно так):

git config --global core.hooksPath .githooks

Заключение

В данной статье я постарался собрать основную информацию, необходимую для того, чтобы быстро сделать первый шаг к GitHub Actions и начать писать свои собственные скрипты автоматизации на TypeScript.

Репозиторий с примером можно найти тут: https://github.com/Chakrygin/hello-github-action

Подробнее..

Легкий DataBinding для Android

22.03.2021 04:10:35 | Автор: admin

Здравствуйте уважаемые читатели. Все мы любим и используем DataBinding, который представила компания Google несколько лет назад, для связи модели данных с вьюшками через ViewModel. В этой статье, хочу поделиться с вами, как можно унифицировать этот процесс с помощью языка Kotlin, и уместить создание адаптеров для RecyclerView (далее RV), ViewPager и ViewPager2 в несколько строчек кода.

Начну с того, что раньше разрабатывали кастомные адаптеры, которые под капотом создавали ViewHolder'ы, и их написание, а тем более поддержка, занимала достаточно большое количество времени. Ниже приведу пример типичного адаптера для RV:

class CustomAdapter(private val dataSet: Array<String>) : RecyclerView.Adapter<CustomAdapter.ViewHolder>() {        class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {    val textView: TextView            init {      textView = view.findViewById(R.id.textView)    }  }       override fun onCreateViewHolder(viewGroup: ViewGroup, viewType: Int): ViewHolder {    val view = LayoutInflater.from(viewGroup.context).inflate(R.layout.text_row_item, viewGroup, false)          return ViewHolder(view)  }         override fun onBindViewHolder(viewHolder: ViewHolder, position: Int) {    // Get element from your dataset at this position and replace the    viewHolder.textView.text = dataSet[position]  }        override fun getItemCount() = dataSet.size}

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

Затем появился DataBinding и большую часть по связыванию данных перекладывалась на него, но адаптеры все равно приходилось писать вручную, изменились только методы onCreateViewHolder, где вместо инфлэйтинга через LayoutInflater, использовался DataBindingUtil.inflate, а при создании вьюхолдеров данные связывались непосредственно с самой вьюшкой через ссылку на созданный объект байдинга.

class BindingViewHolder(val binding: ItemTextRowBinding) : RecyclerView.ViewHolder(binding.root)override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BindingViewHolder {        val binding = DataBindingUtil.inflate<ItemTextRowBinding>(LayoutInflater.from(parent.context), viewType, parent, false)        val viewHolder = BindingViewHolder(binding)        return viewHolder}override fun onBindViewHolder(holder: BindingViewHolder, position: Int) {      holder.binding.setVariable(BR.item, dataSet[position])}

Выглядит уже лучше, но что если в RV, по прежнему должны отображаться элементы лайаута с разными типами данных, то такая реализация не сильно помогла решить проблему больших адаптеров. И здесь на помощь приходит аннотация BindingAdapter из библиотеки androidx.databinding. С ее помощью, можно создать универсальное решение, которое скрывает реализацию создания адаптера для RV, если использовать вспомогательный объект-конфигуратор DataBindingRecyclerViewConfig, в котором содержится ряд свойств для настройки адаптера.

В результате на свет появилась библиотека, которая называется EasyRecyclerBinding. В нее так же вошли BindingAdapters для ViewPager и ViewPager2. Теперь процесс связывания данных выглядит следующим образом:
1) В лайауте фрагмента, необходимо добавить специальные переменные, которые содержат список отображаемых моделей данных и конфигурацию, указав их атрибутами для RV, - app:items и app:rv_config.

<?xml version="1.0" encoding="utf-8"?><layout xmlns:android="http://personeltest.ru/away/schemas.android.com/apk/res/android"    xmlns:app="http://personeltest.ru/away/schemas.android.com/apk/res-auto"    xmlns:tools="http://personeltest.ru/away/schemas.android.com/tools">    <data>    <variable        name="vm"        type="com.rasalexman.erb.ui.base.ExampleViewModel" />        <variable            name="rvConfig"            type="com.rasalexman.easyrecyclerbinding.DataBindingRecyclerViewConfig" />    </data>    <androidx.constraintlayout.widget.ConstraintLayout        android:id="@+id/main"        android:layout_width="match_parent"        android:layout_height="match_parent">        <androidx.recyclerview.widget.RecyclerView            android:layout_width="0dp"            android:layout_height="0dp"            app:items="@{vm.items}"            app:rv_config="@{rvConfig}"            app:layout_constraintBottom_toBottomOf="parent"            app:layout_constraintEnd_toEndOf="parent"            app:layout_constraintStart_toStartOf="parent"            app:layout_constraintTop_toTopOf="parent"            tools:listitem="@layout/item_recycler"/>    </androidx.constraintlayout.widget.ConstraintLayout></layout>

ViewModel, соответственно, содержит список моделей данных для адаптера, которые должны отображаться в RV, а фрагмент конфигурацию DataBindingRecyclerViewConfig.

// названия пакетов не указаны для простоты примераclass ExampleViewModel : ViewModel() {     val items: MutableLiveData<MutableList<RecyclerItemUI>> = MutableLiveData()}data class RecyclerItemUI(    val id: String,    val title: String)

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

<?xml version="1.0" encoding="utf-8"?><layout xmlns:android="http://personeltest.ru/away/schemas.android.com/apk/res/android"    xmlns:app="http://personeltest.ru/away/schemas.android.com/apk/res-auto"    xmlns:tools="http://personeltest.ru/away/schemas.android.com/tools">    <data>        <variable            name="item"            type="com.rasalexman.erb.models.RecyclerItemUI" />    </data>    <androidx.constraintlayout.widget.ConstraintLayout        android:layout_width="match_parent"        android:layout_height="wrap_content"        android:background="@android:drawable/list_selector_background">        <TextView            android:id="@+id/titleTV"            android:layout_width="match_parent"            android:layout_height="wrap_content"            android:paddingStart="16dp"            android:paddingTop="8dp"            android:paddingEnd="16dp"            android:textColor="@color/black"            android:textSize="18sp"            android:text="@{item.title}"            app:layout_constraintEnd_toEndOf="parent"            app:layout_constraintStart_toStartOf="parent"            app:layout_constraintTop_toTopOf="parent"            tools:text="Hello world" />        <TextView            android:layout_width="match_parent"            android:layout_height="wrap_content"            android:paddingStart="16dp"            android:paddingEnd="16dp"            android:paddingBottom="8dp"            android:textColor="@color/gray"            android:textSize="14sp"            android:text="@{item.id}"            app:layout_constraintEnd_toEndOf="parent"            app:layout_constraintStart_toStartOf="parent"            app:layout_constraintTop_toBottomOf="@+id/titleTV"            tools:text="Hello world" />    </androidx.constraintlayout.widget.ConstraintLayout></layout>

2) Во фрагменте нам нужно получить конфигурацию для адаптера и передать её в отображение через инстанс dataBinding, используя специальную функцию-конструктор createRecyclerConfig<I : Any, BT : ViewDataBinding>, которая создаст и вернет инстанс DataBindingRecyclerViewConfig, указав при этом id лайаута для выбранной модели, и название свойства, к которому будет прикреплена данная модель.

class RecyclerViewExampleFragment : BaseBindingFragment<RvExampleFragmentBinding, ExampleViewModel>() {      override val layoutId: Int get() = R.layout.rv_example_fragment  override val viewModel: ExampleViewModel by viewModels()        override fun initBinding(binding: RvExampleFragmentBinding) {        super.initBinding(binding)        binding.rvConfig = createRecyclerConfig<RecyclerItemUI, ItemRecyclerBinding> {            layoutId = R.layout.item_recycler        itemId = BR.item                    }    }}

Это все, что нужно сделать, чтобы связать данные из ViewModel с отображением списка в RV. Так же при создании адаптера можно назначить слушатели событий для байдинга вьюхолдера, такие как onItemClick,onItemCreate, onItemBind и другие.

А чтобы использовать вьюхолдеры с разными визуальными лайаутами, к которым привязаны свои модели отображения данных, необходимо имплементировать в них специальный интерфейс из библиотеки EasyRecyclerBinding - IBindingModelи переопределить поле layoutResId, - id лайаута, который будет отображаться для этой модели в списке.

data class RecyclerItemUI(    val id: String,    val title: String) : IBindingModel {        override val layoutResId: Int            get() = R.layout.item_recycler}data class RecyclerItemUI2(    val id: String,    val title: String) : IBindingModel {    override val layoutResId: Int        get() = R.layout.item_recycler2}

Реализация для фрагмента с разными вьюхолдерами не сильно отличается от вышеприведенной, кроме того, используется другая функция-конструктор createRecyclerMultiConfig для конфигурации адаптера, в которой уже не нужно указывать тип привязанных данных и сгенерированный класс байдинга.

class RecyclerViewExampleFragment : BaseBindingFragment<RvExampleFragmentBinding, RecyclerViewExampleViewModel>() {      override val layoutId: Int get() = R.layout.rv_example_fragment    override val viewModel: RecyclerViewExampleViewModel by viewModels()        override fun initBinding(binding: RvExampleFragmentBinding) {        super.initBinding(binding)        binding.rvConfig = createRecyclerMultiConfig {            itemId = BR.item        }    }}class RecyclerViewExampleViewModel : BasePagesViewModel() {    open val items: MutableLiveData<MutableList<IBindingModel>> = MutableLiveData()}

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


Аналогичный процесс создания адаптеров для ViewPager и ViewPager2, представлен в примере на github вместе с открытым кодом, ссылку на который, я разместил в конце статьи. В настоящий момент библиотека еще дорабатывается, и хочется получить адекватный фидбек, и пожелания по дальнейшему ее развитию. Так же в неё вошли вспомогательные функции для удобного создания байдинга, в том числе в связке с ViewModel. (LayoutInflater.createBinding, Fragment.createBindingWithViewModel, etc)

Спасибо, что дочитали до конца. Приятного кодинга и хорошего настроения)

Подробнее..

Перевод Продвинутые функции гита, о которых вы, возможно, не знали

04.03.2021 18:06:42 | Автор: admin

Git очень мощный инструмент, который практически каждый разработчик должен использовать ежедневно, но для большинства из нас git сводится к нескольким командам: pull commit push. Однако, чтобы быть эффективным, продуктивным и обладать всей мощью git, необходимо знать ещё несколько команд и трюков. Итак, в этой статье мы исследуем функции git, которые просто запомнить, применять и настроить, но которые могут сделать ваше время с git гораздо более приятным.


Прокачиваем базовый рабочий процесс

Прежде чем мы воспользуемся даже самыми базовыми командами pull, commit и push, необходимо выяснить, что происходит с нашими ветками и изменёнными файлами. Для этого можно воспользоваться git log довольно известной командой, хотя не все знают, как сделать его вывод на самом деле читабельным и красивым:

Дерево git log.Дерево git log.

Такой граф даст хороший обзор, однако часто нужно копать немного глубже. Например, посмотреть историю (эволюцию) определённых файлов или даже отдельных функций; в этом поможет git log с флагом -L::).

git log для функции.git log для функции.

Теперь, когда мы немного представляем происходящее в репозитории, мы, возможно, захотим проверить различия между обновлёнными файлами и последним коммитом. Здесь можно воспользоваться git diff; опять же ничего нового здесь нет, но у diff есть кое-какие опции и флаги, о которых вы, возможно, не знаете. Например, можно сравнить две ветки: git diff branch -a branch -b, или даже конкретные файлы в разных ветках: `git diff <commit-a> <commit-b> -- <пути>`.

Иногда чтение git diff становится трудной задачей. Можно попробовать прописать игнорирующий все пробельные символы (white-space) флаг -w, и этим немного заспамить diff, или флаг --word-diff и работать вместо строк с раскрашенными словами.

Если простой статичный вывод в оболочке вас не устраивает, можно запустить difftool, вот так: git difftool=vimdiff, команда откроет файлы diff внутри vim в два окна слева и справа. Очевидно, что Vim не единственный вариант; можно запустить git difftool --tool-help, чтобы увидеть список всех инструментов, которые можно использовать вместе с diff.

Мы уже видели, как просматривать историю конкретных частей или строк в файла с помощью git log. Было бы удобно делать нечто подобное, например, стейджинг частей файлов, правда? И такое легко делается в в IDE, например, в IntelliJ; то же самое уже сложнее в git CLI, но, конечно же, по-прежнему возможно: в git add пропишите опцию --patch:

Команда открывает редактор, в котором отображается один "hunk" [кусок], представляющий собой кусок кода с несколькими отличающимися друг от друга строками в нём. Можно много чего сделать с этим куском, но самые важные опции это y принять изменения (делает стейджинг), n не принимать (не делать стейджинг) и e отредактировать кусок перед стейджингом (полный список опций здесь).

Когда закончите с интерактивным стейджингом, вы можете запустить git status, и увидите, что файл с частичным стейджингом находится в разделах "Changes to be committed:" и "Changes not staged for commit:". Кроме того, можно запустить git add -i (интерактивный стейджинг), а затем воспользоваться командой s (статус), которая покажет вам, какие строки находятся на стейджинге, а какие нет.

Исправление распространённых ошибок

Закончив со стейджингом, я (слишком) часто осознаю, что добавил то, чего добавлять не хотел. Однако на этот случай у git для файлов нет команды un-stage. Чтобы обойти ограничение, можно сбросить репозиторий командой git reset --soft HEAD somefile.txt. Вы также можете включить в git reset флаг -p, который покажет вам тот же UI, что и у git-add -p. Также не забудьте добавить туда флаг --soft, иначе вы сотрёте ваши локальные изменения!

Поменьше грубой силы

Теперь, когда мы закончили стейджинг, всё, что осталось, commit и push. Но что, если мы забыли что-то добавить или совершили ошибку и хотим исправить уже запушенные коммиты? Есть простое решение, использующее git commit -a и git push --force, но оно может быть довольно опасным, если мы работаем над общей веткой, например, master. Таким образом, чтобы избежать риска перезаписи чужой работы из-за того, что мы решили проблему грубой силой, мы можем воспользоваться флагом --force-with-lease. Этот флаг в отличие от --force запушит на изменения только в том случае, если за время работы никто не добавил никаких изменений в ветку. Если ветка была изменялась, код не будет отправлен, и этот факт сам по себе указывает на то, что перед отправкой кода мы должны выполнить git pull.

Правильное слияние веток

Если вы работаете над репозиторием, в котором участвует более одного разработчика, можно с уверенностью предположить, что вы работаете в отдельной ветке, а не в мастере. Это также означает, что рано или поздно вам придётся включить свой код в кодовую базу (главную ветку). Вполне вероятно, что, пока вы работали над своей веткой, кто-то другой уже добавил свой код в мастер, из-за чего ветка вашей функциональности отстаёт на несколько коммитов. Можно пойти напролом и выполнить слияние вашего кода в мастер с помощью git merge, но команда создаст дополнительный комммит слияния, а также, без необходимости на то, затруднит чтение истории и сделает её сложнее:

История с ветвлением.История с ветвлением.

Подход гораздо лучше (не стесняйтесь спорить со мной по этому поводу, образно говоря, это та высота, на которой я готов умереть) заключается в том, чтобы сделать rebase ветки функции в master, а затем выполнить так называемую быструю перемотку (git merge --ff). Подход сохраняет историю линейной, читать такую историю легче, упрощается и последующий поиск коммитов с новым функционалом и коммитов виновников ошибок.

Но как нам сделать такой rebase? Можно выполнить rebase в его базовой форме с помощью git rebase master feature_branch, чего часто бывает достаточно (за этим следует push --force). Однако, чтобы получить от git rebase максимальную отдачу, также следует включить флаг -i, чтобы rebase был интерактивным. Интерактивный rebase удобный инструмент, чтобы, например, переформулировать, сжать или вообще очистить ваши коммиты и всю ветку. В качестве небольшой демонстрации мы можем даже сделать rebase ветки на саму себя:

Приём выше позволяет нам повторно применять последние 4 коммита и изменить их, получив полезный результат, например сжать одни коммиты и переформулировать другие:

Выше показан пример сеанса rebase. В верхней части показывается ветка перед перезагрузкой. Вторая часть фрагмента это список коммитов, представленных после запуска git rebase каждый из них можно выбрать, чтобы включить в работу (pick). Мы можем изменить действие для каждого из них, а также полностью переупорядочить коммиты. Как показано в третьем разделе примера, некоторые допустимые действия переформулирование (оно говорит git открыть редактор сообщений о коммите), сжатие коммита (объединяет коммиты в предыдущий) и исправление коммита: (исправление работает как сжатие, но при этом сбрасывает сообщение о коммите). После того как мы применим эти изменения и переформулируем изменённые коммиты, мы получим историю, которая показана на скриншоте выше, в его нижней части.

Если во время rebase вы столкнулись с каким-либо конфликтом, чтобы разрешить его, вы можете запустить git mergetool --tool=vimdiff, а затем продолжить rebase с помощью git rebase --continue. git mergetool может быть вам не знаком, на первый взгляд он может показаться пугающим. В действительности же это то же самое, что IDE вроде IntelliJ, просто в стиле Vim. Если вы не знаете хотя бы несколько сочетаний клавиш Vim, то, как и в случае с любым другим использующим этот редактор инструментом, вам, может быть, трудно даже понять, на что на самом деле вы смотрите. Если вам нужна помощь, я рекомендую прочитать эту исчерпывающую статью.

Если всё это кажется слишком сложным или вы просто боитесь работать с rebase, в качестве альтернативы создайте пул реквест на GitHub и нажмите кнопку Rebase and merge, чтобы сделать, по крайней мере, простые и быстрые rebase и merge с быстрой перемоткой.

Главное эффективность

Я думаю, что примеры выше показали несколько изящных советов и хитростей, но всё это может быть довольно сложно запомнить, особенно когда дело касается команд вроде git log. К счастью, чтобы разрешить эти трудности, можно воспользоваться глобальной конфигурацией git. Она находится в ~/.gitconfig и обновляется каждый раз, когда вы запускаете git config --global. Даже если вы не настраивали этот файл, он, вероятно, содержит кое-какие базовые вещи, такие как раздел [user], но можно добавить много других разделов:

Выше приведён пример некоторых из доступных опций конфигурации. Примечательно, что длинная команда git log это только псевдоним git graph. Автокоррекция установлена 10: такое значение включает её и заставляет ждать 1 секунду, прежде чем выполнить правильную команду, в которой была опечатка, и, наконец, последний раздел подписывание коммита GPG (подробнее об этом читайте ниже).

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

Автозавершение команд это инструмент не менее продуктивный, чем псевдонимы, и он просто устанавливается:

Extras

Можно не только писать свои псевдонимы, но и взять на вооружение плагин git-extras, он вводит много полезных команд, которые могут немного упростить вам жизнь. Я не буду вдаваться в подробности обо всех возможностях этого плагина посмотрите список команд, а я просто покажу один краткий пример из этого списка прямо здесь:

  • git delta список файлов, которые в другой ветке отличаются.

  • git show-tree древовидное представление коммитов всех ветвей, похожее на показанный ранее git log.

  • git pull-request пул-реквест в командной строке.

  • git changelog генерирует журнал изменений (changelog) из тегов и сообщений в коммитах.

Конечно, это не единственный крутой плагин. Например, есть ещё один удобный инструмент, позволяющий открыть репозиторий в браузере прямо из командной строки. Кроме того, в приглашении терминала можно настроить статус репозитория, это делается с помощью zsh или bash-it.

Подписываем коммиты

Даже если вы никогда не вкладывались в какой-либо проект Open Source, вы, вероятно, прокручивали историю коммитов такого проекта. В ней вы, скорее всего, видели значок подтверждённого (sign-off знак о правах на ПО), проверенного или подписанного коммита. Что это такое и зачем?

Первый значок используется, когда автор подтверждает, что соответствующий код написал именно он, или же значком вы подтверждаете, что, насколько вам известно, он был создан на основе соответствующей лицензии Open Source. Это делается по юридическим причинам, которые связаны со статусом авторских прав на код. Обычно вам не нужно пользоваться этим значком, но, если вы в какой-то момент захотите внести вклад в проект, который требует подтверждения прав, знак подтверждения ставится так:

Сверху видно, что в git commit с опцией --sign-off в конце сообщения о коммите автоматически добавляется строка Signed-off-by: , которая формируется на основе вашего имени пользователя в конфигурации git.

Что касается значка signed/verified, который вы, вероятно, заметили в некоторых репозиториях, он существует, потому что на GitHub довольно легко выдавать себя за других пользователей. Всё, что вам нужно сделать, изменить имя сделавшего коммит человека и электронную почту в вашей конфигурации и отправить изменения. Чтобы предупредить ситуацию, вы можете подписывать коммиты с помощью ключей GPG, подтверждающих, что автор коммита и отправитель изменений на самом деле является тем, за кого он себя выдаёт. Подпись коммита более распространена, чем подтверждение прав, поскольку важно знать, кто на самом деле внёс код.

Если вы хотите начать пользоваться этой функцией или, возможно, хотите внедрить её в вашей команде, можно сделать следующее:

Сначала вы генерируете пару ключей GPG (если у вас её ещё нет), затем устанавливаете ключи при помощи git config и, наконец, добавляете опцию -S, когда делаете коммит. Затем, посмотрев на информацию о коммите на GitHub, вы увидите значок, как на картинке ниже.

Подписанный непроверенный коммит.Подписанный непроверенный коммит.

Однако, как видно на изображении, подпись не проверена, потому что GitHub не знает, что ключ GPG принадлежит вам. Чтобы это исправить, открытый ключ из нашей пары ключей нужно отправить на GitHub. Для этого экспортируем ключ командой gpg --export, как здесь:

Затем скопируйте этот ключ и вставите его в поле https://github.com/settings/gpg/new. Если вы проверите ранее подписанный коммит после добавления ключа, то увидите, что коммит теперь проверен (verified). Здесь предполагаем, что вы добавили на GitHub именно тот ключ, которым подписывали коммит:

Подписанный проверенный коммит.Подписанный проверенный коммит.

Заключение

Git очень мощный инструмент, у которого слишком много подкоманд и опций, чтобы в одной статье описать их все. Если вы хотите глубже погрузиться в некоторые связанные с Git темы, я бы порекомендовал прочитать Debugging with Git, чтобы узнать больше о blame, bisect или Getting solid at Git rebase vs. merge, чтобы глубже понять rebase и merge. Помимо множества полезных статей в Интернете часто при поиске информации о некоторых тонкостях git лучший выбор это мануал, который выводится опцией --help, или версия в сети.


Узнайте подробности, как получить Level Up по навыкам и зарплате или востребованную профессию с нуля, пройдя онлайн-курсы SkillFactory со скидкой 40% и промокодомHABR, который даст еще +10% скидки на обучение.

Подробнее..

GitOps Определение дрейфа вашей инфраструктуры Terraform Terragrunt

18.03.2021 14:16:42 | Автор: admin

Всем привет.

Дисклеймер: сказу скажу, что пишу статью по-ходу дела, "код" в ней рабочий, но не претендует на какие-либо best practices, поэтому не придирайтесь :) Цель статьи: донести до интересующейся русскоязычной части населения общие принципы, возможно разбудить интерес поразбираться самостоятельно и сделать что-то гораздо лучше и интереснее. Итак поехали!

Допустим Вы работаете с Terraform / Terragrunt (второе здесь непринципиально, но лучше изучайте, если ещё не используете) и автоматизируете инфраструктуру, например, в AWS (но совершенно необязательно AWS). Инфраструктура в коде репозитория, разворачивается из него же, казалось бы вот оно GitOps счастье :)

Всё идёт хорошо, пока какой-то пользователь не поменял что-то руками через консоль / UI и конечно забыл об этом кому-либо сказать. А то и сделал что-то нехорошее намеренно. И вот он ваш дрейф: код и инфраструктура больше не совпадают! :(

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

Как обычно, есть много различных путей добиться желаемого. Например, недавно на горизонте появилась неплохо развивающаяся утилита https://github.com/cloudskiff/driftctl , которая может даже больше, чем предложу Вашему вниманию чуть ниже я, но на момент написания статьи driftctl как минимум не поддерживает работу с aws provider v2, а также не умеет в multi region, что делает его использование невозможным в большинстве серьёзных проектов. Но ребята обещают доделать её через месяц-два.

А пока что опишу и приведу пример небольшого количества кода для следующей очень простой схемы:

1) создаём pipeline, который или по расписанию (в Gitlab можно воспользоваться Pipeline schedules) или по кругу будет делать terraform plan

2) при нахождении дрейфа (diff в плане) pipeline будет, например, отправлять сообщение с его содержанием в Slack.

Аналогично можно реализовать и, например, создание issue в любом из используемых вами репозиториев, где поддерживается их создание через api и любое другое действие, например apply, который вернёт инфраструктуру к её эталонному состоянию. Или всё-таки импортировать изменение в state, если оно действительно необходимо.

Допустим есть репозиторий содержащий код для вашей live инфраструктуры, т.е. код, которому она должна соответствовать и откуда она и была развёрнута с такой структурой:

account_1/ eu-central-1  dev   eks    terragrunt.hcl    values.yaml   s3-bucket       terragrunt.hcl       values.yaml  prod   eks    terragrunt.hcl    values.yaml   s3-bucket       terragrunt.hcl       values.yaml  staging      eks       terragrunt.hcl       values.yaml      s3-bucket          terragrunt.hcl          values.yaml us-east-1  dev   eks    terragrunt.hcl    values.yaml   s3-bucket       terragrunt.hcl       values.yaml  prod   eks    terragrunt.hcl    values.yaml   s3-bucket       terragrunt.hcl       values.yaml  staging      eks       terragrunt.hcl       values.yaml      s3-bucket          terragrunt.hcl          values.yaml terragrunt.hcl

В приведённом выше примере в папке account_1 находятся 2 папки: us-east-1 и eu-central-1 , по имени регионов AWS. Иногда удобно организовать структуру именно так и тогда имена папок можно использовать как значение для передачи в модуль с помощью Terragrunt функции/й, например, таких "${basename(get_terragrunt_dir())}"

Аналогичная логика с папками имеющими в названии окружение и далее идут названия самих компонентов, которых в этом примере 2: eks и s3-bucket

Если смотреть от корня репозитория, то путь до каждого из файлов внутри папки компонента

<account_name>/<region>/<environment>/<component>/*

Т.е. "в общих чертах" */*/*/<component>/*

Выберем, например, компонент s3-bucket (на самом деле конечно можно реализовать это для всего сразу, но бывают нюансы и здесь интересно показать принцип).

Не забудьте подключить Incoming WebHooks в Slack и записать полученный Webhook URL. Делается это так: https://api.slack.com/messaging/webhooks

Тогда вот такой скрипт может выполнять требуемое планирование в pipeline и отправку в Slack diff'а при его нахождении:

#!/bin/bashROOT_DIR=$(pwd)plan () {  echo -e "$(date +'%H-%M-%S %d-%m-%Y') $F"  CURRENT_DIR=$(pwd)  PLAN=$CURRENT_DIR/plan.tfplan  terragrunt run-all plan --terragrunt-non-interactive -lock=false -detailed-exitcode -out=$PLAN 2>/dev/null || ec=$?    case $ec in    0) echo "No Changes Found"; exit 0;;    1) printf '%s\n' "Command exited with non-zero"; exit 1;;    2) echo "Changes Found! Reporting!";          MESSAGE=$(terragrunt show -no-color ${PLAN} | sed "s/\"/'/g");    # let's replace the double quotes from the diff with single as double quotes "break" the payload       curl -X POST --data-urlencode "payload={\"channel\": \"#your-slack-channel-here\", \"username\": \"webhookbot\", \"text\": \"DRIFT DETECTED!!!\n ${MESSAGE}\", \"icon_emoji\": \":ghost:\"}" https://hooks.slack.com/services/YOUR/WEBHOOK/URL_HERE;;  esac}N="$(($(grep -c ^processor /proc/cpuinfo)*4))"    # any number suitable for your situation goes herefor F in */*/*/s3-bucket/*; do  ((i=i%N)); ((i++==0)) && wait    # let's run only N jobs in parallel to speed up the process  cd $ROOT_DIR  cd $F  plan &    # send the job to background to start the new onedone

Меняем что-нибудь руками, запускаем pipeline или ждём его выполнения и радуемся :)

На этом на сегодня всё!

Если Вы решали подобную задачу иначе, есть конкретные замечания/предложения, или просто хочется что-то спросить, то, по мере возможности, готов выслушать либо в комментариях, либо в личке, например, в телеграм @vainkop

P.S. имхо проект https://github.com/cloudskiff/driftctl мне лично кажется действительно полезным и решающим правильную задачу и хороших аналогов ему нет, так что прошу поддержать ребят, а по-возможности внести свою лепту ибо open source.

Всем хорошего настроения!

Подробнее..

Перевод Коммиты это снимки, а не различия

13.04.2021 20:10:28 | Автор: admin

Git имеет репутацию запутывающего инструмента. Пользователи натыкаются на терминологию и формулировки, которые вводят в заблуждение. Это более всего проявляется в "перезаписывающих" историю командах, таких как git cherry-pick или git rebase. По моему опыту, первопричина путаницы интерпретация коммитов как различий, которые можно перетасовать. Однако коммиты это не различия, а снимки! Я считаю, что Git станет понятным, если поднять занавес и посмотреть, как он хранит данные репозитория. Изучив модель хранения данных мы посмотрим, как новый взгляд помогает понять команды, такие как git cherry-pick и git rebase.


Если хочется углубиться по-настоящему, читайте главу о внутренней работе Git (Git internals) книги Pro Git. Я буду работать с репозиторием git/git версии v2.29.2. Просто повторяйте команды за мной, чтобы немного попрактиковаться.

Хеши идентификаторы объектов

Самое важное, что нужно знать о Git-объектах, это то, что Git ссылается на каждый из них по идентификатору объекта (OID для краткости), даёт объекту уникальное имя.

Чтобы найти OID, воспользуемся командой git rev-parse. Каждый объект, по сути, простой текстовый файл, его содержимое можно проверить командой git cat-file -p.

Мы привыкли к тому, что OID даны в виде укороченной шестнадцатеричной строки. Строка рассчитана так, чтобы только один объект в репозитории имел совпадающий с ней OID. Если запросить объект слишком коротким OID, мы увидим список соответствующих подстроке OID.

$ git cat-file -t e0c03error: short SHA1 e0c03 is ambiguoushint: The candidates are:hint: e0c03f27484 commit 2016-10-26 - contrib/buildsystems: ignore irrelevant files in Generators/hint: e0c03653e72 treehint: e0c03c3eecc blobfatal: Not a valid object name e0c03

Блобы это содержимое файлов

На нижнем уровне объектной модели блобы содержимое файла. Чтобы обнаружить OID файла текущей ревизии, запустите git rev-parse HEAD:<path>, а затем, чтобы вывести содержимое файла git cat-file -p <oid>.

$ git rev-parse HEAD:README.mdeb8115e6b04814f0c37146bbe3dbc35f3e8992e0$ git cat-file -p eb8115e6b04814f0c37146bbe3dbc35f3e8992e0 | head -n 8[![Build status](http://personeltest.ru/aways/github.com/git/git/workflows/CI/PR/badge.png)](http://personeltest.ru/aways/github.com/git/git/actions?query=branch%3Amaster+event%3Apush)Git - fast, scalable, distributed revision control system=========================================================Git is a fast, scalable, distributed revision control system with anunusually rich command set that provides both high-level operationsand full access to internals.

Если я отредактирую файл README.md на моём диске, то git status предупредит, что файл недавно изменился, и хэширует его содержимое. Когда содержимое файла не совпадает с текущим OID в HEAD:README.md, git status сообщает о файле как о "модифицированном на диске". Таким образом видно, совпадает ли содержимое файла в текущей рабочей директории с ожидаемым содержимым в HEAD.

Деревья это списки каталогов

Обратите внимание, что блобы хранят содержание файла, но не его имя. Имена берутся из представления каталогов Git деревьев. Дерево это упорядоченный список путей в паре с типами объектов, режимами файлов и OID для объекта по этому пути. Подкаталоги также представлены в виде деревьев, поэтому деревья могут указывать на другие деревья!

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

$ git rev-parse HEAD^{tree}75130889f941eceb57c6ceb95c6f28dfc83b609c$ git cat-file -p 75130889f941eceb57c6ceb95c6f28dfc83b609c  | head -n 15100644 blob c2f5fe385af1bbc161f6c010bdcf0048ab6671ed    .cirrus.yml100644 blob c592dda681fecfaa6bf64fb3f539eafaf4123ed8    .clang-format100644 blob f9d819623d832113014dd5d5366e8ee44ac9666a    .editorconfig100644 blob b08a1416d86012134f823fe51443f498f4911909    .gitattributes040000 tree fbe854556a4ae3d5897e7b92a3eb8636bb08f031    .github100644 blob 6232d339247fae5fdaeffed77ae0bbe4176ab2de    .gitignore100644 blob cbeebdab7a5e2c6afec338c3534930f569c90f63    .gitmodules100644 blob bde7aba756ea74c3af562874ab5c81a829e43c83    .mailmap100644 blob 05f3e3f8d79117c1d32bf5e433d0fd49de93125c    .travis.yml100644 blob 5ba86d68459e61f87dae1332c7f2402860b4280c    .tsan-suppressions100644 blob fc4645d5c08bd005238fc72cfa709495d8722e6a    CODE_OF_CONDUCT.md100644 blob 536e55524db72bd2acf175208aef4f3dfc148d42    COPYING040000 tree a58410edddbdd133cca6b3322bebe4fb37be93fa    Documentation100755 blob ca6ccb49866c595c80718d167e40cfad1ee7f376    GIT-VERSION-GEN100644 blob 9ba33e6a141a3906eb707dd11d1af4b0f8191a55    INSTALL

Деревья дают названия каждому подпункту и также содержат такую информацию, как разрешения на файлы в Unix, тип объекта (blob или tree) и OID каждой записи. Мы вырезаем выходные данные из 15 верхних записей, но можем использовать grep, чтобы обнаружить, что в этом дереве есть запись README.md, которая указывает на предыдущий OID блоба.

$ git cat-file -p 75130889f941eceb57c6ceb95c6f28dfc83b609c | grep README.md100644 blob eb8115e6b04814f0c37146bbe3dbc35f3e8992e0    README.md

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

Само дерево не знает, где внутри репозитория оно находится, то есть указывать на дерево роль объектов. Дерево, на которое ссылается <ref>^{tree}, особое это корневое дерево. Такое обозначение основано на специальной ссылке из вашего коммита.

Коммиты это снапшоты

Коммит это снимок во времени. Каждый содержит указатель на своё корневое дерево, представляющее состояние рабочего каталога на момент снимка.

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

Коммиты также содержат метаданные, которые описывают снимки, например автора и коммиттера (включая имя, адрес электронной почты и дату) и сообщение о коммите. Сообщение о коммите для автора коммита это возможность описать цель коммита по отношению к родителям.

Например, коммит в v2.29.2 в Git-репозитории описывает этот релиз, также он авторизован, а его автор член команды разработки Git.

$ git rev-parse HEAD898f80736c75878acc02dc55672317fcc0e0a5a6/c/_git/git ((v2.29.2))$ git cat-file -p 898f80736c75878acc02dc55672317fcc0e0a5a6tree 75130889f941eceb57c6ceb95c6f28dfc83b609cparent a94bce62b99be35f2ee2b4c98f97c222e7dd9d82author Junio C Hamano <gitster@pobox.com> 1604006649 -0700committer Junio C Hamano <gitster@pobox.com> 1604006649 -0700Git 2.29.2Signed-off-by: Junio C Hamano <gitster@pobox.com>

Заглянув немного дальше в историю при помощи git log, мы увидим более подробное сообщение о коммите, оно рассказывает об изменении между этим коммитом и его родителем.

$ git cat-file -p 16b0bb99eac5ebd02a5dcabdff2cfc390e9d92eftree d0e42501b1cf65395e91e22e74f75fc5caa0286eparent 56706dba33f5d4457395c651cf1cd033c6c03c7aauthor Jeff King &lt;peff@peff.net&gt; 1603436979 -0400committer Junio C Hamano &lt;gitster@pobox.com&gt; 1603466719 -0700am: fix broken email with --committer-date-is-author-dateCommit e8cbe2118a (am: stop exporting GIT_COMMITTER_DATE, 2020-08-17)rewrote the code for setting the committer date to use fmt_ident(),rather than setting an environment variable and letting commit_tree()handle it. But it introduced two bugs:- we use the author email string instead of the committer email- when parsing the committer ident, we used the wrong variable tocompute the length of the email, resulting in it always being azero-length stringThis commit fixes both, which causes our test of this option via therebase "apply" backend to now succeed.Signed-off-by: Jeff King &lt;peff@peff.net&gt; Signed-off-by: Junio C Hamano &lt;gitster@pobox.com&gt;

Круги на диаграммах будут представлять коммиты:

  • Квадраты это блобы. Они представляют содержимое файла.

  • Треугольники это деревья. Они представляют каталоги.

  • Круги это коммиты. Снапшоты во времени.

Ветви это указатели

В Git мы перемещаемся по истории и вносим изменения, в основном не обращаясь к OID. Это связано с тем, что ветви дают указатели на интересующие нас коммиты. Ветка с именем main на самом деле ссылка в Git, она называется refs/heads/main. Файлы ссылок буквально содержат шестнадцатеричные строки, которые ссылаются на OID коммита. В процессе работы эти ссылки изменяются, указывая на другие коммиты.

Это означает, что ветки существенно отличаются от Git-объектов. Коммиты, деревья и блобы неизменяемы (иммутабельны), это означает, что вы не можете изменить их содержимое. Изменив его, вы получите другой хэш и, таким образом, новый OID со ссылкой на новый объект!

Ветки именуются по смыслу, например, trunk [ствол] или my-special-object. Ветки используются, чтобы отслеживать работу и делиться её результатами. Специальная ссылка HEAD указывает на текущую ветку. Когда коммит добавляется в HEAD, он автоматически обновляется до нового коммита ветки. Создать новую ветку и обновить HEAD можно при помощи флага git -c:

$ git switch -c my-branchSwitched to a new branch 'my-branch'$ cat .git/refs/heads/my-branch1ec19b7757a1acb11332f06e8e812b505490afc6$ cat .git/HEADref: refs/heads/my-branch

Обратите внимание: когда создавалась my-branch, также был создан файл (.git/refs/heads/my-branch) с текущим OID коммита, а файл .git/HEAD был обновлён так, чтобы указывать на эту ветку. Теперь, если мы обновим HEAD, создав новые коммиты, ветка my-branch обновиться так, что станет указывать на этот новый коммит!

Общая картина

Посмотрим на всю картину. Ветви указывают на коммиты, коммиты на другие коммиты и их корневые деревья, деревья указывают на блобы и другие деревья, а блобы не указывают ни на что. Вот диаграмма со всеми объектами сразу:

Время на диаграмме отсчитывается слева направо. Стрелки между коммитом и его родителями идут справа налево. У каждого коммита одно корневое дерево. HEAD указывает здесь на ветку main, а main указывает на самый недавний коммит.

Корневое дерево у этого коммита раскинулось полностью под ним, у остальных деревьев есть указывающие на эти объекты стрелки, потому что одни и те же объекты доступны из нескольких корневых деревьев! Эти деревья ссылаются на объекты по их OID (их содержимое), поэтому снимкам не нужно несколько копий одних и тех же данных. Таким образом, объектная модель Git образует дерево хешей.

Рассматривая объектную модель таким образом, мы видим, почему коммиты это снимки: они непосредственно ссылаются на полное представление рабочего каталога коммита!

Вычисление различий

Несмотря на то, что коммиты это снимки, мы часто смотрим на коммит в его историческом представлении или видим его на GitHub как diff. На самом же деле сообщение о коммите часто ссылается на различие. генерируемое динамически из данных снимка путём сравнения корневых деревьев коммита и его родителя. Git может сравнить не только соседние снимки, но и два любых снимка вообще.

Чтобы сравнить два коммита, сначала рассмотрите их корневые деревья, которые почти всегда отличаются друг от друга. Затем в поддеревьях выполните поиск в глубину, следуя по парам, когда пути для текущего дерева имеют разные OID.

В примере ниже корневые деревья имеют разные значения для docs, поэтому мы рекурсивно обходим их. Эти деревья имеют разные значения для M.md, таким образом, два блоба сравниваются построчно и отображается их различие. Внутри docs N.md по-прежнему тот же самый, так что пропускаем их и возвращаемся к корневому дереву. После этого корневое дерево видит, что каталоги things имеют одинаковые OID, так же как и записи README.md.

На диаграмме выше мы заметили, что дерево things не посещается никогда, а значит, не посещается ни один из его достижимых объектов. Таким образом, стоимость вычисления различий зависит от количества путей с разным содержимым.

Теперь, когда понятно, что коммиты это снимки, можно динамически вычислять разницу между любыми двумя коммитами. Почему тогда этот факт не общеизвестен? Почему новые пользователи натыкаются на идею о том, что коммит это различие?

Одна из моих любимых аналогий дулизм коммитов как дуализм частиц, при котором иногда коммиты рассматриваются как снимки, а иногда как различия. Суть дела в другом виде данных, которые не являются Git-объектами в патчах.

Подождите, а что такое патч?

Патч это текстовый документ, где описывается, как изменить существующую кодовую базу. Патчи это способ самых разрозненных команд делиться кодом без коммитов в Git. Видно, как патчи перетасовываются в списке рассылки Git.

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

Git может преобразовать коммит в патч командой git format-patch. Затем патч может быть применён к Git-репозиторию командой git application. В первые дни существования открытого исходного кода такой способ обмена доминировал, но большинство проектов перешли на обмен коммитами непосредственно через пул-реквесты.

Самая большая проблема с тем, чтобы делиться исправлениями в том, что патч теряет родительскую информацию, а новый коммит имеет родителя, который одинаков с вашим HEAD. Более того, вы получаете другой коммит, даже если работаете с тем же родителем, что и раньше, из-за времени коммита, но при этом коммиттер меняется! Вот основная причина, по которой в объекте коммита Git есть разделение на "автора", и "коммиттера".

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

Идея перемещения патчей с места на место перешла в несколько команд Git как "перемещение коммитов". На самом же деле различие коммитов воспроизводится, создавая новые коммиты.

Если коммиты это не различия, что делает git cherry-pick?

Команда [git cherry-pick создаёт новый коммит с идентичным отличием от <oid>, родитель которого текущий коммит. Git в сущности выполняет такие шаги:

  1. Вычисляет разницу между <oid> коммита и его родителя.

  2. Применяет различие к текущему HEAD.

  3. Создаёт новый коммит, корневое дерево которого соответствует новому рабочему каталогу, а родитель созданного коммита HEAD.

  4. Перемещает ссылку HEAD в этот новый коммит.

После создания нового коммита вывод git log -1 -p HEAD должен совпадать с выводом git log -1 -p <oid>.

Важно понимать, что мы не "перемещали" коммит так, чтобы он был поверх нашего текущего HEAD, мы создали новый коммит, и его вывод diff совпадает со старым коммитом.

А что делает git rebase?

Команда git rebase это способ переместить коммиты так, чтобы получить новую историю. В простой форме это на самом деле серия команд git cherry-pick, которая воспроизводит различия поверх другого, отличного коммита.

Самое главное: git rebase <target> обнаружит список коммитов, доступных из HEAD, но недоступных из <target>. С помощью команды git log --online <target>...HEAD вы можете отобразить их самостоятельно.

Затем команда rebase просто переходит в местоположению <target> и выполняет команды git cherry-pick в этом диапазоне коммитов, начиная со старых. В конце мы получили новый набор коммитов с разными OID, но схожих с первоначальным диапазоном.

Для примера рассмотрим последовательность из трёх коммитов в текущей ветке HEAD с момента разветвления target. При запуске git rebase target? чтобы определить список коммитов A, B, и C, вычисляется общая база P. Затем поверх target они выбираются cherry-pick, чтобы создать новые коммиты A', B' и C'.

Коммиты A', B' и C' это совершенно новые коммиты с общим доступом к большому количеству информации через A, B и C, но они представляют собой отдельные новые объекты. На самом деле старые коммиты существуют в вашем репозитории до тех пор, пока не начнётся сбор мусора.

С помощью команды git range-diff мы даже можем посмотреть на различие двух диапазонов коммитов! Я использую несколько примеров коммитов в репозитории Git, чтобы сделать rebase на тег v2.29.2, а затем слегка изменю описание коммита.

$ git checkout -f 8e86cf65816$ git rebase v2.29.2$ echo extra line >>README.md$ git commit -a --amend -m "replaced commit message"$ git range-diff v2.29.2 8e86cf65816 HEAD1:  17e7dbbcbc = 1:  2aa8919906 sideband: avoid reporting incomplete sideband messages2:  8e86cf6581 ! 2:  e08fff1d8b sideband: report unhandled incomplete sideband messages as bugs    @@ Metadata     Author: Johannes Schindelin <Johannes.Schindelin@gmx.de>           ## Commit message ##    -    sideband: report unhandled incomplete sideband messages as bugs    +    replaced commit message         -    It was pretty tricky to verify that incomplete sideband messages are    -    handled correctly by the `recv_sideband()`/`demultiplex_sideband()`    -    code: they have to be flushed out at the end of the loop in    -    `recv_sideband()`, but the actual flushing is done by the    -    `demultiplex_sideband()` function (which therefore has to know somehow    -    that the loop will be done after it returns).    -    -    To catch future bugs where incomplete sideband messages might not be    -    shown by mistake, let's catch that condition and report a bug.    -    -    Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>    -    Signed-off-by: Junio C Hamano <gitster@pobox.com>    + ## README.md ##    +@@ README.md: and the name as (depending on your mood):    + [Documentation/giteveryday.txt]: Documentation/giteveryday.txt    + [Documentation/gitcvs-migration.txt]: Documentation/gitcvs-migration.txt    + [Documentation/SubmittingPatches]: Documentation/SubmittingPatches    ++extra line           ## pkt-line.c ##     @@ pkt-line.c: int recv_sideband(const char *me, int in_stream, int out)

Обратите внимание: результирующий range-diff утверждает, что коммиты 17e7dbbcbc и 2aa8919906 "равны", а это означает, что они будут генерировать один и тот же патч. Вторая пара коммитов различается: показано, что сообщение коммита изменилось, есть правка в README.md, которой не было в исходном коммите.

Если пройти вдоль дерева, вы увидите, что история коммитов всё ещё существует у обоих наборов коммитов. Новые коммиты имеют тег v2.29.2 в истории это третий коммит, тогда как старые имеют тег v2.28.0 болеее ранний, а в истории он также третий.

$ git log --oneline -3 HEADe08fff1d8b2 (HEAD) replaced commit message2aa89199065 sideband: avoid reporting incomplete sideband messages898f80736c7 (tag: v2.29.2) Git 2.29.2$ git log --oneline -3 8e86cf658168e86cf65816 sideband: report unhandled incomplete sideband messages as bugs17e7dbbcbce sideband: avoid reporting incomplete sideband messages47ae905ffb9 (tag: v2.28.0) Git 2.28

Если коммиты не отличия, тогда как Git отслеживает переименования?

Внимательно посмотрев на объектную модель, вы заметите, что Git никогда не отслеживает изменения между коммитами в сохранённых объектных данных. Можно задаться вопросом: "Откуда Git знает, что произошло переименование?"

Git не отслеживает переименования. В нём нет структуры данных, которая хранила бы запись о том, что между коммитом и его родителем имело место переименование.

Вместо этого Git пытается обнаружить переименования во время динамического вычисления различий. Есть два этапа обнаружения переименований: именно переименования и редактирования.

После первого вычисления различий Git исследует внутренние различия, чтобы обнаружить, какие пути добавлены или удалены. Естественно, что перемещение файла из одного места в другое будет выглядеть как удаление из одного места и добавление в другое. Git попытается сопоставить эти действия, чтобы создать набор предполагаемых переименований.

На первом этапе этого алгоритма сопоставления рассматриваются OID добавленных и удалённых путей и проверяется их точное соответствие. Такие точные совпадения соединяются в пары.

Вторая стадия дорогая часть вычислений: как обнаружить файлы, которые были переименованы и отредактированы? Посмотреть каждый добавленный файл и сравните этот файл с каждым удалённым, чтобы вычислить показатель схожести в процентах к общему количеству строк. По умолчанию что-либо, что превышает 50 % общих строк, засчитывается как потенциальное редактирование с переименованием. Алгоритм сравнивает эти пары до момента, пока не найдёт максимальное совпадение.

Вы заметили проблему? Этот алгоритм прогоняет A * D различий, где A количество добавлений и D количество удалений, то есть у него квадратичная сложность! Чтобы избежать слишком долгих вычислений по переименованию, Git пропустит часть с обнаружением редактирований с переименованием, если A + D больше внутреннего лимита. Ограничение можно изменить настройкой опции diff.renameLimit в конфигурации. Вы также можете полностью отказаться от алгоритма, просто отключив diff.renames.

Я воспользовался знаниями о процессе обнаружения переименований в своих собственных проектах. Например, форкнул VFS for Git, создал проект Scalar и хотел повторно использовать большое количество кода, но при этом существенно изменить структуру файла. Хотелось иметь возможность следить за историей версий в VFS for Git, поэтому рефакторинг состоял из двух этапов:

  1. Переименовать все файлы без изменения блобов.

  2. Заменить строки, чтобы изменить блобы без изменения файлов.

Эти два шага позволили мне быстро выполнить git log --follow -- <path>, чтобы посмотреть историю переименовывания.

$ git log --oneline --follow -- Scalar/CommandLine/ScalarVerb.cs4183579d console: remove progress spinners from all commands5910f26c ScalarVerb: extract Git version check...9f402b5a Re-insert some important instances of GVFS90e8c1bd [REPLACE] Replace old name in all filesfb3a2a36 [RENAME] Rename all filescedeeaa3 Remove dead GVFSLock and GitStatusCache codea67ca851 Remove more dead hooks code...

Я сократил вывод: два этих последних коммита на самом деле не имеют пути, соответствующего Scalar/CommandLine/ScalarVerb.cs, вместо этого отслеживая предыдущий путь GVSF/GVFS/CommandLine/GVFSVerb.cs, потому что Git распознал точное переименование содержимого из коммита fb3a2a36 [RENAME] Rename all files.

Не обманывайтесь больше

Теперь вы знаете, что коммиты это снапшоты, а не различия! Понимание этого поможет вам ориентироваться в работе с Git.

И теперь мы вооружены глубокими знаниями объектной модели Git. Не важно, какая у вас специализация, frontend, backend, или вовсе fullstack вы можете использовать эти знания, чтобы развить свои навыки работы с командами Git'а или принять решение о рабочих процессах в вашей команде. А к нам можете приходить за более фундаментальными знаниями, чтобы иметь возможность повысить свою ценность как специалиста или вовсе сменить сферу.

Узнайте, как прокачаться в других специальностях или освоить их с нуля:

Другие профессии и курсы
Подробнее..

Представляем OpenShift Pipelines

27.05.2021 16:21:56 | Автор: admin

3 мая 2021 года Red Hat выпустила первую общедоступную версию OpenShift Pipelines, облачно-ориентированной системы непрерывной интеграции на базе СПО-проекта Tekton. Решение реализует Kubernetes фреймворк CI/CD для разработки и выполнения конвейеров, в которых каждый шаг запускается в своем собственном контейнере, что позволяет масштабировать шаги независимо друг от друга. Сегодня мы вкратце рассмотрим ключевые особенности и преимущества этого решения, а также приведем список дополнительных ресурсов для дальнейшего знакомства с ней и освоения.

Но, прежде чем переходить к OpenShift Pipelines, освежим в памяти основные концепты Tekton.

Основные концепты Kubernetes-нативной CI/CD

OpenShift Pipelines дополняет Kubernetes/OpenShift соответствующими CRD (ресурсами, определяемыми пользователем) для концептов CI/CD, таких как конвейер (pipeline), задача (task), шаг (step). В результате, эти концепты становятся своими (native) инстанцируемыми их можно создавать в виде отдельных экземпляров и, как следствие, полноценно масштабировать и развертывать, а также обеспечивать их безопасность средствами Kubernetes.

Поэтому для начала вспомним, что такое концепты Tekton:

Рис. 1. КонцептыTektonРис. 1. КонцептыTekton

По сути, основные концепты Tekton делятся на два вида: те, что задают конвейер, и те, что запускают конвейер.

Концепты, задающие конвейер (define pipeline)

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

  • Pipeline описание конвейера и задач (Task), которые он должен выполнять.

Концепты, запускающие конвейер (run pipelines)

  • TaskRun запуск и результаты выполнения экземпляра Task.

  • PipelineRun запуск и результаты выполнения экземпляра конвейера, который включает в себя ряд концептов TaskRun.

Подробнее об этих концептах можно почитать в официальной документации.

Теперь разберемся, что такое OpenShift Pipelines и для чего он нужен

Что такого особенного в OpenShift Pipelines?

OpenShift Container Platform это ведущая отраслевая Kubernetes-платформа корпоративного класса, которая предоставляет разработчикам множество функций, среди которых есть и CI/CD.

OpenShift Pipelines базируется на СПО-проекте Tekton и расширяет функционал платформы OpenShift стандратными методами, что сильно облегчает жизнь разработчикам.

Установка OpenShift Pipelines через механизм Operator

OpenShift Pipelines поддерживается на уровне механизма операторов, поэтому он легко устанавливается и обновляется, и, соответственно, легко администрируется.

OpenShift Pipelines доступен на сайте OperatorHub, где представлены более 450 различных операторов для OpenShift Container Platform:

Установка OpenShift Pipelines предельно проста, и он сразу устанавливается как оператор уровня кластера, автоматически становясь доступным для всех проектов:

OpenShift Pipelines также дополняет OpenShift соответствующими CR, которые позволят добиться еще большей универсальности в плане конфигурации и интеграции с консолью OpenShift и т.д.

При появлении в OperatorHub новой версии OpenShift Pipelines, вы как администратор можете выбрать обновление до следующей версии, задав нужный канал обновления.

Развитый UI в рамках консоли OpenShift

Tekton тоже дополняет стандартную поставку OpenShift концептами CI/CD, но в нем при создании и запуске конвейеров сложно обойтись без создания YAML-кода, и этого кода требуется писать очень много, тысячи строк. Поэтому Red Hat реализовала в консоли OpenShift полноценный UI для запуска и визуализации конвейеров (как тех, что работают сейчас, так и тех, что уже отработали), а также для графического создания конвейеров. При этом все необходимые YAML-файлы создаются автоматически, без написания какого-либо кода.

Ниже показано графическое представление конвейера, который уже был выполнен на платформе OpenShift, причем прямо из него можно получить доступ к журналам и событиям всех задач этого конвейера:

Рис. 2. Конвейеры в консоли OpenShiftРис. 2. Конвейеры в консоли OpenShift

При желании можно легко просмотреть полный лог выбранной задачи:

Чтобы еще больше облегчить жизнь разработчикам, OpenShift Pipelines позволяет рисовать конвейеры прямо в консоли OpenShift, поэтому вам больше не нужен черный пояс по YAML, чтобы создать свой первый конвейер Tekton:

Рис. 3. Графическое проектирование конвейера в консоли OpenShiftРис. 3. Графическое проектирование конвейера в консоли OpenShift

Но если вы, как обладатель черного пояса по YAML, захотите что-то подправить, это всегда можно сделать прямо из консоли OpenShift:

Рис. 4. YAML примеры и снипеты в консоли OpenShiftРис. 4. YAML примеры и снипеты в консоли OpenShift

Более того, OpenShift Pipelines пригодится, даже если вы решите пойти по пути чистого YAML, и предложит вам готовые примеры и снипеты кода для более быстрой разработки конвейеров YAML. Кроме того, в систему можно интегрировать ваши корпоративные снипеты, сделав их доступными всем разработчикам. Именно для этого мы и добавили специальный CRD под названием ConsoleYAMLSamples.

События как триггеры для запуска конвейеров

Хотите увязать запуск конвейеров с некими внешними событиями (в терминах Tekton это называется Trigger), например, push- или pull-запросами Github или Gitlab? Вообще не проблема, в OpenShift Pipelines это есть из коробки, причем поддерживаются различные вендоры, включая Github, Gitlab, BitBucket и т.д.

Рис. 5. Добавление триггера в консоли OpenShiftРис. 5. Добавление триггера в консоли OpenShift

Вы просто создаете нужный триггер в UI, а OpenShift сам формирует все необходимые концепты, такие как EventListeners, TriggerTemplates (подробнее о них можно почитать в официальной документации).

Готовые повторно используемые задачи и кастомизация

OpenShift Pipelines из коробки содержит десятки готовых задач, которые можно использовать в составе конвейеров, включая задачи по клонированию кода из репозитория, сборки приложений на различных языках программирования, таких как java, dotnet core, python go, nodejs или использования инструментов сборки вроде maven, развертывания приложений и т.д. Список доступных задач можно увидеть в консоли OpenShift, раздел ClusterTasks, меню Pipelines -> Tasks:

Рис. 6. OpenShift Pipelines из коробки предлагает десятки готовых задачРис. 6. OpenShift Pipelines из коробки предлагает десятки готовых задач

Кроме того, этот список можно легко расширить. Для этого достаточно добавить ClusterTasks в кластер, после чего вам станут доступны сотни дополнительных задач на сайте TektonHub, который представляет собой публичный репозиторий для обмена задачами Tekton:

Рис. 7.TektonHub публичный репозиторий повторно используемых задач и конвейеров TektonРис. 7.TektonHub публичный репозиторий повторно используемых задач и конвейеров Tekton

Интеграция с IDE

Разработчики, использующие командную строку и IDE, могут воспользоваться преимуществами Tekton CLI, расширения Tekton для Visual Studio Code и плагина Tekton для IntelliJ, чтобы взаимодействовать с конвейерами прямо из своей обычной среды разработки и создавать, запускать, просматривать и выполнять действия на кластере непосредственно из командной строки.

Рис. 8. Расширение VSCode для OpenShift PipelinesРис. 8. Расширение VSCode для OpenShift Pipelines

Полезные ресурсы

В заключение советуем посмотреть видеоверсию этой статьи на английском:

А также рекомендуем следующие ресурсы (EN):

Видеоролики на русском:

Вебинары:

Подробнее..

Делаем телеграм бота за 5 минут быстрый старт с продвинутым шаблоном

27.03.2021 00:13:02 | Автор: admin

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

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

Также стоит отметить, что хостинг в этом примере бесплатный, но его хватает для разумной нагрузки.

А еще сразу скажу, что далее будет все на питоне... Вот... Сказал. Не буду больше ходить вокруг да около, у нас всего 5 минут (помните, да?). Приступим!

Пошаговая инструкция

1) Создаем репозиторий на гитхабе из моего шаблона

2) Регистрируемся на Heroku

3) Создаем новое приложение

4) Привязываем наш репозиторий к проекту на Heroku

5) Настраиваем автоматический deployment

6) Смотрим на адрес, где будет висеть наш бот

7) Настраиваем переменные среды

KEY

VALUE

BOT_TOKEN

Токен для бота

WEBHOOK_TOKEN

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

ADMIN_PASSWORD

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

HOST

Адрес полученный в пункте 6 (например fancy-panda.herokuapp.com). Обратите внимание на формат!

IS_PRODUCTION

True

LOG_BOT_TOKEN

Токен для бота, куда будут отправляться логи

ADMIN_ID

user_id, куда будут отправляться логи (получить в боте @userinfobot)

8) Собираем наше приложение и ждем пока оно запустится

9) Заходим по адресу из пункта 6 и добавляем к ссылке пароль. Получиться что-то такое: fancy-panda.herokuapp.com/?password=<ADMIN_PASSWORD>

10) Устанавливаем webhook, переходя по ссылке на подобие fancy-panda.herokuapp.com/set_webhook?password=<ADMIN_PASSWORD>

Тестируем

Теперь, когда мы закончили все настраивать, пора посмотреть, что же мы "натворили".

Посмотреть в живую можно тут.

Пример работы из коробкиПример работы из коробкиПример работы логированияПример работы логирования

Добавляем функционал

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

@bot.message_handler(commands=["id"])def get_id(message):    logger.info(f'</code>@{message.from_user.username}<code> used /id')    bot.send_message(message.chat.id, f"user_id = {message.chat.id}")

Думаю, дальше ограничивает вас только воображение... (ну почти)

Применение в проектах

Все любят, когда есть примеры работы. На основе этого шаблона я сделал бота wifi_qr_bot, который генерирует QR-коды для подключения к WiFi. Это упрощает жизнь, ведь пароль у вас длинный (безопасность, все дела), а вводить его на каждом новом устройстве вам лень.

Выводы

Вот мы и сделали нашего бота, который хоститься в облаке. Он уже многое умеет в плане логирования. Для логирования я написал отдельную библиотеку, tg-logger. Если интересно, как она работает, то потыкайте в демо бота. Если все еще интересно, прочитайте мою статью. Такие пироги с котятками...

Ссылки

Подробнее..

Шаблонизация PDF

18.04.2021 20:08:22 | Автор: admin
image
Хабрахабр, уважаемые коллеги!

Проблема впечатывания данных в pdf документ не нова, не я первый и не я последний кто с ней сталкивается, поэтому решил поделиться опытом решения и заодно представить вашему вниманию небольшое веб приложение по этой теме.
1. pdf формат хорош тем, что он не редактируемый. Во всяком случае рядовой пользователь вряд-ли будет заниматься внесением правок в документ pdf. И значит формат pdf хорошо подходит для обмена юридически значимыми документами.
2. pdf формат плох тем, что он не редактируемый :) Т.к. шаблонизация, заполнение набором данных бланка документа pdf в автоматическом режиме затруднена, а в ручном режиме требуется установка платных, тяжеловесных приложений.

Меня, как программиста, беспокоит прежде всего 2-й пункт.
Как в программном приложении впечатать необходимый набор данных в документ pdf?

Очередное гугление на эту тему не принесло результатов.
Удалось нагуглить только, что с впечатыванием всё плохо (Почему так сложно извлекать текст из PDF?, PDF с точки зрения программиста) и есть вариант шаблонизировать сначала docx файл, это сделать не сложно (Заполняем документы в Microsoft Word...), а затем преобразовать в консольном libreoffice (librewrite) docx файл в pdf. Это всё можно сделать автоматически, из приложения.
Но во-первых, такое решение означает, что проект будет иметь тяжёлую зависимость от libreoffice.
А во-вторых, при преобразовании docx в pdf в libreoffice вид документа получается немного не таким, как он смотрится в word, и/или pdf сгенерированном в word из docx файла.

Перейдём наконец к сути рассматриваемого решения. Конечно шаблонизация в данном случае слово громкое, но предлагаемое решение вполне годное и полезное.
На python (и на php) есть несколько библиотек (не сложно загуглить), которые позволяют впечатывать строки и картинки в pdf файлы, мы используем pdfrw + reportlab.Canvas. Т.е., в принципе впечатать данные нет проблем, проблема у этих библиотек в том, что для каждого поля нужно задать свои точные координаты в документе, а это значит, что
1. Нужен какой-то унифицированный функционал, который хранил бы координаты полей не внутри исходного кода, а в отдельном файле. Сразу уточню, что по опыту рекомендую хранить эти координаты в файлах и под контролем версий, т.е. коммитить координаты вместе с соответствующими pdf бланками и методами, генерирующими тот или иной комплект документов. И не засовывать эти координаты в базу данных, т.к. это затруднит откат к предыдущим версиям (координатам) документов, если возникнет такая необходимость. Тут вроде бы всё понятно.
2. Эти координаты надо как-то вычислить, а это грустное занятие, если делать это вручную.
Тут основная идея заключается в том, чтобы в браузере создать перемещаемые div элементы, с помощью мышки настроить их положение в нужное место документа и получившиеся в браузере координаты элементов сохранить в файл на бэкенде.
Собственно эти два пунка и реализованы в приложении

Способ приминения


Похоже, что получилось небольшое веб приложение с фронтэндом и бэкендом, т.е. оформить его в качестве python пакета, пожалуй не получится.

1. Скачиваем с гита исходники

2. Устанавливаем зависимости

3. Читаем README.md (устанавливаем и настраиваем nginx для статических файлов)

4. В папке documents создаём подпапку с именем документа, который нужно генерировать и внутри этой подпапки создаём два файла и (если необходимо) одну дирректорию с картинками:
form.pdf # бланк документа в который надо впечатывать данные
fields.json # параметры полей, которые необходимо впечатывать
images # не обязательно, набор картинок, которые необходимо впечатать
Рекомендую также сохранить исходный docx файл (если имеется), который не участвует в генерации документа, но пригодится при необходимости внести изменения и перегенерировать бланк документа pdf
form.docx # не обязательно, имя любое
Файл fields.json имеет следующую структуру, например:

{    "0": [        [32.25, 710.25, "fio", "DejaVuSans", 12, 420],        [425.25, 681.75, "gender", "DejaVuSans", 12, 18],        [206.25, 681.75, "birth_date", "DejaVuSans", 12, 173],        [462.75, 681.53, "foto.jpg", "DejaVuSans", 12, 92],        [146.25, 665.25, "birth_place", "DejaVuSans", 12, 418],        [228.0, 634.5, "registration", "DejaVuSans", 12, 340]    ],    "1": [        [132.0, 720.76, "1_work", "DejaVuSans", 10, 260],        [132.0, 697.51, "2_work", "DejaVuSans", 10, 260],        [132.0, 673.51, "3_work", "DejaVuSans", 10, 141]    ]}


Добавление/удаление строк в этот файл добавляет/удаляет поля, впечатываемые в бланк

5. Открываем страницу для настроек полей (http://personeltest.ru/away/127.0.0.1/tpdf/positioning?pdf_name=ZayavlenieNaZagranpasport&page_num=1)

6. Настраиваем положение полей с помощью мышки в браузере и сохраняем это положение

7. Мышкой не всегда точно удаётся установить нужное положение полей, чтобы подровнять положение полей можно открыть файл fieldd.json и поправить координаты вручную. Данные в файле упорядочены по координате Y и каждое поле хранится в своей строке файла. Т.е. файл с координатами полей отформатирован аккуратно, что позволяет вручную, легко вносить необходимые корректировки.

8. Создаём ещё один метод для печати данного типа документа (если нужно как-то подготовить исходные данные и/или взять их не из фронта, а из бэкэнда).

9. Если всё в порядке, то коммитим получившийся набор данных fields.json и файлы (только не ко мне на гит, а в свой локальный гит, хотя, если документ может кому-то ещё пригодиться, то можно и публичный банк документов собрать, это идея).
Полученный файл с координатами можно использовать в другом проекте, на другом языке программирования, например php, ведь координаты в файле записаны в единицах измерения (поинты) которые используются в pdf файлах.

Если у вас проект на python, то исходники данного приложения можно просто внедрить в проект и через использование основного класса Tpdf генерировать pdf в любом удобном месте кода.

Часто нужно сгенерировать не просто один документ из нескольких страниц, а собрать в один pdf файл несколько документов, каждый из которых должен быть напечатан в нужном порядке и некоторые из них более одного раза. В основном классе данного приложения имеется для этих нужд специальный метод, который генерирует комплект документов, смотрите обработку метод /tpdf/example/.

Данные в основной класс нужно передавать при его инстансцинировании. Основной класс можно расширять свойствами (@property), которые будут вычисляться на основе входных данных и вставляться в pdf по имени свойства = имени поля. Так в примере выводится поле fio, а данные передаются last_name, first_name, middle_name

Можно развернуть это небольшое приложение в качестве самостоятельного сервиса, а все остальные приложения окружения будут к нему обращаться за нужным документом по сети, но тогда появятся расходы времени передачи по сети, файлы pdf не слишком лёгкие, сама генерация документов происходит быстро.

Вместо сотни слов, иногда лучше посмотреть видео инструкцию (звук не записывал).


Опыт реализации (грабли).


1. Сначала я реализовал это небольшое приложение на библиотеке PyPDF2, но комплект документов из 28 страниц генерировался 3 секунды, как-то долго. Тогда, чтобы ускорить генерацию документов, я решил попробовать мультипоточность, выделив генерацию каждой страницы в отдельный поток, однако это усложнило код но, на удивление, не дало выигрыша производительности, плюс возникли дополнительные ошибки видимо из-за конфликтов процессов. Тогда я попробовал многопроцессность, однако результат оказался тот же производительность не выросла а в некоторых конфигурациях кода даже ухудшалась. Наконец я решил проверить быстродействие другой, аналогичной библиотеки pdfrw под которую, оказалось, почти не пришлось переписывать код и она заработала почти на порядок быстрее без всякой мультипоточности и мультипроцессности. Т.е. комплект документов из 28 страниц сгенерировался за 0.3 секунды. Не зависимо от библиотеки код изначально оптимизировал с точки зрения повторной генерации страниц: каждая страница заполняется данными один раз и хранится в памяти, и если она должна быть напечатана несколько раз, то первый раз она генерируется, а последующие разы берётся готовая из памяти.
2. Листание страниц лучше делать не на ajax, так как, чтобы подтянулись новые поля всё равно нужно перезагружать всю страницу.
3. Было много возни с преобразованием координат с пикселей фронта в поинты pdf. В итоге, опытным путём и путём гугления выяснилось, что отношение фронтовые координаты нужно умножать на 3/4, чтобы получить координаты документа pdf. Обратное преобразование, соответственно, наоборот.

Нужно сделать (TODO)


1. Добавление и удаление новых полей с фронта.
Сейчас, чтобы добавить/удалить поле необходимо добавить/удалить строку в/из файл(а) fields.json
2. Точное позиционирование полей на фронте
3. Разобраться с шрифтами, сейчас доступен всего один шрифт, поддерживающий русский алфавит.
4. Общий метод, принимающий на вход набор данных и возвращающий pdf документ.
5. Общий метод, принимающий на вход набор данных и возвращающий комплект документов.
Подробнее..

Перевод Turbolift инструмент для масштабного рефакторинга

08.05.2021 16:18:41 | Автор: admin

Системы Skyscanner сложно назвать маломасштабными. Наш сайт и приложение каждый месяц используются миллионами путешественников, мы обрабатываем умопомрачительные объёмы запросов, используя микросервисную архитектуру, которая сама по себе далеко не маленькая.В общей совокупности у нас задействовано несколько сотен микросервисов и микросайтов (веб-приложений, поддерживающих определённую часть нашего сайта), обслуживаемых сотнями экземпляров AWS Lambda и библиотек. Каждое из этих средств хранится в своём собственном репозитории GitHub, что даёт некоторые преимущества с точки зрения разделения задач, но имеет и свою цену: когда одно и то же изменение нужно выполнить во всех этих репозиториях, как это можно осуществить?


Большинство наших микросервисов используют общие разделяемые библиотеки, поэтому обновление для установки нового исправления безопасности, повышения устойчивости или добавления функции наблюдения (например) часто является относительно простой задачей, выполняемой сервисом Dependabot.

Однако отнюдь не каждое изменение, которое требуется внести, выполняется в библиотеке. Несмотря на все наши усилия, у нас всё ещё остаются шаблонные (boilerplate) конфигурации и код, который время от времени необходимо совершенствовать. И, хотя мы сокращаем число репозиториев там, где это возможно (в том числе благодаря объединению репозиториев, когда оно имеет смысл), у нас всё ещё остается множество репозиториев.

Нам необходимо иметь возможность выполнения достаточно сложных изменений одновременно в десятках и сотнях репозиториев.

Долгое время мы разрабатывали свою внутреннюю систему под названием Codelift. В первую очередь это была система пакетной обработки, которая в ночное время применяла написанный на Python сценарий изменения для каждого из сотен репозиториев, отправляя предложения на изменения кода в чужих репозиториях (PR-предложения) для всех таких изменений. Но, как оказалось, очень сложно написать такой сценарий, который бы надёжно отрабатывал со всеми репозиториями. Главным узким местом была потребность в квалифицированных специалистах, которые требовались для проверки этих сценариев изменений. И самим сценариям часто требовалось несколько раундов настройки, чтобы преодолеть неизбежные сбои. Система Codelift постепенно выводилась из эксплуатации, но потребность в ней оставалась.

Появление Turbolift

Система Turbolift это переосмысление процесса внесения массовых изменений.

  • Прежде, чтобы написать надёжно работающий сценарий внесения изменения в системе Codelift, инженерам приходилось создавать локальные копии (клоны) многих или даже всех задействованных репозиториев только для проверки работоспособности этого изменения. Но если инженеры в любом случае собираются создавать локальные копии репозиториев, почему не сделать это частью процесса?

  • Подготовка сценариев изменений на Python накладывала свои ограничения: иногда самым простым способом реализации изменения является просто вызов команды из оболочки или запуск более специализированного инструмента рефакторинга, такого как codemod или comby. Иногда предпочтителен вызов редактора или интегрированной среды разработки это будет хоть и тяжеловесным, но самым верным способом. А иногда самым простым вариантом выполнения будет автоматическое изменение, которое сработает для 95 % репозиториев с последующей ручной настройкой для нескольких репозиториев, где такая настройка потребуется.

  • Наличие сценариев изменения само по себе полезно только в том случае, если планируется повторно выполнять такую же операцию массового рефакторинга. Но во многих случаях можно с полной уверенностью констатировать, что это разовые изменения. И, хотя очень важно протоколировать то, что мы делали, совсем не обязательно выполнять это в форме сценария для многократного применения.

  • Одной из тонких проблем с Codelift было то, что все его PR-предложения исходили от пользователя-бота: при этом от владельцев системы Codelift ожидалась тщательная проверка каждого изменения, и это становилось серьёзным узким местом. Мы поняли, что оптимальным будет создание предложений на изменения кода в чужих репозиториях инженером, который фактически несёт ответственность за их выполнение. В этом случае обеспечиваются прозрачность распределения прав, более простая обратная связь и отсутствие необходимости создавать целую команду посредников.

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

Создание клонов на машинах разработчиков имеет очевидные минусы: для этого требуется время и дисковое пространство. Но, на наш взгляд, снижение информационной нагрузки на инженеров нивелирует эти издержки.

Система Turbolift начинала жизнь как наскоро написанный набор bash-сценариев, но она быстро доказала нам свою полезность. Теперь, когда мы переписали эту систему на Go, привели в порядок и сделали её инструментом с открытым исходным кодом, хотелось бы поделиться ею с вами. По сравнению с первоначальной версией язык Go помог сделать этот инструмент более удобным для использования и обслуживании в долгосрочной перспективе. У нас есть множество идей о дальнейшем развитии этого инструмента, и мы приветствуем все поступающие от вас предложения о том, как улучшить его.

Если вы начнёте работать с Turbolift, советуем вам уделить особое внимание потребностям инженеров, анализирующих предложения на изменения кода в чужих репозиториях, особенно, если создаётся много таких PR-предложений. В файле README для этого проекта содержится несколько рекомендаций, которые мы разработали внутрикорпоративно, чтобы помочь авторам изменений, удерживая их в рамках разумного.

Как инструмент Turbolift помог нам

  • При приближении момента, когда истечёт срок действия какого-либо внутреннего SSL-сертификата, наша команда сопровождения промышленной платформы использовала Turbolift для выполнения PR-предложений сотен репозиториев, в которых были ссылки на истекающий сертификат.

  • Turbolift применяется нашей командой веб-поддержки для стандартизации версий и тестирования библиотек на наших микросайтах.

  • Наша команда сопровождения промышленной платформы использовала Turbolift для исправления ошибки, которую когда-то допустили в шаблоне кода, а затем растиражировали по множествам репозиториев.

  • Региональные команды смогли очистить и обновить файлы с метаданными репозиториев, в которых отслеживались информация о владельцах и прочие сведения. Обновление этих файлов и раньше было рутинной, но необходимой работой, поскольку региональные команды меняли названия или менялись владельцы репозиториев.

В целом за последние три месяца, используя Turbolift, мы отправили свыше 1200 внутренних предложений на изменения кода в чужих репозиториях. Каждый из этих случаев означает устранённую проблему или исправленную техническую недоработку, которые в противном случае превратились бы в создаваемые вручную PR-предложения. Мы надеемся, что инженеры в Skyscanner и других компаниях в полной мере ощутят преимущества от упрощения рабочего процесса при выполнении масштабных изменений.

Turbolift написан на Go компилируемом языке от Google, который вы за год освоите с нуля на курсе Backend-разработчик на Go от ключевых понятий в IT, основ Linux и до применения Go для DevOps. Мы используем модель фундаментального образования, поэтому вы получите не только практические навыки, но и крепкую теоретическую базу, научитесь мыслить по-новому и в этом вам помогут эксперты в своём деле и менторы, которые с удовольствием ответят на ваши вопросы и передадут вам свои знания.

Узнайте, как прокачаться и в других специальностях или освоить их с нуля:

Другие профессии и курсы
Подробнее..

Как эффективнее подбирать разработчиков?

30.03.2021 16:11:46 | Автор: admin

Какие инструменты можно использовать рекрутеру чтобы оценить навыки разработчика без привлечения технических специалистов?

На что обращать внимание при просмотре резюме кандидата?

Как увеличить процент кандидатов, которые прошли техническое интервью?

С чего начинается оценка кандидата? Чаще всего с просмотра его резюме, представленного в виде документа (pdf,doc) или его профиля в LinkedIn. Первое на что необходимо обратить внимание - насколько совпадают знания соискатели с требованиями к вакансии. Звучит очевидно, но давайте разберемся в нюансах.

Предположим, что вы ищете кандидата на вакансию Senior Frontend Developer, работать которому предстоит с React, Webpack и PostCSS. В этом случае важно понимать, на какие ключевые слова в резюме стоит обращать внимание, а на какие не очень. Например, в нашем случае Webpack и PostCSS - это вспомогательные инструменты разработчика, и можно не проверять их наличие в портфолио, так как овладеть навыками работы с ними не займёт много времени, а тот факт что разработчик с ними не работал означает то, что не подвернулось подходящего проекта, и ничего не говорит о его навыках.

Следующий по важности идёт React. React - это библиотека для JavaScript, которая требует изучения и практического опыта. Это уже достаточно важный инструмент, и если большая часть фронтенда в компании написана с его помощью, то онбординг кандидата, который не работал ранее с React, может занять существенно большее количество времени, нежели того кто имеет опыт с React. Но это все ещё не основной инструмент Frontend разработчика, поэтому я бы рекомендовал не отсеивать сразу кандидатов, кто не владеет React, так как достаточно опытный разработчик может овладеть им на неплохом уровне, даже за то время что занимают процессы собеседования и найма.

Основным по важности в нашем случае будет JavaScript. Ни Java. Ни ActionScript. А именно JavaScript. Это собственно и есть тот язык программирования, с которым фронтенд разработчик проводит в обнимку и в слезах большую часть времени своей профессиональной карьеры. То есть знание этого языка и количество опыта работы с ним критически важно для нашего с вами поиска.

Что же получается: нам необходимо выделить то, какие технологии критичны для нас, просто важны, и не важны совсем, и оценивать кандидата исходя из этих знаний.

А как оценить уровень владения этими технологиями?

1. Вы можете обратиться к тому, от кого получили вакансию. То eсть к тех лиду в вашей компании, если вы штатный сотрудник, либо к тех.специалистам заказчика, если вы работаете на аутсорсе.

2. Обратиться с вопросом непосредственно к самим разработчикам, которых вы можете найти в профильных чатах в телеграм. Как правило там обитают достаточно дружелюбные товарищи, которые всегда рады поспособствовать тому чтобы им перестали присылать неподходящие вакансии :)

3. Следить за апдейтами в нашем Telegram-канале - у нас есть рубрика #techstack в которой мы периодически публикуем разборы разных технологических стеков с указанием важности инструментов.

Отлично, теперь мы знаем на какие ключевые слова обращать внимание, а на какие - нет, но ещё важно то, насколько долго и качественно кандидат работал с этими технологиями. Например, если в резюме указано что опыт разработки - 7 лет, но все последние 5 лет это была бэкенд разработка с Node.js, либо вообще с использованием другого языка программирования, и только первые два года соискатель провёл за разработкой фронтенда, с очень большой вероятностью этот кандидат не будет заинтересован в вакансии фронтенд разработчика, либо не будет способен пройти техническое собеседование, так как JavaScript сейчас и пять лет назад это очень разные технологии :)

Тоесть наш алгоритм в данном случае таков:

1. Листаем резюме и смотрим на наличие важных технологий на указанных местах работы и в описании.

2. Мысленно проставляем веса каждому увиденному кейворду, по принципу A - важность технологии, B - насколько недавно соискатель с ней работал, и умножаем А на В.

3. Если получилась достаточно уверенная первичная оценка, можно пойти подкреплять ее дополнительными способами, о которых мы расскажем далее.

Также есть сервисы, которые помогают в подобной оценке кандидата, например, AmazingHiring.

Но у него есть два существенных минуса: он очень приблизительно оценивает технический стек кандидата, основываясь только на появлении кейвордов в списке технологий, никак не взвешивая их, при это стоимость начального тарифа составляет 270т,р, в год.

Второй сервис - это Deviato, он достаточно качественно оценивает появление кейвордов на странице кандидата, и бесплатно оценивает неограниченное количество страниц LinkedIn по этому параметру.

Окей, с базовой оценкой кандидата мы разобрались, как мы можем проверить то, что написано в резюме, не прибегая к техническому собеседованию?

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

GitHub

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

Как найти профиль кандидата на GitHub?

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

Либо, если нам не помогло расширение, мы можем попробовать найти его сами. Для начала просто попробуйте поискать имя фамилию соискателя в гугле с приставкой site:github.com , этот фильтр будет искать вхождения имени и фамилии только на гитхабе. Если же имя фамилия уникальные и нашлись, я вас поздравляю, это было просто. Но это Иван Сидоров, которых достаточно много гитхабе, то постарайтесь найти того, чей технический стек и дата регистрации совпадают с началом карьеры. Если же у вас не получилось никого найти с таким именем и фамилией, то не отчаивайтесь, есть ещё один вариант. Люди достаточно часто используют один и тот же никнейм для всех своих социальных сетей. То есть вы можете поискать в гугле фио соискателя, и найдя его профиль на какой либо из социальных сетей (Facebook/vk/instagram/twitter) можете использовать это для гитхаба, например у меня и в Твиттере и на гитхабе Ник - x1ting.

Отлично, мы нашли кандидата, но куда смотреть дальше?

Анализ профиля на Github

Самое простое с чего мы можем начать - это дата регистрации.

Чтобы ее найти, надо открыть гитхаб профиль и справа снизу в ленте активности выбрать самый первый год. Там откроется первая активность - Joined Github с датой

Как правило, разработчики в самом старте своей карьеры, или как минимум коммерческой карьеры, заводят себе гитхаб аккаунт. И если вы видите что в резюме заявлено более 6 лет опыта, а гитхаб зарегистрирован в прошлом году - это повод лишний раз задуматься

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

и поставить фильтр по Type: Sources, так вы будете видеть только репозитории созданные этим человеком.

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

то лучше, наверное, сэкономить время и пропустить его.

Open Source активность

раз уж мы дошли до репозиториев на гитхабе, то самое время посмотреть, а насколько качественный код пишет разработчик. Но, будучи рекрутером, мы врядли сможем оценить качество кода, смотря непосредственно на него. Для этого есть одна хитрость, которую мы можем использовать. Дело в том, что на гитхабе расположено множество популярных репозиториев, которые поддерживаются и развиваются участниками OpenSource сообщества. И, как правило, код который попадает в эти репозитории посмотрел уже не один опытный разработчик. Какую пользу это дает нам? Мы можем найти, вносил ли какой либо вклад наш кандидат в популярные репозитории на гитхабе, и если этот вклад был написан на интересующем нас языке программирования (не просто правки документации), то это может означать что его поревьювали как минимум несколько опытных разработчиков и приняли, что говорит нам о сильном опыте разработчика.

Как это сделать?

Перейдем в знакомую нам вкладку репозиториев и выберем фильтр Type: Forks

Здесь мы можем видеть один репозиторий, который скопировал себе для внесения изменений Михаил. Теперь давайте перейдем в оригинал репозитория по маленькой ссылке под названием (выделена красным).

После перехода в репозиторий открываем ссылку поиска вверху и вводим <nickname> где <nickname> это ник нашего пользователя. И выбираем область поиска - In this repository (можно указать опцию from: чтобы сделать поиск более строгим, но тогда можем пропустить часть результатов)

в результатах поиска переходим во вкладку commits (если во всех результатах у нас 0, значит либо он просто скопировал репозиторий, но ничего в нем не менял, либо что-то менял, но участники сообщества эти изменения не приняли). Если что-то нашлось, то открываем непосредственно тело этих изменений (выделено красным)

В нашем случае оказалось, что внесенные изменения, это подписание петиции (да, и такое на гитхабе бывает), и практической ценности в оценке кандидата (кроме его гражданской позиции) это не несет. То есть не стоит уменьшать рейтинг кандидата, просто мы не можем его усилить.

Но могут встречаться и вот такие изменения (они были найдены без операнда from:, а просто по комментарию с благодарностью)

На этом скриншоте, мы можем видеть что внесенные изменения - это код на Ruby в достаточно популярный репозиторий (в правом верхнем углу Star 1k, одна тысяча звездочек на гитхабе). Хоть и тело изменения не самое большое, все равно, увеличивает уверенность что перед нами достаточно опытный Ruby разработчик.

Ну, пожалуй с гитхаба хватит, хоть и способ глубокой оценки вручную занимает достаточно много времени, зато позволяет нам отсеяться достаточно большое количество кандидатов самостоятельно, не привлекая технических специалистов. Кстати, чтобы не делать эту оценку вручную - можно использовать расширение от Deviato - у него тоже есть бесплатный тариф, и за ценную обратную связь, его можно продлевать ;)

Перейдем к последнему, самому простому - StackOverflow.

Как найти профиль кандидата на StackOverflow? Да примерно так же как и на Github, с использованием расширений, либо гугла, либо вбив в поисковой строке его ник.

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

Также можно проверить вкладку tags, чтобы посмотреть на вопросы по каким технологиям отвечал кандидат:

Напоминаю, что StackOverflow - это сервис вопросов и ответов, где одни разработчики задают вопросы, а другие дают ответы. Соответственно за ответы которые помогли, либо за ценные комментарии и раздаются баллы репутации пользователям.

Отлично, мы показали вам комплексный метод оценки кандидатов, который поможет отсеять очень большое количество человек прямо на старте, делая вашего техлида более счастливым, потому что не придется собеседовать кандидатов, которые не пройдут интервью!

Подробнее..

Вышла Java 16

16.03.2021 18:09:12 | Автор: admin

Вышла 16-я версия платформы Java SE. В этот релиз попало около двух с половиной тысяч закрытых задач и 17 JEP'ов. Изменения API можно посмотреть здесь. Release notes здесь.


Уже сейчас доступны для скачивания дистрибутивы Oracle JDK и OpenJDK.



JEP'ы, которые попали в Java 16, мы разобьём на четыре категории: язык, API, JVM и инфраструктура.


Язык


Паттерн-матчинг для оператора instanceof (JEP 375)


Оператор instanceof с паттерн-матчингом, который появился в Java 14 и перешёл во второе preview в Java 15, теперь стал стабильной синтаксической конструкцией и больше не требует флага --enable-preview. Паттерн-матчинг мы подробно рассматривали в этой статье, и с того момента в него было внесено два изменения:


Во-первых, переменные паттернов теперь не являются неявно финальными:


if (obj instanceof String s) {    s = "Hello"; // OK в Java 16, ошибка в Java 15}

Во-вторых, если тип выражения, известный на этапе компиляции, является подтипом проверяемого типа, то теперь это ошибка компиляции:


String str = ...if (str instanceof String s) { // Oшибка в Java 16, OK в Java 15}


Записи (JEP 395)


Ещё одна синтаксическая конструкция, которая стала стабильной это записи. Она также была в режиме preview в Java 14 и Java 15. Записи мы также подробно рассматривали ранее. В Java 16 было внесено следующее изменение: теперь во внутренних классах разрешено объявлять статические члены:


public class Outer {    public class Inner {        // OK в Java 16, ошибка в Java 15        static void main(String[] args) {        }        // OK в Java 16, ошибка в Java 15        record Point(int x, int y) {        }    }}


sealed классы (второе preview) (JEP 397)


Запечатанные классы, которые появились в Java 15 в режиме preview, остаются в этом статусе. Их мы рассматривали в этой статье. Изменения по сравнению с прошлой версией следующие:


  • Теперь в спецификации языка Java появилось понятие contextual keyword взамен старым понятиям restricted keyword и restricted identifier, и одними из таких contextual keywords стали sealed, non-sealed и permits.
  • Компилятор теперь производит более строгие проверки при конверсии типов, в иерархиях которых есть sealed классы:
    sealed interface Sealed {}final class Impl implements Sealed {    void f(Runnable r) {        Sealed s = (Sealed) r; // error: incompatible types    }}
    
  • Метод Class.permittedSubclasses() переименован в Class.getPermittedSubclasses().


JVM


Строгая инкапсуляция внутренностей JDK по умолчанию (JEP 396)


Инкапсуляция внутренних API JDK, которая была введена в Java 9, теперь стала строгой: если в Java 9-15 значение опции --illegal-access было по умолчанию permit, то с Java 16 она становится deny. Это значит, что рефлективный доступ к защищённым членам классов и статический доступ к неэкспортированным API (sun.*, com.sun.*, jdk.internal.* и т.д.) теперь будет выбрасывать ошибку.


Если код требует доступа к внутренностям JDK во время выполнения, то чтобы он продолжал работать на Java 16, теперь придётся явно указывать одну из трёх опций JVM:


  • --illegal-access=permit/warn/debug: открытие всех пакетов JDK
  • --add-opens=module/package=target-module: открытие одного пакета
  • --add-exports=module/package=target-module: экспортирование одного пакета (только для статического доступа)

В будущем опция --illegal-access может быть удалена окончательно. Начиная с Java 16, при её использовании выдаётся предупреждение: Option --illegal-access is deprecated and will be removed in a future release.


Изменения не касаются критического API в модуле jdk.unsupported: классы в пакетах sun.misc и sun.reflect остаются доступными без флагов.



Warnings for Value-Based Classes (JEP 390)


Классы-обёртки примитивных типов (Integer, Double, Character и т.д.) теперь относятся к категории value-based классов, и их конструкторы, которые ранее стали deprecated в Java 9, теперь помечены как deprecated for removal.


Понятие value-based классов появилось в спецификации API Java 8. Такие классы являются неизменяемыми, создаются только через фабрики, и в их использовании не должны использоваться операции, чувствительные к identity: сравнение на ==, синхронизация, identityHashCode() и т.д. Value-based классы являются кандидатами для миграции на примитивные классы в рамках проекта Valhalla, который сейчас находится в стадии активной разработки.


При синхронизации на объектах value-based классов теперь будет выдаваться предупреждение во время компиляции:


Double d = 0.0;synchronized (d) { // warning: [synchronization] attempt to synchronize on an instance of a value-based class}

Также можно включить проверки синхронизации на value-based объектах во время выполнения с помощью флагов JVM:


  • -XX:+UnlockDiagnosticVMOptions -XX:DiagnoseSyncOnValueBasedClasses=1: при попытке синхронизации будет фатальная ошибка.
  • -XX:+UnlockDiagnosticVMOptions -XX:DiagnoseSyncOnValueBasedClasses=2: при попытке синхронизации будет предупреждение.


ZGC: Concurrent Thread-Stack Processing (JEP 376)


Обработка стеков потоков в сборщике мусора ZGC теперь перенесена из safepoints в конкурентную фазу. Это позволило ещё сильнее уменьшить паузы сборщика мусора.



Unix-Domain Socket Channels (JEP 380)


Добавлена поддержка сокетов доменов Unix в socket channel и server-socket channel API. Такие сокеты используются для межпроцессного взаимодействия внутри одного хоста, и в них не используются сетевые соединения, что делает такое взаимодействие более безопасным и эффективным. Сокеты доменов Unix с недавних пор поддерживаются в Windows 10 и Windows Server 2019.



Elastic Metaspace (JEP 387)


Metaspace (пространство JVM, в котором хранятся метаданные классов) переработан для более эффективной отдачи неиспользуемой памяти обратно операционной системе и меньшего потребления памяти вне кучи в целом. Такое улучшение может быть полезно для приложений, которые интенсивно загружают и выгражают классы посредством большого количества загрузчиков классов.



Alpine Linux Port (JEP 386)


JDK теперь портирован на Alpine Linux и другие дистрибутивы Linux, которые используют musl в качестве реализации стандартной библиотеки C. Alpine Linux популярен в облаках, микросервисах и контейнерах благодаря своему маленькому размеру образа. Новый порт позволит нативно запускать JDK в этих окружениях.



Windows/AArch64 Port (JEP 388)


JDK также портирован на архитектуру Windows/AArch64. Это позволит запускать Java на компьютерах с Windows on ARM, которые в последнее время набирают популярность.



API


Новые методы в Stream


Хотя для этих двух новых методов в интерфейсе java.util.stream.Stream нет отдельного JEP, хочется упомянуть их здесь, так как это довольно заметное изменение.


Первый метод это Stream.toList(). Этот метод собирает содержимое Stream в неизменяемый список и возвращает его. При этом, в отличие от Collectors.toUnmodifiableList(), список, который возвращается из Stream.toList(), толерантен к null-элементам.


Второй метод это Stream.mapMulti() (и примитивные специализации). Это метод является императивным аналогом метода Stream.flatMap(): если flatMap() принимает функцию, которая для каждого элемента должна вернуть Stream, то mapMulti() принимает процедуру с двумя параметрами, где первый параметр это текущий элемент, а второй Consumer, в который кладутся значения. Пример:


IntStream.rangeClosed(1, 10).mapMulti((i, consumer) -> {    for (int j = 1; j <= i; j++) {        consumer.accept(j);    }}); // Возвращает 1, 1, 2, 1, 2, 3, 1, 2, 3, 4, 1, 2, 3, 4, 5, ...


Инструмент упаковки (JEP 392)


Инструмент создания самодостаточных приложений jpackage, который появился в Java 14 в инкубационном статусе, теперь стал постоянным модулем.



Vector API (Incubator) (JEP 338)


Появился новый инструментарий для преобразования векторных вычислений в SIMD-инструкции процессора (x64 и AArch64). Векторное API позволит разработчику контролировать процесс компиляции и не полагаться на автовекторизацию, которая в JVM является ограниченным и хрупким механизмом. Явная векторизация может применяться в таких областях как машинное обучение, линейная алгебра, криптография и др.


API находится в инкубационном модуле jdk.incubator.vector.



Foreign Linker API (Incubator) (JEP 389)


Ещё одно новое API, которое появилось в результате работы над проектом Panama это Foreign Linker API. Это инструментарий для статического доступа к нативному коду из Java, созданный для замены JNI: он должен быть более простым в использовании, более безопасным и желательно более быстрым.


Про Foreign API делал доклад Владимир Иванов из Oracle.



Foreign-Memory Access API (Third Incubator) (JEP 393)


API для доступа вне кучи Java, которое появилось в Java 14, остаётся в инкубационном статусе с некоторыми изменениями.



Инфраструктура


Enable C++14 Language Features (JEP 347)


Кодовая база JDK до Java 16 использовала стандарты C++98/03. При этом с Java 11 код стал собираться версией с более новым стандартом, однако в нём всё ещё нельзя было использовать возможности стандарта C++11/14. Теперь же часть из этих возможностей использовать можно: в гиде по стилю HotSpot определён список возможностей C++11/14, которые можно использовать и которые нельзя.



Migrate from Mercurial to Git (JEP 357) и Migrate to GitHub (JEP 369)


Совершён переход репозиториев JDK на Git и GitHub. Миграция была полностью завершена в сентябре 2020 года, и разработка Java 16 уже полностью велась в новом репозитории.


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


Также сейчас обсуждается переход на Git более старых версий JDK: jdk11u и, возможно, jdk8u.



Java 16 является STS-релизом, у которого выйдет только два обновления.



Если вы не хотите пропускать новости о Java, то подписывайтесь на Telegram-канал miniJUG

Подробнее..

Бенчмарки VKUI и других ребят из UI-библиотек

26.05.2021 12:10:08 | Автор: admin

Меня зовут Григорий Горбовской, я работаю в Web-команде департамента по экосистемным продуктам ВКонтакте, занимаюсь разработкой VKUI.

Хочу вкратце рассказать, как мы написали 8 тестовых веб-приложений, подключили их к моно-репозиторию, автоматизировали аудит через Google Lighthouse с помощью GitHub Actions и как решали проблемы, с которыми столкнулись.

VKUI это полноценный UI-фреймворк, с помощью которого можно создавать интерфейсы, внешне неотличимые от тех, которые пользователь видит ВКонтакте. Он адаптивный, а это значит, что приложение на нём будет выглядеть хорошо как на смартфонах с iOS или Android, так и на больших экранах планшетах и даже десктопе. Сегодня VKUI используется практически во всех сервисах платформы VK Mini Apps и важных разделах приложения ВКонтакте, которые надо быстро обновлять независимо от магазинов.

VKUI также активно применяется для экранов универсального приложения VK для iPhone и iPad. Это крупное обновление с поддержкой планшетов на iPadOS мы представили 1 апреля.

Адаптивный экран на VKUIАдаптивный экран на VKUI

Масштабирование было внушительным поэтому мы провели аудит, проанализировали проблемы, которые могут возникнуть у нас и пользователей при работе с VKUI, и заодно сравнили свои решения с продуктами ближайших конкурентов.

Какие задачи поставили

  1. Выявить главные проблемы производительности VKUI.

  2. Подготовить почву, чтобы развернуть автоматизированные бенчмарки библиотеки и наших веб-приложений на основеVKUI.

  3. Сравнить производительность VKUI и конкурирующих UI-фреймворков.

Технологический стек

Инструменты для организации процессов:

Чтобы проще взаимодействовать с несколькими веб-приложениями, в которых применяются разные UI-библиотеки, здесь используем lerna. Это удобный инструмент, с помощью которого мы объединили в большой проект ряд приложений с отличающимися зависимостями.

Бенчмарки мы проводим через Google Lighthouse официальный инструмент для измерения Web Vitals. Де-факто это стандарт индустрии для оценки производительности в вебе.

Самое важное делает GitHub Actions: связывает воедино сборку и аудит наших приложений.

Библиотеки, взятые для сравнения:

Название

Сайт или репозиторий

VKUI

github.com/VKCOM/VKUI

Material-UI

material-ui.com

Yandex UI

github.com/bem/yandex-ui

Fluent UI

github.com/microsoft/fluentui

Lightning

react.lightningdesignsystem.com

Adobe Spectrum

react-spectrum.adobe.com

Ant Design

ant.design

Framework7

framework7.io


Мы решили сравнить популярные UI-фреймворки, часть из которых основана на собственных дизайн-системах. В качестве базового шаблона на React использовали create-react-app, и на момент написания приложений брали самые актуальные версии библиотек.

Тестируемые приложения

Первым делом мы набросали 8 приложений. В каждом были такие страницы:

  1. Default страница с адаптивной вёрсткой, содержит по 23 подстраницы.

    • На главной находится лента с однотипным контентом и предложением ввести код подтверждения (абстрактный). При его вводе на несколько секунд отображается полноэкранный спиннер загрузки.

    • Страница настроек, в которую входит модальное окно с примитивным редактированием профиля и очередным предложением ввести код.

    • Страница с простым диалогом условно, с техподдержкой.

  2. List (Burn) страница со списком из 500 элементов. Главный аспект, который нам хотелось проверить: как вложенность кликабельных элементов влияет на показатель Performance.

  3. Modals страница с несколькими модальными окнами.

Не у всех UI-фреймворков есть аналогичные компоненты недостающие мы заменяли на равнозначные им по функциональности. Ближе всего к VKUI по компонентам и видам их отображения оказались Material-UI и Framework7.

Сделать 8 таких приложений поначалу кажется простой задачей, но спустя неделю просто упарываешься писать одно и то же, но с разными библиотеками. У каждого UI-фреймворка своя документация, API и особенности. С некоторыми я сталкивался впервые. Особенно запомнился Yandex UI кажется, совсем не предназначенный для использования сторонними разработчиками. Какие-то компоненты и описания параметров к ним удавалось найти, только копаясь в исходном коде. Ещё умилительно было обнаружить в компоненте хедера логотип Яндекса <3

Вернёмся к нашим приложениям. Когда были написаны первые три, мы начали параллельную работу над автоматизацией аудита:

Автоматизация

Краткая блок-схема, описывающая процессы в автоматизацииКраткая блок-схема, описывающая процессы в автоматизации

Подготовили два воркфлоу:

  • Build and Deploy здесь в первую очередь автоматизировали процессы сборки и разворачивания. Используем surge, чтобы быстро публиковать статичные приложения. Но постепенно перейдём к их запуску и аудиту внутри GitHub Actions воркеров.

  • Run Benchmarks а здесь создаётся issue-тикет в репозитории со ссылкой на активный воркфлоу, затем запускается Lighthouse CI Action по подготовленным ссылкам.

UI-фреймворк

URL на тестовое приложение

VKUI

vkui-benchmark.surge.sh

Ant Design

ant-benchmark.surge.sh

Material UI

mui-benchmark.surge.sh

Framework7

f7-benchmark.surge.sh

Fluent UI

fluent-benchmark.surge.sh

Lightning

lightning-benchmark.surge.sh

Yandex UI

yandex-benchmark.surge.sh

Adobe Spectrum

spectrum-benchmark.surge.sh


Конфигурация сейчас выглядит так:

{  "ci": {    "collect": {      "settings": {        "preset": "desktop", // Desktop-пресет        "maxWaitForFcp": 60000 // Время ожидания ответа от сервера      }    }  }}

После аудита всех приложений запускается задача finalize-report. Она форматирует полученные данные в удобную сравнительную таблицу (с помощью магии Markdown) и отправляет её комментарием к тикету. Удобненько, да?

Пример подобного репорта от 30 марта 2021 г.Пример подобного репорта от 30 марта 2021 г.

Нестабильность результатов

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

Из отчётов Lighthouse выяснили следующее: по неведомым причинам сервер долго отвечал, и это привело к общему замедлению и снижению баллов. Изначально грешили на воркеры и хостинг в целом, и я решился задеплоить и провести бенчмарки на одном сервере. Результаты стали лучше, но такое странное поведение оставалось нелогичным.

Предупреждения из отчётов Google LighthouseПредупреждения из отчётов Google Lighthouse

GitHub Actions по умолчанию выполняет задачи параллельно, как и в нашем воркфлоу с бенчмарками. Решение ограничить максимальное количество выполняющихся одновременно задач:

jobs.<job_id>.strategy.max-parallel: 1

Мы провели бенчмарк дважды, и вуаля результаты стали ощутимо стабильнее. Теперь можно переходить к результатам.

Результаты от 30 марта 2021 г.

VKUI (4.3.0) vs ant:

app

type (app link)

report

performance

vkui (4.3.0)

default

report

0.99

ant

default

report

0.99

vkui (4.3.0)

modals

report

1

ant

modals

report

0.99

vkui (4.3.0)

list

report

0.94

ant

list

report

0.89

list - У ant нет схожего по сложности компонента для отрисовки сложных списков, но на 0,05 балла отстали.

VKUI (4.3.0) vs Framework7:

app

type (app link)

report

performance

vkui (4.3.0)

default

report

0.99

f7

default

report

0.98

vkui (4.3.0)

modals

report

1

f7

modals

report

0.99

vkui (4.3.0)

list

report

0.94

f7

list

report

0.92

list - Framework7 не позволяет вложить одновременно checkbox и radio в компонент списка (List).

VKUI (4.3.0) vs Fluent:

app

type (app link)

report

performance

vkui (4.3.0)

default

report

0.99

fluent

default

report

0.94

vkui (4.3.0)

modals

report

1

fluent

modals

report

0.99

vkui (4.3.0)

list

report

0.94

fluent

list

report

0.97

modals - Разница на уровне погрешности.

list - Fluent не имеет схожего по сложности компонента для отрисовки сложных списков.

VKUI (4.3.0) vs Lightning:

app

type (app link)

report

performance

vkui (4.3.0)

default

report

0.99

lightning

default

report

0.95

vkui (4.3.0)

modals

report

1

lightning

modals

report

1

vkui (4.3.0)

list

report

0.94

lightning

list

report

0.99

list - Lightning не имеет схожего по сложности компонента для отрисовки сложных списков.

VKUI (4.3.0) vs mui:

app

type (app link)

report

performance

vkui (4.3.0)

default

report

0.99

mui

default

report

0.93

vkui (4.3.0)

modals

report

1

mui

modals

report

0.96

vkui (4.3.0)

list

report

0.94

mui

list

report

0.77

default и modals - Расхождение незначительное, у Material-UI проседает First Contentful Paint.

list - При примерно одинаковой загруженности списков в Material-UI и VKUI выигрываем по Average Render Time почти в три раза (~1328,6 мс в Material-UI vs ~476,4 мс в VKUI).

VKUI (4.3.0) vs spectrum:

app

type (app link)

report

performance

vkui (4.3.0)

default

report

0.99

spectrum

default

report

0.99

vkui (4.3.0)

modals

report

1

spectrum

modals

report

1

vkui (4.3.0)

list

report

0.94

spectrum

list

report

1

list - Spectrum не имеет схожего по сложности компонента для отрисовки сложных списков.

VKUI (4.3.0) vs yandex:

app

type (app link)

report

performance

vkui (4.3.0)

default

report

0.99

yandex

default

report

1

vkui (4.3.0)

modals

report

1

yandex

modals

report

1

vkui (4.3.0)

list

report

0.94

yandex

list

report

1

default - Разница на уровне погрешности.

list - Yandex-UI не имеет схожего по сложности компонента для отрисовки сложных списков.

modals - Модальные страницы в Yandex UI объективно легче.

Выводы из отчёта Lighthouse о VKUI

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

  • Одно из явных проблемных мест вложенные Tappable протестированы на большом списке. Единственная библиотека, в которой полноценно реализован этот кейс, Material-UI. И VKUI уверенно обходит её по производительности.

  • Lighthouse ругается на стили после сборки много неиспользуемых. Они же замедляют First Contentful Paint. Над этим уже работают.

Два CSS-чанка, один из которых весит 27,6 кибибайт без сжатия в gzДва CSS-чанка, один из которых весит 27,6 кибибайт без сжатия в gz

Планы на будущее vkui-benchmarks

Переход с хостинга статики на локальное тестирование должен сократить погрешность: уменьшится вероятность того, что из-за внешнего фактора станет ниже балл у того или иного веб-приложения. Ещё у нас в репортах есть показатель CPU/Memory Power и он немного отличается в зависимости от воркеров, которые может дать GitHub. Из-за этого результаты в репортах могут разниться в пределах 0,010,03. Это можно решить введением перцентилей.

Также планируем сделать Lighthouse Server для сохранения статистики по бенчмаркам на каждый релиз.

Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru