<?xml version="1.0" encoding="utf-8" ?><rss version="2.0" xmlns:tt="http://teletype.in/" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:media="http://search.yahoo.com/mrss/"><channel><title>Александр Никитин</title><generator>teletype.in</generator><description><![CDATA[Александр Никитин]]></description><image><url>https://img4.teletype.in/files/fb/ce/fbce9f74-ac8d-408f-9d49-99766f119295.png</url><title>Александр Никитин</title><link>https://blog.ihumster.ru/</link></image><link>https://blog.ihumster.ru/?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=ihumster</link><atom:link rel="self" type="application/rss+xml" href="https://teletype.in/rss/ihumster?offset=0"></atom:link><atom:link rel="next" type="application/rss+xml" href="https://teletype.in/rss/ihumster?offset=10"></atom:link><atom:link rel="search" type="application/opensearchdescription+xml" title="Teletype" href="https://teletype.in/opensearch.xml"></atom:link><pubDate>Wed, 06 May 2026 15:38:03 GMT</pubDate><lastBuildDate>Wed, 06 May 2026 15:38:03 GMT</lastBuildDate><item><guid isPermaLink="true">https://blog.ihumster.ru/nK_4wEHidW8</guid><link>https://blog.ihumster.ru/nK_4wEHidW8?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=ihumster</link><comments>https://blog.ihumster.ru/nK_4wEHidW8?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=ihumster#comments</comments><dc:creator>ihumster</dc:creator><title>Не доверяй ИИ</title><pubDate>Tue, 25 Feb 2025 18:50:00 GMT</pubDate><category>Мысли вслух</category><description><![CDATA[Блин, товарищи коллеги. Я с чувством невероятного удовлетворения в очередной раз убеждаюсь что никакой искусственный интеллект нас не заменит. И он херовый помощник тому кто не понимает что он хочет получить в результате и как этот результат должен воспроизводиться.]]></description><content:encoded><![CDATA[
  <p id="S12z">Блин, товарищи коллеги. Я с чувством невероятного удовлетворения в очередной раз убеждаюсь что никакой искусственный интеллект нас не заменит. И он херовый помощник тому кто не понимает что он хочет получить в результате и как этот результат должен воспроизводиться.</p>
  <p id="LksA">Це была фабула. А дальше мой короткий рассказ. 😅</p>
  <p id="qkdT">Понадобилось мне стало быть в логи nginx пихнуть кусок (именно кусок, а не всю строку ибо там чувствительные данные) строки из заголовка http запроса. Пошёл читать документацию. Понял что необходимое мне можно извлечь из одной переменной в другую через директиву map. И вот я два часа &quot;беседовал&quot; с perplexity/openai-o3 которые отчаянно меня убеждали что результат регулярки (match) или именованную группу нельзя использовать как результат map. И действительно приведенный пример конфига nginx не принимал за валидный конфиг. После чтения документации и пары статей на хабре, в комментариях я таки нашел пример, который натолкнул меня на решение моей задачи, которая в итоге решилась и оказалась простой и элегантной (ввиду простенькой регулярочки которую я уже накидал сам).</p>
  <p id="FuHX">В качестве эпилога хочу вам пожелать не верить всяким ИИ, даже если они утверждают что что-то не будет работать. 😁 доверяйте своему инженерному чутью!</p>
  <p id="1mDv">У меня всё.</p>

]]></content:encoded></item><item><guid isPermaLink="true">https://blog.ihumster.ru/netbox-change-current-config-nbshell</guid><link>https://blog.ihumster.ru/netbox-change-current-config-nbshell?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=ihumster</link><comments>https://blog.ihumster.ru/netbox-change-current-config-nbshell?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=ihumster#comments</comments><dc:creator>ihumster</dc:creator><title>Управление CurrentConfig через nbshell</title><pubDate>Mon, 13 Jan 2025 16:37:14 GMT</pubDate><category>netbox</category><description><![CDATA[Опытные пользователи Netbox наверняка сталкивались с таким мощным инструментом как Netbox Shell (построенный на базе Django Shell и представляющий собой интерпретатор iPython с расширенной функциональностью).]]></description><content:encoded><![CDATA[
  <p id="LvKh">Опытные пользователи Netbox наверняка сталкивались с таким мощным инструментом как Netbox Shell (построенный на базе Django Shell и представляющий собой интерпретатор iPython с расширенной функциональностью).</p>
  <h2 id="0v70">Управление в интерактивном режиме</h2>
  <p id="xLrY">Чтобы запустить nbshell необходимо открыть в командной строке папку с дистрибутивом приложения и, активировав virtual environment, выполнить команду <em>python netbox/manage.py nbshell</em></p>
  <p id="NZaw">Что бы получить текущую ревизию конфигурации приложения (которая и содержит динамически конфигурируемые параметры) нам понадобится ID текущей (активной) конфигурации, ее можно получить из кэша приложения:</p>
  <pre id="EDN4" data-lang="python">from django.core.cache import cache

activeRevision = ConfigRevision.objects.get(pk=cache.get(&#x27;config_version&#x27;))</pre>
  <p id="30h2">Объект activeRevision будет содержать объект типа ConfigRevision, являющийся активной конфигурацией в данный момент (&quot;Curren configuration&quot;). Объект простой, в поле data содержит словарь с динамически конфигурируемыми параметрами (список всех параметров и их значение можно подсмотреть в <a href="https://netboxlabs.com/docs/netbox/en/stable/configuration/" target="_blank">официальной документации</a>). Однако просто изменить текущую конфигурацию нельзя, конфигурация будет применена только к новой ревизии конфигурации, а значит нам нужно ее создать.</p>
  <p id="hCHh">Однако создавать мы ее будем не с нуля, а на базе предыдущей активной конфигурации.</p>
  <p id="eXTB">Для примера мы включим параметр MAINTENANCE_MODE, переведя таким образом приложение в режим обслуживания.</p>
  <pre id="iPQn" data-lang="python"># Хорошим тоном будет записать смысл новой ревизии конфига в комментарий
# Заполняем все параметры из текущей конфигурации
newRevsion = ConfigRevision(comment=&#x27;Enable MM&#x27;, data=activeRevision.data)
# Изменяем нужный нам параметр
newRevsion.data[&#x27;MAINTENANCE_MODE&#x27;] = True
# Валидируем нашу модель конфигурации на соответствие модели данных,
# заложенных разработчиком
newRevsion.full_clean()
# Сохраняем новую конфигурацию (она автоматически станет активной)
newRevsion.save()</pre>
  <h2 id="1PWO">Управление в режиме скрипта</h2>
  <p id="zv8h">Данный код можно склеить в одну строку, разделив строки точкой с запятой <strong>; </strong>и передать одним аргументом для nbshell.</p>
  <pre id="2ju7" data-lang="shell">python netbox/manage.py nbshell -c &quot;from django.core.cache import cache; activeRevision = ConfigRevision.objects.get(pk=cache.get(&#x27;config_version&#x27;)); newRevsion = ConfigRevision(comment=&#x27;Enable MM&#x27;, data=activeRevision.data); newRevsion.data[&#x27;MAINTENANCE_MODE&#x27;] = True; newRevsion.full_clean(); newRevsion.save();&quot;</pre>
  <h2 id="gQqO">Для мультинодовых конфигураций</h2>
  <section style="background-color:hsl(hsl(170, 33%, var(--autocolor-background-lightness, 95%)), 85%, 85%);">
    <p id="cDdW">не забывайте что проделывать манипуляции с конфигурациями придется для каждой ноды отдельно. </p>
  </section>
  <h2 id="pqJ0">UPD: Работа с существующей конфигурацией</h2>
  <p id="AxhH">Если же хочется изменить какую-то текущую конфигурацию или просто сделать ее активной (например, откатиться на предыдущую), то сделать это можно через встроенный метод класса ConfigRevision - activate()</p>
  <pre id="9Fkq" data-lang="python">revision = ConfigRevision.object.get(pk=revisionNumber)
revision.activate()</pre>

]]></content:encoded></item><item><guid isPermaLink="true">https://blog.ihumster.ru/netbox-keycloak-custom-pipeline</guid><link>https://blog.ihumster.ru/netbox-keycloak-custom-pipeline?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=ihumster</link><comments>https://blog.ihumster.ru/netbox-keycloak-custom-pipeline?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=ihumster#comments</comments><dc:creator>ihumster</dc:creator><title>Авторизация в Netbox через Keycloak (кастомный pipeline)</title><pubDate>Sat, 04 Jan 2025 20:53:43 GMT</pubDate><category>netbox</category><description><![CDATA[В сети есть хороший мануал по настройке авторизации в Netbox через Keycloak, однако он хорошо подходит для новых инсталляций, когда у вас чистая БД, ну или размер таблицы с пользователями и группами не очень большой и вам не составит труда все переделать.]]></description><content:encoded><![CDATA[
  <p id="tlDW">В сети есть хороший мануал по настройке <a href="https://itdraft.ru/2023/05/17/sso-avtorizacija-v-netbox-cherez-keycloak/" target="_blank">авторизации в Netbox через Keycloak</a>, однако он хорошо подходит для новых инсталляций, когда у вас чистая БД, ну или размер таблицы с пользователями и группами не очень большой и вам не составит труда все переделать.</p>
  <p id="1JPj">Сложность, с которой я столкнулся, заключалась в том, что используемая нами инсталляция Netbox имеет множество активных пользователей импортированных из Active Directory, активную ролевую модель основанную на группах из Active Directory и потерять все эти ассоциации очень не хотелось бы.</p>
  <p id="s8FO">Изучая документацию на модуль <a href="https://python-social-auth.readthedocs.io/en/latest/" target="_blank">python-social-auth</a> (а также код самого модуля и его бэкэнд к Keycloak) я наткнулся на раздел <a href="https://python-social-auth.readthedocs.io/en/latest/pipeline.html#extending-the-pipeline" target="_blank">Extending the Piplene</a> и понял что весь конвеер авторизации легко настраивается и изменяется, однако придётся немного по-питонить.</p>
  <h2 id="1xG1">Проблемы с которыми я столкнулся</h2>
  <p id="1qCy">Собственно их было две:</p>
  <ol id="pGKM">
    <li id="0xr1">Дефолтный Pipeline падает при наличии в Netbox пользователей с одинаковыми email (это специфический случай, который характерен для нашей организации и с которым вы можете никогда не столкнуться. Для снятия лишних вопросов скажу что, у нас есть правило записывать в поле email соответствующий email владельца технической учётной записи)</li>
    <li id="ybtp">Дефолтный Pipeline не обновляет членство в группах (а вот это уже критично для ролевой модели доступа основанной на группах).</li>
  </ol>
  <h2 id="Jxpc">Дефолтный Pipeline</h2>
  <p id="1T0x">Дефолтный pipeline для Netbox определяется в модуле &lt;your-netbox-folder&gt;/netbox/netbox/settings.py и выглядит так:</p>
  <pre id="brZJ" data-lang="python">SOCIAL_AUTH_PIPELINE = (
    &#x27;social_core.pipeline.social_auth.social_details&#x27;,
    &#x27;social_core.pipeline.social_auth.social_uid&#x27;,
    &#x27;social_core.pipeline.social_auth.social_user&#x27;,
    &#x27;social_core.pipeline.user.get_username&#x27;,
    &#x27;social_core.pipeline.social_auth.associate_by_email&#x27;,
    &#x27;social_core.pipeline.user.create_user&#x27;,
    &#x27;social_core.pipeline.social_auth.associate_user&#x27;,
    &#x27;netbox.authentication.user_default_groups_handler&#x27;,
    &#x27;social_core.pipeline.social_auth.load_extra_data&#x27;,
    &#x27;social_core.pipeline.user.user_details&#x27;,
)</pre>
  <p id="02l5">Он содержит функции из дефолтного, для модуля python-social-auth, конвеера, за исключением функции <strong><em>user_default_groups_handler</em></strong> из модуля &lt;your-netbox-folder&gt;/netbox/netbox/authentication.py (строка &#x27;netbox.authentication.user_default_groups_handler&#x27;).</p>
  <h2 id="pVXd">Решение проблемы №1</h2>
  <p id="q9Vv">В первых трех функциях Pipeline происходит получение и декодирование JWT access_token из которого извлекаются различные поля, которые передаются клиенту (в нашем случае Netbox) от Keycloak, и помещаются в переменную details (dict содержащий данные о пользователе, на базе которых пайплайн должен будет создать или обновить данные пользователя Netbox).</p>
  <p id="sRsK">Функция get_username создает на базе совокупности различных настроек приложения и модуля python-social-auth имя пользователя, а функция associate_by_email пытается по email найти в БД Netbox соответствующего пользователя.</p>
  <p id="VdsC">Функция associate_by_email выглядит так:</p>
  <pre id="jz60" data-lang="python">def associate_by_email(backend, details, user=None, *args, **kwargs):
    &quot;&quot;&quot;
    Associate current auth with a user with the same email address in the DB.

    This pipeline entry is not 100% secure unless you know that the providers
    enabled enforce email verification on their side, otherwise a user can
    attempt to take over another user account by using the same (not validated)
    email address on some provider.  This pipeline entry is disabled by
    default.
    &quot;&quot;&quot;
    if user:
        return None

    email = details.get(&quot;email&quot;)
    if email:
        # Try to associate accounts registered with the same email address,
        # only if it&#x27;s a single object. AuthException is raised if multiple
        # objects are returned.
        users = list(backend.strategy.storage.user.get_users_by_email(email))
        if len(users) == 0:
            return None
        elif len(users) &gt; 1:
            raise AuthException(
                backend, &quot;The given email address is associated with another account&quot;
            )
        else:
            return {&quot;user&quot;: users[0], &quot;is_new&quot;: False}</pre>
  <p id="1GMd">Очевидно, что при наличии в в БД нескольких пользователей в одинаковым email эта функция упадёт.</p>
  <p id="pvzo">Чтобы решить эту проблему я создал новый модуль и переписал эту функцию так, чтобы использовать для ассоциации не только поле email но username.</p>
  <pre id="Cx86" data-lang="python">import re
from social_core.exceptions import AuthException


def associate_by_email_and_username(backend, details, user=None, *args, **kwargs):
    &quot;&quot;&quot;
    Associate current auth with a user with the same email address and username in the DB.
    &quot;&quot;&quot;
    if user:
        return None
    email = details.get(&quot;email&quot;)
    username = details.get(&quot;username&quot;)
    # If username in upn format, get username part
    if m:=re.match(r&quot;^(?:(?P&lt;username&gt;[^@]+)@(?P&lt;domain&gt;.+))quot;, username):
        username = m.group(&#x27;username&#x27;)
    if email:
        users = list(backend.strategy.storage.user.get_users_by_email(email))
        if len(users) == 0:
            return None
        elif len(users) &gt; 1:
            for u in users:
                if u.username == username:
                    return {&quot;user&quot;: u, &quot;is_new&quot;: False}
            raise AuthException(
                backend, &quot;The given email address is associated with another account&quot;
            )
        else:
            return {&quot;user&quot;: users[0], &quot;is_new&quot;: False}
    else:
        raise AuthException(backend, &quot;No email address returned&quot;)</pre>
  <p id="Kckm">Функция извлекает из словаря details username, если он в формате userPrincipalName - извлекает из него username часть и при наличии в БД Netbox пользователей с одинаковым email проводит дополнительную проверку по полю username.</p>
  <p id="TEyg">Остается только собрать пакет из модуля, импортировать его в venv окружение, в котором работает Netbox, и переопределить в настройках приложения Pipeline (об этом расскажу ниже).</p>
  <h2 id="hScU">Решение проблемы №2</h2>
  <p id="lo41">Изучив встроенную функцию netbox.authentication.user_default_groups_handler я понял что она назначает пользователю только группы определенные в переменной REMOTE_AUTH_DEFAULT_GROUPS из configuration.py. Я принял решение заменить эту функцию в конвеере совместив с функциональностью обновления групп, полученных от Keycloak.</p>
  <p id="9DKE">Код этой функции будет выглядеть так:</p>
  <section style="background-color:hsl(hsl(34,  84%, var(--autocolor-background-lightness, 95%)), 85%, 85%);">
    <p id="Dert"><strong>Важно!</strong> Для того чтобы эта функция корректно работала access_token, который возвращает ваш Keycloak после авторизации, должен содержать поле groups с массивом имён групп. Если этого поля нет или оно содержит пустой массив - пользователю Netbox будут сброшены все связи с группами, а новых связей создано не будет.</p>
  </section>
  <pre id="KU4p" data-lang="python">import logging
from django.conf import settings
from django.contrib.auth.models import Group

def update_user_groups(backend, response, user=None, *args, **kwargs):
    &quot;&quot;&quot;
    Custom pipline handler with adds remote auth users to extist groups or
    create non-exist groups
    &quot;&quot;&quot;
    logger = logging.getLogger(
        &quot;netbox-social-auth-custom.auth_username.update_user_groups_handler&quot;
    )
    if not user:
        return None
    else:
        user_groups = []
        auth_groups = response.get(&quot;groups&quot;, [])
        user.groups.clear()

        # Add or Create DEFAULT USER GROUPS defined in configuration.py
        for name in settings.REMOTE_AUTH_DEFAULT_GROUPS:
            group, created = Group.objects.get_or_create(name=name)
            user_groups.append(group)

        # Add or Create Social Auth user groups
        # groups should be added to access token as array (list) of names
        for name in auth_groups:
            group, created = Group.objects.get_or_create(name=name)
            user_groups.append(group)

        if user_groups:
            user.groups.add(*user_groups)
        else:
            logger.info(
                f&quot;No any groups assignments for {user}. May be REMOTE_AUTH_DEFAULT_GROUPS incorrectly set\
                  or not returned from OAuth provider&quot;
            )</pre>
  <h2 id="gy80">Переопределение Pipeline</h2>
  <p id="V8qc">Итак, обе функции помещены в файл, который я назвал auth_username.py, сложены в папки netbox-social-auth-custom/pipeline (вторая папка больше для красоты в названии - обозначения что это часть pipeline по аналогии с модулем social_core) и из этого собран python package (как собирать python-пакеты я описывать не буду, т.к. для этого есть куча разного инструментария и соответствующих гайдов - <a href="https://packaging.python.org/en/latest/tutorials/packaging-projects/" target="_blank">например такой</a>, разве что подскажу, что не плохо бы при сборке указать зависимость от social-auth-core&gt;=4.5).</p>
  <p id="9IYN">Пакет установлен в окружении venv нашего Netbox и остается только переопределить собственно сам Pipline. Для этого в файле configuration.py переопределяется переменная SOCIAL_AUTH_PIPELINE в виде вот такого массива:</p>
  <pre id="yOXy" data-lang="python">SOCIAL_AUTH_PIPELINE = (
    &#x27;social_core.pipeline.social_auth.social_details&#x27;,
    &#x27;social_core.pipeline.social_auth.social_uid&#x27;,
    &#x27;social_core.pipeline.social_auth.social_user&#x27;,
    &#x27;social_core.pipeline.user.get_username&#x27;,
    &#x27;netbox_social_auth_custom.pipeline.auth_username.associate_by_email_and_username&#x27;,
    &#x27;social_core.pipeline.user.create_user&#x27;,
    &#x27;social_core.pipeline.social_auth.associate_user&#x27;,
    &#x27;social_core.pipeline.social_auth.load_extra_data&#x27;,
    &#x27;social_core.pipeline.user.user_details&#x27;,
    &#x27;netbox_social_auth_custom.pipeline.auth_username.update_user_groups&#x27;,
)</pre>
  <p id="670e">Собственно на этом всё. Спасибо что дочитали, надеюсь это кому-то пригодится.</p>
  <p id="HjuR">UPD: Отладка Pipline</p>
  <p id="bBA8">Забыл написать про отладку вашего (или вообще) конвеера авторизации. Чтобы в увидеть в логах или dev-консоли сервера Netbox </p>
  <h2 id="HjuR">UPD: Отладка Pipline</h2>
  <p id="bBA8">Забыл написать про отладку вашего (или вообще) конвеера авторизации. Чтобы в увидеть в логах или dev-консоли сервера Netbox отладочную информацию добавьте в конец (или перед падающей функцией конвеера) строку</p>
  <pre data-lang="python" id="KfhA">&#x27;social_core.pipeline.debug.debug&#x27;,</pre>

]]></content:encoded></item><item><guid isPermaLink="true">https://blog.ihumster.ru/ansible-role-argument-validation</guid><link>https://blog.ihumster.ru/ansible-role-argument-validation?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=ihumster</link><comments>https://blog.ihumster.ru/ansible-role-argument-validation?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=ihumster#comments</comments><dc:creator>ihumster</dc:creator><title>Проверка аргументов (переменных) для роли Ansible</title><pubDate>Mon, 23 Sep 2024 07:09:41 GMT</pubDate><media:content medium="image" url="https://img4.teletype.in/files/77/af/77af2f0a-4923-49e1-a776-2a27b8081401.png"></media:content><category>Ansible</category><description><![CDATA[<img src="https://img1.teletype.in/files/8e/0d/8e0d343a-84fc-40d4-8871-4cb5b536e99d.png"></img>При написании новой роли задумался о теме данной статьи - как проверять набор минимально необходимых параметров (аргументов, переменных, всё в Ansible сводится к переменным) для выполнения роли и как обычно пошел изучать документацию.]]></description><content:encoded><![CDATA[
  <p id="RhUn">При написании новой роли задумался о теме данной статьи - как проверять набор минимально необходимых параметров (аргументов, переменных, всё в Ansible сводится к переменным) для выполнения роли и как обычно пошел изучать <a href="https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_reuse_roles.html#role-argument-validation" target="_blank">документацию</a>.</p>
  <p id="esCJ">С приятным удивлением обнаружил механизм, который в Ansible появился с релизом ansible-core 2.11 (апрель 2021) и называется он <strong>Role argument validation</strong>. Исходя из собственного опыта написания модулей Ansible, я предполагаю, что в данном механизме используется тот же самый код, что и при валидации аргументов модуля (по крайней мере, всё на это указывает).</p>
  <p id="yOuk">Более того, этот механизм позволяет, помимо валидации аргументов, документировать вашу роль. После добавления файла, описывающего Ваш набор переменных, Вы получаете документированную роль, документацию по которой можно вызвать при помощи ansible-doc.</p>
  <pre id="jSJS">ansible-doc --type role --roles-path &lt;you roles path&gt; &lt;role_name&gt;</pre>
  <p id="vzG2">Пример вызова такой роли:</p>
  <figure id="HjXW" class="m_original">
    <img src="https://img1.teletype.in/files/8e/0d/8e0d343a-84fc-40d4-8871-4cb5b536e99d.png" width="1339" />
  </figure>
  <h2 id="2zph">Реализация</h2>
  <p id="zEUs">Для создания подобной функциональности используется файл meta/argument_specs.yml. Его формат описан в <a href="https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_reuse_roles.html#specification-format" target="_blank">документации </a>и довольно прост. Более того, у Вашей роли может быть не одна точка входа (по умолчанию это main.yml) но и дополнительные, возможно для вызова через <strong><em>ansible.builtin.import_role</em></strong> или <strong><em>ansible.builtin.include_role</em></strong>, набор параметров для каждой точки входа можно описать в этом файле. Также параметры можно пометить обязательными, указать тип данных (набор типов данных, которые умеет проверять AnsibleModule <a href="https://docs.ansible.com/ansible/latest/dev_guide/developing_program_flow_modules.html#argument-spec" target="_blank">тут</a>), который должен содержаться в параметре, его значение по умолчанию (если оно находится в ../defaults/&lt;entry-point&gt;.yml, например) ну и, конечно же, описание (short_description и description для точки входа и description для каждого параметра).</p>
  <p id="ND6b">В дальнейшем роль, имеющая такое описание параметров при вызове будет проверять набор необходимых параметров автоматически отдельным таском.</p>
  <figure id="03d1" class="m_original">
    <img src="https://img4.teletype.in/files/ff/f7/fff71be9-1138-49ed-bdd4-d63c1e1eef11.png" width="1314" />
  </figure>
  <p id="Dgii">При вызове роли через <strong><em>ansible.builtin.import_role</em></strong> или <strong><em>ansible.builtin.include_role</em></strong> есть возможность отключить проверку аргументов, используя параметр <strong>rolespec_validate</strong> установленный в <strong>false</strong>.</p>

]]></content:encoded></item><item><guid isPermaLink="true">https://blog.ihumster.ru/interesting-cmdlet-from-the-microsoft-powershell</guid><link>https://blog.ihumster.ru/interesting-cmdlet-from-the-microsoft-powershell?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=ihumster</link><comments>https://blog.ihumster.ru/interesting-cmdlet-from-the-microsoft-powershell?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=ihumster#comments</comments><dc:creator>ihumster</dc:creator><title>Интересный cmdlet из модуля Microsoft.PowerShell.TextUtility</title><pubDate>Mon, 23 Sep 2024 07:04:41 GMT</pubDate><description><![CDATA[Модуль Microsoft.PowerShell.TextUtility получил новый командлет ConvertFrom-TextTable. Он умеет превращать текстовую таблицу (типичный вывод большинства shell-команд Linux) в массив объектов. И даже больше - он может распарсить значения в столбцах таблицы и сконвертировать их в соответсвующие типы (например Int или Float, Boolean). Ну и наконец вместо объекта массива, командлет может вернуть массив строк в формате JSON.]]></description><content:encoded><![CDATA[
  <p id="RuMN">Модуль Microsoft.PowerShell.TextUtility получил новый командлет ConvertFrom-TextTable. Он умеет превращать текстовую таблицу (типичный вывод большинства shell-команд Linux) в массив объектов. И даже больше - он может распарсить значения в столбцах таблицы и сконвертировать их в соответсвующие типы (например Int или Float, Boolean). Ну и наконец вместо объекта массива, командлет может вернуть массив строк в формате JSON.</p>
  <p id="rEHO">Установить модуль можно из PowershellGallery:</p>
  <pre id="ll2v">Install-Module -Name Microsoft.PowerShell.TextUtility -AllowPrerelease</pre>
  <p id="lwli">Пример использования:</p>
  <pre id="jz9M" data-lang="powershell">PS&gt; df | select -first 6 | convertfrom-texttable -ConvertPropertyValue | Format-Table

Filesystem 1K-blocks      Used Available Use% Mounted_on
---------- ---------      ---- --------- ---- ----------
none         8167564         4   8167560 1%   /mnt/wsl
none       498723140 443586308  55136832 89%  /usr/lib/wsl/drivers
none         8167564         0   8167564 0%   /usr/lib/wsl/lib
/dev/sdc   263112772  27894640 221779976 12%  /
rootfs       8164316      1936   8162380 1%   /init</pre>
  <p id="JwjP">Командлет позовляет указать где определяется строка заголовков или если ее нет просто брать табличные данные и превращать их в массив объектов.</p>
  <pre id="PgiV" data-lang="powershell">PS&gt; ls -l | select -First 5 | ConvertFrom-TextTable -Skip 1 -NoHeader | ft

Property_01 Property_02 Property_03 Property_04 Property_05 Property_06 Property_07 Property_08 Property_09
----------- ----------- ----------- ----------- ----------- ----------- ----------- ----------- -----------
drwxr-xr-x  3           ihumster    ihumster    4096        Aug         21          2022        aac-base
drwxr-xr-x  3           ihumster    ihumster    4096        Jul         20          23:48       ansible_collections
drwxr-xr-x  7           ihumster    ihumster    4096        Apr         30          15:43       ansible-module-vcloud-director
drwxr-xr-x  3           ihumster    docker      4096        Jun         14          2022        ansible-netbox-dev</pre>
  <p id="lrr2">Модуль еще находится в процессе разработки и если он вам понравился и пригодился, но вы нашли какую-либо ошибку, то оставить баг-репорт можно прямо в разделе <a href="https://github.com/PowerShell/TextUtility/issues" target="_blank">Issues </a>репозитория.</p>

]]></content:encoded></item><item><guid isPermaLink="true">https://blog.ihumster.ru/vcd-upgrade-10-4-1-nuanses</guid><link>https://blog.ihumster.ru/vcd-upgrade-10-4-1-nuanses?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=ihumster</link><comments>https://blog.ihumster.ru/vcd-upgrade-10-4-1-nuanses?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=ihumster#comments</comments><dc:creator>ihumster</dc:creator><title>Нюансы обновления до VCD 10.4.1</title><pubDate>Mon, 23 Sep 2024 07:03:35 GMT</pubDate><category>Cloud Director</category><description><![CDATA[После обновления до VCD 10.4.1 одна из Advisores отсылает нас к KB78885 (но да кто их читает, да?), которая опубликована в далеком уже 2021 году и которая напоминает нам о том, что после обновления VCD у нас может пропасть доверненное подключение к различным элементам нашей инфраструктуры. Ужесточение требований к сертификатам в инфраструктуре началось в VCD еще с версии 10.1 (о чём так же отдельно пишется во врезке, на странице документации посвященной обновлению VCD).]]></description><content:encoded><![CDATA[
  <p id="fvYh">После обновления до VCD 10.4.1 одна из Advisores отсылает нас к <a href="https://kb.vmware.com/s/article/78885" target="_blank">KB78885</a> (но да кто их читает, да?), которая опубликована в далеком уже 2021 году и которая напоминает нам о том, что после обновления VCD у нас может пропасть доверненное подключение к различным элементам нашей инфраструктуры. Ужесточение требований к сертификатам в инфраструктуре началось в VCD еще с версии 10.1 (о чём так же отдельно пишется во врезке, на странице документации <a href="https://docs.vmware.com/en/VMware-Cloud-Director/10.4/VMware-Cloud-Director-Install-Configure-Upgrade-Guide/GUID-6EC093C1-4CE7-4435-9FC5-F801EA618D91.html" target="_blank">посвященной обновлению</a> VCD).</p>
  <p id="hDWo">В 10.4.1 ужесточили еще один аспект инфраструктуры, а именно подключение к ESXi хостам. В результате, после обновления до 10.4.1 (и автоматическом переходе на новую консоль, старую выпилили таки, и слава богу), в VCD перестанут работать операции требующие прямого доступа к хосту, а именно <strong>доступ к консоли ВМ, ипорту OVF/ISO, Guest Customization</strong>.</p>
  <p id="B8Zk">В логах это может отобразиться в виде следующих сообщений:</p>
  <pre id="yf6H">[ 88fc5c17-f0c7-4021-922b-5dfe1b84b7cd ] PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
 - PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
 - PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
 - unable to find valid certification path to requested target</pre>
  <pre id="O4lr"> 2023-02-24 02:59:01,050 | ERROR    | pool-jetty-81             | ServerWebSocket                | Connecting to ESX esx01.lab.ihumster.ru [server: [L=/192.168.168.25:443 R=/192.168.168.30:60004]] [client: [id: 0x13240ef6, L:/192.168.168.25:52056 ! R:esx01.lab.ihumster.ru/192.168.168.101:443]] |
io.netty.handler.codec.DecoderException: javax.net.ssl.SSLHandshakeException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
</pre>
  <h2 id="UaNd">Решение</h2>
  <p id="HDwa">Самый простой вариант - это запустить повторно <strong>CMT trust-infra-certs</strong>:</p>
  <pre id="ydsi">./cell-management-tool trust-infra-certs --vsphere --unattended
Downloading certificates for 2 host(s), including 1 vCenter(s):
        vc-mng.lab.ihumster.ru                                      [Download: SUCCESS] (VMCA)
        vc-mng.lab.ihumster.ru                                      [Download: SUCCESS] (VMCA)
        vc-mng.lab.ihumster.ru                                      [Download: SUCCESS]
        nsx.lab.ihumster.ru                                         [Download: SUCCESS]
Downloaded certificates for 3/2 host(s).
Trusting certificates for 2 host(s):
        vc-mng.lab.ihumster.ru                                       [Already Trusted: SUCCESS]
        nsx.lab.ihumster.ru                                          [Already Trusted: SUCCESS]
Trusting VMCA certificates for 1 vCenter(s):
        vc-mng.lab.ihumster.ru                                       [Add Trusted: SUCCESS] (VMCA) - alias=vc-mng.lab.ihumster.ru_vmca_2023-02-24-11-05-13
Trusted 3/3 downloaded certificates.</pre>
  <p id="BxPL">Или самостоятельно скачать с vCenters их сертификаты VMCA (&lt;vcenter&gt;/certs/download.zip) и вручную добавить VMCA сертификаты в Trusted Certificates вашего VCD.</p>
  <p id="TYoj">Для VCD с большим количеством vCenters (или организаций с высокими требованиями к безопасности инфраструктурных компонентов) самым правильным путём, по моему мнению, будет вариант с <a href="https://kb.vmware.com/s/article/2097936#use_of_replacing_vmca_certificate_with_custom_ca_certificate" target="_blank">заменой сертификата VMCA</a> на vCenters на подписанный с внутреннего или внешнего CA и последующей регенерацией сертификатов ESXi.</p>

]]></content:encoded></item><item><guid isPermaLink="true">https://blog.ihumster.ru/vcda-initial-setup-api</guid><link>https://blog.ihumster.ru/vcda-initial-setup-api?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=ihumster</link><comments>https://blog.ihumster.ru/vcda-initial-setup-api?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=ihumster#comments</comments><dc:creator>ihumster</dc:creator><title>VMware Cloud Director Availability Initial Setup API</title><pubDate>Mon, 23 Sep 2024 07:02:53 GMT</pubDate><description><![CDATA[Встала задача по автоматизации развертывания VCDA для инсталляций VCD где VCDA еще не развернут. Начав изучение вопроса и прочтя внимательно документацию по VCDA с удивлением понял что для развертывания самих аплайнсов есть способ по автоматизации процесса, т.к. аплайнсы поставляются в формате OVF с обычными, для аплайнсов VMware, возможностями деплоя их как с помощью UI, так и с помощью OVFTool ну и прочими средствами (например, community.vmware.vmware_deploy_ovf из коллекции ansible). Но для дальнейшей первоначальной настройки нет иного официального пути, кроме как пойти в UI и потыкать кнопки, заполняя поля в Initial Setup Wizard.]]></description><content:encoded><![CDATA[
  <p id="025Y">Встала задача по автоматизации развертывания VCDA для инсталляций VCD где VCDA еще не развернут. Начав изучение вопроса и прочтя внимательно документацию по VCDA с удивлением понял что для развертывания самих аплайнсов есть способ по автоматизации процесса, т.к. аплайнсы поставляются в формате OVF с обычными, для аплайнсов VMware, возможностями деплоя их как с помощью UI, так и с помощью OVFTool ну и прочими средствами (например, community.vmware.vmware_deploy_ovf из коллекции ansible). Но для дальнейшей первоначальной настройки нет иного официального пути, кроме как пойти в UI и потыкать кнопки, заполняя поля в Initial Setup Wizard.</p>
  <p id="KBLT">Вооружившись DevTools я за несколько итераций записал все необходимые API вызовы, которые происходят при выполнении этапов Initial Setup Wizard и после, проверив возможность их воспроизведения сторонними средствами (воспроизводил в Postman) решил что хочу записать их в виде документации OpenAPI 3.0 что бы любой мог импортировать ее в Postman и ознакомиться с деталями запросов.</p>
  <p id="lLZN">По этому спешу представить Вам первую версию <a href="https://github.com/ihumster/VCDAInitialSetupAPI" target="_blank">документации </a>к VCDA Initial Setup API. В репозиторий добавлена также готовая коллекция для Postman (на случай если вы не умеете генерировать коллекцию из OpenAPI спецификации).</p>
  <p id="fOv6">В коллекции все запросы пронумерованы в порядке выполнения их в Wizard. Отдельно не пронумерован один Get запрос - он используется для проверки доступности той или иной системы (VCD, vCenter server, ReplicatorAppliance, Tunnel Appliance) из VCDA Manager, а также возвращает SSL-сертификат используемый системой (сам сертефикат в PEM-encoded формате, его thumbprint - используются в остальных запросах).</p>
  <p id="qZTY">Конфигурация VCDA которую я изучал - это рекомендуемая в продуктивной среде конфигурация из отдельных &quot;ролевых&quot; аплайнсов - manager, replicator, tunnel. Для combined и vc_combined версии, последовательность и структура запросов скорее всего иная (в vc_combined например не используется VCD).</p>
  <p id="3gNR">Также, если вы используете несколько replicator аплайнсов то соответствующие запросы необходимо будет проделать для каждого аплайнса последовательно.</p>
  <p id="VRaF">Для работы с этим API вам необходимы минимум 3 задеплоенных удобным вам способом аплайнса.</p>

]]></content:encoded></item><item><guid isPermaLink="true">https://blog.ihumster.ru/vcd-api-with-postman-2</guid><link>https://blog.ihumster.ru/vcd-api-with-postman-2?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=ihumster</link><comments>https://blog.ihumster.ru/vcd-api-with-postman-2?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=ihumster#comments</comments><dc:creator>ihumster</dc:creator><title>Комфортная работа в Postman с VMware Cloud Director (2)</title><pubDate>Mon, 23 Sep 2024 07:02:07 GMT</pubDate><category>Cloud Director</category><description><![CDATA[В процессе работы с OpenAPI разных версий VMware Cloud Director понял что меня раздражает постоянно следить за версией API, которая указывается в Header 'Accept'. Чтобы автоматизировать актуализацию версии API в моих запросах я модифицировал свой pre-request Script из предыдущей статьи.]]></description><content:encoded><![CDATA[
  <p id="ZQaK">В процессе работы с OpenAPI разных версий VMware Cloud Director понял что меня раздражает постоянно следить за версией API, которая указывается в Header &#x27;Accept&#x27;. Чтобы автоматизировать актуализацию версии API в моих запросах я модифицировал свой pre-request Script из предыдущей статьи.</p>
  <p id="qTQ5">Добавьте следующую строку между запросами версии и получением access_token, либо после них:</p>
  <pre id="CXUc">pm.request.headers.get(&#x27;Accept&#x27;).replace(&#x27;application\/json;version=(?&lt;version&gt;\d{2}\.\d{1})&#x27;,&#x27;application/json;version={{apiVersion}}&#x27;)</pre>

]]></content:encoded></item><item><guid isPermaLink="true">https://blog.ihumster.ru/regex-url-rfc</guid><link>https://blog.ihumster.ru/regex-url-rfc?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=ihumster</link><comments>https://blog.ihumster.ru/regex-url-rfc?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=ihumster#comments</comments><dc:creator>ihumster</dc:creator><title>Regexp для парсинга URL по RFC</title><pubDate>Mon, 23 Sep 2024 07:01:22 GMT</pubDate><description><![CDATA[Не претендуя на истину в последней инстанции, однако, хотелось закрыть для себя этот вопрос на всегда. Частенько в скриптах posh\python и т.п. приходится на входе принимать в качестве параметра строку с URL и быть уверенным что это:]]></description><content:encoded><![CDATA[
  <p id="S2TW">Не претендуя на истину в последней инстанции, однако, хотелось закрыть для себя этот вопрос на всегда. Частенько в скриптах posh\python и т.п. приходится на входе принимать в качестве параметра строку с URL и быть уверенным что это:</p>
  <ol id="nAC9">
    <li id="LP3p">точно URL</li>
    <li id="Lxy1">иметь возможность легко получить его составляющие (схему\fqdn\query\etc)</li>
  </ol>
  <p id="oogw">Подсматривая в интернет, получилось написать вот такую &quot;красивую&quot; регулярку, которая входящую строку бъет на следующие группы:</p>
  <ol id="MrcK">
    <li id="qd3v">url (собственно если строка соответствует паттерну url - в этой группе будет отображаться полный url</li>
    <li id="lOZ5">scheme - схема URL http:// | ftp:// | anycustomscheme://</li>
    <li id="YA5Q">hostname - FQDN</li>
    <li id="xay0">port</li>
    <li id="SAQ9">path</li>
    <li id="xAdb">query</li>
  </ol>
  <p id="RAx1">Как работает этот regexp вы можете увидеть ниже</p>
  <figure id="sYYw" class="m_original">
    <img src="https://img2.teletype.in/files/5d/26/5d264052-16e5-47cc-91f8-8eee91e3a63b.png" width="1940" />
  </figure>
  <pre id="7Yum" data-lang="regex">(?&lt;url&gt;(?:(?&lt;scheme&gt;[a-zA-Z]+:\/\/)?(?&lt;hostname&gt;(?:[-a-zA-Z0-9@%_\+~#=]{1,256}\.){1,256}(?:[-a-zA-Z0-9@%_\+~#=]{1,256})))(?::(?&lt;port&gt;[[:digit:]]+))?(?&lt;path&gt;(?:\/[-a-zA-Z0-9!$&amp;&#x27;()*+,\\\/:;=@\[\]._~%]*)*)(?&lt;query&gt;(?:(?:\#|\?)[-a-zA-Z0-9!$&amp;&#x27;()*+,\\\/:;=@\[\]._~]*)*))</pre>
  <p id="ZtPB">На Python именованные группы нужно обозначать через символ P, поэтому для Python regex будет выглядеть чуть иначе:</p>
  <pre id="zrZv" data-lang="regex">(?P&lt;url&gt;(?:(?P&lt;scheme&gt;[a-zA-Z]+:\/\/)?(?P&lt;hostname&gt;(?:[-a-zA-Z0-9@%_\+~#=]{1,256}\.){1,256}(?:[-a-zA-Z0-9@%_\+~#=]{1,256})))(?::(?P&lt;port&gt;[[:digit:]]+))?(?P&lt;path&gt;(?:\/[-a-zA-Z0-9!$&amp;&#x27;()*+,\\\/:;=@\[\]._~%]*)*)(?P&lt;query&gt;(?:(?:\#|\?)[-a-zA-Z0-9!$&amp;&#x27;()*+,\\\/:;=@\[\]._~]*)*))</pre>
  <p id="lBCT">Ну и пример кода на Powershell:</p>
  <pre id="Vi4h" data-lang="powershell">$URL = &quot;https://vcloud.lab.ihumster.ru/api/query?type=adminOrgVdcStorageProfile&quot;
$URL -match &quot;(?&lt;url&gt;(?:(?&lt;scheme&gt;[a-zA-Z]+:\/\/)?(?&lt;hostname&gt;(?:[-a-zA-Z0-9@%_\+~#=]{1,256}\.){1,256}(?:[-a-zA-Z0-9@%_\+~#=]{1,256})))(?::(?&lt;port&gt;[[:digit:]]+))?(?&lt;path&gt;(?:\/[-a-zA-Z0-9!$&amp;&#x27;()*+,\\\/:;=@\[\]._~%]*)*)(?&lt;query&gt;(?:(?:\#|\?)[-a-zA-Z0-9!$&amp;&#x27;()*+,\\\/:;=@\[\]._~]*)*))&quot;
$Matches

True

Name                           Value
----                           -----
path                           /api/query
hostname                       vcloud.lab.ihumster.ru
url                            https://vcloud.lab.ihumster.ru/api/query?type=adminOrgVdcStorageProfile
scheme                         https://
query                          ?type=adminOrgVdcStorageProfile
0                              https://vcloud.lab.ihumster.ru/api/query?type=adminOrgVdcStorageProfile</pre>
  <p id="6Rkz">Операнд -math возвращает boolean результат соответствия строки в $URL нашему регулярному выражению и если он True, то автопеременная $Matches будет содержать key-value словарь в котором ключами будут имена групп с соответствующими values.</p>
  <p id="4RV8">Спасибо что дочитали до конца, надеюсь это будет вам полезно.</p>

]]></content:encoded></item><item><guid isPermaLink="true">https://blog.ihumster.ru/vcd-api-with-postman</guid><link>https://blog.ihumster.ru/vcd-api-with-postman?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=ihumster</link><comments>https://blog.ihumster.ru/vcd-api-with-postman?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=ihumster#comments</comments><dc:creator>ihumster</dc:creator><title>Комфортная работа в Postman с VMware Cloud Director</title><pubDate>Mon, 23 Sep 2024 07:00:31 GMT</pubDate><media:content medium="image" url="https://img1.teletype.in/files/0e/87/0e875be6-6656-4439-872f-7f9aaa2ae8e1.png"></media:content><category>Cloud Director</category><description><![CDATA[<img src="https://blog.ihumster.ru/wp-content/uploads/2022/07/image-2.png"></img>В последний раз что-то полезное на эту тему писал Tomas Fojta в, далеком уже, 2018 году. С тех пор в VCD появились новые интересные методы API и в частности в 10.3.1 появились Cloud Director API Token.]]></description><content:encoded><![CDATA[
  <p id="XjFW">В последний раз что-то полезное на эту <a href="https://fojta.wordpress.com/2018/10/18/postman-and-vcloud-director-9-5-access-token-authentication/" target="_blank">тему писал</a> Tomas Fojta в, далеком уже, 2018 году. С тех пор в VCD появились новые интересные методы API и в частности в 10.3.1 появились <a href="https://blogs.vmware.com/cloudprovider/2022/03/cloud-director-api-token.html" target="_blank">Cloud Director API Token</a>.</p>
  <p id="3GdV">По этому я решил слегка модернизировать подход к работе с VCD API в Postman с использованием токенов.</p>
  <p id="UV4l">Если вам интересно - добро пожаловать под cut.</p>
  <p id="zjLZ">Подход к работе с токенами/авторизацией и вообще разными инстансами VCD в Postman будет основан на использовании всей доступной в Postman функциональности <strong>Workspaces</strong>, <strong>Collections</strong>, <strong>Variables </strong>и <strong>Scripts</strong>.</p>
  <p id="NtrJ">Первым делом создадим для работы с VCD отдельный <strong>Workspace </strong>и дадим ему имя (например, внезапно, VMware Cloud Director).</p>
  <figure id="t7SK" class="m_original">
    <img src="https://img2.teletype.in/files/db/9f/db9fe05e-914a-4003-a1dd-21a089ccc63d.png" width="363" />
  </figure>
  <figure id="EJ9g" class="m_original">
    <img src="https://img4.teletype.in/files/34/6b/346b017c-8e95-416e-9ff9-da2eeeaf463e.png" width="572" />
  </figure>
  <p id="DNU8">Создадим коллекцию запросов назвав ее, ну например, VCD API.</p>
  <figure id="BTON" class="m_original">
    <img src="https://img1.teletype.in/files/0f/57/0f574e7a-e544-4b69-8782-130e08e76aaf.png" width="382" />
  </figure>
  <h2 id="tb7B">Переменные окружения и глобальные переменные</h2>
  <p id="L2jp">Откроем блок глобальных переменных и создадим там следующие переменные:</p>
  <figure id="NQBF" class="m_original">
    <img src="https://img1.teletype.in/files/00/17/0017722a-f478-44e7-aa56-620be57d5c5b.png" width="730" />
  </figure>
  <figure id="36J2" class="m_original">
    <img src="https://img4.teletype.in/files/fe/e6/fee6fbcd-5a66-4a9c-9d62-73d3ab38c8bd.png" width="1060" />
  </figure>
  <pre id="GDRJ">apiVersion
accept-vcloud-xml: application/*+xml;version={{apiVersion}}
accept-vcloud-json: application/*+json;version={{apiVersion}}</pre>
  <p id="uul3">Это будут переменные общего назначения, и наиболее часто используемые, т.к. именно через поле заголовка Accept в запросе передается используемая версия API.</p>
  <p id="FrcI">Не забудьте сохранить глобальные переменные (кнопка Save).</p>
  <p id="UCfc">Следующим этапом будет создание переменных окружения. Наборы переменных окружения позволяют нам использовать одни и те же запросы к разным инстансам VCD. Выбор текущего набора переменных окружения позволит вам работать с конкретным инстансом VCD.</p>
  <figure id="7SC5" class="m_original">
    <img src="https://img4.teletype.in/files/b3/eb/b3eb4dba-cf84-417c-99a3-35d812552e27.png" width="732" />
  </figure>
  <figure id="fwg0" class="m_original">
    <img src="https://img3.teletype.in/files/65/84/65843d98-2c08-4158-99dc-a59825fcf672.png" width="1064" />
  </figure>
  <p id="HIKM">В окружении &quot;ihumster lab&quot; (дайте своему окружению имя, позволяющее конкретно идентифицировать инстанс с котороым будет вестись работа) будет 3 переменных:</p>
  <p id="pntO"><strong>vcd-url</strong> - базовый url вашего инстанса VCD (я намеренно не указываю url вида https://vcd-fqdn/api т.к. у VCD с некоторых пор 2 API - классический XML-RPC и новый OpenAPI)</p>
  <p id="FljG"><strong>bearer</strong> - переменная типа secret, ее initial значение пустое, т.к. получать значение переменная будет в процессе работы</p>
  <p id="qIgK"><strong>user_token</strong> - переменная типа secret, содержащая токен вашего пользователя, позволяющий получить bearer токен без аутентификации с логином и паролем (как получить этот токен описано в <a href="https://blogs.vmware.com/cloudprovider/2022/03/cloud-director-api-token.html" target="_blank">статье VMware</a>).</p>
  <section style="background-color:hsl(hsl(323, 50%, var(--autocolor-background-lightness, 95%)), 85%, 85%);">
    <h4 id="523R">Важно!</h4>
  </section>
  <p id="EjZA">Имейте ввиду, с использованием этого API Token вы не сможете через API:</p>
  <ul id="l3oo">
    <li id="gYRg">Менять пароль пользователя</li>
    <li id="LUzn">Управлять пользователями</li>
    <li id="HEFB">Создавать новые API Tokens</li>
    <li id="yhr6">Видеть и отзывать текущие API Tokens</li>
  </ul>
  <p id="PKrE">А также, при использовании API Token, для следующих ресурсов будет иметься доступ только для чтения:</p>
  <ul id="A8uq">
    <li id="jUNy">User</li>
    <li id="iMcN">Group</li>
    <li id="T3g7">Roles</li>
    <li id="RxLp">Global roles</li>
    <li id="nou7">Rights bundles</li>
  </ul>
  <p id="ZW0u">Не забудьте сохранить ваши переменные (кнопка Save). Итого ваше окружение будет выглядеть так:</p>
  <figure id="X1qn" class="m_original">
    <img src="https://img1.teletype.in/files/85/e6/85e6ac7c-59e2-4140-8a07-e3f41ac42661.png" width="730" />
  </figure>
  <h2 id="AH8q">Настройки коллекции</h2>
  <p id="vLfp">Для нашей созданной ранее коллекции мы будем настраивать следующие свойства:</p>
  <p id="Eyz7"><strong>Authorization</strong></p>
  <p id="h6z4">Выбераем тип <strong>Bearer Token</strong> и в свойство Token вписываем переменную <strong>{{bearer}}</strong>.</p>
  <p id="cwoK">Теперь любой созданный в коллекции запрос будет использовать наследуемый тип авторизации через токен.</p>
  <figure id="z8el" class="m_original">
    <img src="https://img4.teletype.in/files/3f/4d/3f4d206a-9769-4e9a-9c5b-165cc0df70db.png" width="760" />
  </figure>
  <figure id="E5wt" class="m_original">
    <img src="https://img3.teletype.in/files/22/95/229528ca-a58b-401f-9564-809e2b012cf7.png" width="1326" />
  </figure>
  <p id="pre-request-script"><strong>Pre-request Script</strong></p>
  <p id="1Bb7">Самая важная часть всей затеи - скрипт, который будет делать две важные вещи:</p>
  <ol id="rMr1">
    <li id="nYUS">Получать последнюю версию API (т.е. текущую для нашего инстанса VCD) и сохранять ее в глобальную переменную <strong>apiVersion</strong>.</li>
    <li id="Q8A2">Получать токен доступа от VCD с использованием нашего <strong>user_token</strong> и сохранение его в переменную окружения <strong>bearer</strong>.</li>
  </ol>
  <pre id="mnxd" data-lang="javascript">// Get last version of VCD and save it to &#x27;apiVersion&#x27; global variable
let urlVer = pm.environment.get(&quot;vcd-url&quot;) + &quot;/api/versions&quot;
const urpRequest = {
    url: urlVer,
    method: &#x27;GET&#x27;,
    header: {
        &#x27;Accept&#x27;: &#x27;application/*+json&#x27;,
        &#x27;Content-Type&#x27;: &#x27;application/json&#x27;
    }
}
pm.sendRequest(urpRequest, (error,repsponse) =&gt;{
    if (error){
        console.log(error)
    } else {
        pm.globals.set(&#x27;apiVersion&#x27;, repsponse.json().versionInfo.last().version)
    }
})

// Get access token using env var &#x27;user_token&#x27; and transform it to Bearer token data 
// with save to &#x27;bearer&#x27; env var
let urlTok = pm.environment.get(&quot;vcd-url&quot;) + &quot;/oauth/provider/token&quot;
let body = &#x27;grant_type=refresh_token&amp;refresh_token=&#x27; + pm.environment.get(&#x27;user_token&#x27;)
const postRequest = {
  url: urlTok,
  method: &#x27;POST&#x27;,
  header: {
    &#x27;Accept&#x27;: &#x27;application/json&#x27;,
    &#x27;Content-Type&#x27;: &#x27;application/x-www-form-urlencoded&#x27;
  },
  body: {
    mode: &#x27;raw&#x27;,
    raw: body
  }
};

pm.sendRequest(postRequest, (error, response) =&gt;{
    if (error){
        console.log(error)
    } else {
        pm.environment.set(&#x27;bearer&#x27;, response.json().access_token)
    }
})</pre>
  <p id="BANo">Остается только проверить работу этого скрипта. Давайте попробуем получить ответ на запрос GET /api/admin/ (надо понимать что ваш user_token взят у пользователя из организации system и имеет права для запросов из раздела /api/admin/).</p>
  <p id="fRCN">Создадим запрос типа GET, в url впишем конечный адрес с использованием переменной <strong>{{vcd-url}}</strong>, в разделе Headers добавим заголовок <strong>Accept </strong>с value <strong>{{accept-vcloud-json}}</strong>, и нажмем <strong>Send</strong>.</p>
  <p id="ux4e">В результате ваш запрос выполнит сначала Pre-request Script (т.е. получит поддерживаемую версию API и получит access token для использования в запросе).</p>
  <figure id="OBmb" class="m_original">
    <img src="https://img4.teletype.in/files/f6/16/f616cd05-9166-4657-bf46-066eda3ed93c.png" width="1024" />
  </figure>
  <h2 id="2Fv7">Использование данного подхода к OpenAPI</h2>
  <p id="CKaF">Залогиньтесь в провайдерскую консоль вашего VCD и откройте его API Exporer (Help-&gt; API Exporer) и сохраните на локальный диск его Swagger файл (cloudapi.json)</p>
  <figure id="GkB8" class="m_original">
    <img src="https://img2.teletype.in/files/1b/7d/1b7d162c-dbdb-4c82-8a10-e2fb2714fc7f.png" width="659" />
  </figure>
  <p id="tdXQ">Перейдите в Postman в раздел APIs и нажмте + после чего в разделе Import выберите ранее скаченный файл.</p>
  <p id="Rb12">Postman покажет вам тип API (Swagger 2.0) и предложит сгенерировать новую коллекцию запросов из этого API.</p>
  <figure id="3Mzd" class="m_original">
    <img src="https://img2.teletype.in/files/58/a8/58a86a01-0670-4ff4-9830-1f351a9c24c2.png" width="555" />
  </figure>
  <p id="PkSb">Имейте ввиду, после процесса импорта окно не закрывается, а остается активным с вновь доступной кнопкой Import. Нажимать ее повторно не нужно - просто закройте окно крестиком.</p>
  <p id="U749">В коллекциях у вас появится новая коллекция VMware Cloud Director OpenAPI с набором всех доступных методов и их описанием (документацией) - это очень удобно (требуется лишь изредка обновлять коллекцию при выходе новых версий API).</p>
  <p id="uDt6">Что бы использовать эту коллекцию с всеми удобствами созданных нами ранее переменных и скриптов проделаем следующее:</p>
  <ol id="1js5">
    <li id="491t">Изменим тип авторизации с API Key на Bearer Token и вставим в поле Token <strong>{{bearer}}</strong>.</li>
    <li id="zdyl">В разделе Pre-request Script вставим <a href="#pre-request-script">аналогичный код</a></li>
    <li id="xmAj">В разделе Variables значение переменной baseUrl замените с /cloudapi на<strong> {{vcd-url}}</strong>/cloudapi для Initial и Current</li>
  </ol>
  <p id="Xb9i">После чего можно проверить новую коллекцию с помощью запроса <strong>Get base navigation links</strong>. В настройки запроса достаточно добавить только заголовок Accept с переменной <strong>{{accept-vcloud-json}}</strong></p>
  <figure id="YeWK" class="m_original">
    <img src="https://img4.teletype.in/files/31/f6/31f6ff19-df0c-415d-a793-c1a95cda83c3.png" width="1024" />
  </figure>
  <p id="L0nE">Спасибо что дочитали до конца!</p>

]]></content:encoded></item></channel></rss>