DevOps & Drupal

Дубовской Александр
dan@ra-don.ru, @adubovskoy

... чуть не забыл

PHP это:
- медленно

- язык разметки внутри html
- плохие практики
- нет фреймворков и сахара
- нет нормальных генераторов и репозитория готовых решений

- язык, призванный умирать и неподходящий для длинных очередей

... а cms это

- медленно
- плохие практики
- нет консольных утилит и возможности писать структурированный код
- нет нормальных генераторов и репозиториев
- для тех кто не осилил в программирование (мышкокликеров)

Text

MVPv3

Drupal8

  • Symfony в ядре (routing/serialization/service container, etc...)
  • Twig как шаблонизатор
  • Yaml как хранилище конфигураций
  • Composer как менеджер зависимостей

Кто настраивал деплой любой CMS?

А у кого получилось сделать безшовный CI, чтобы после коммита в мастер не нужно было ничего делать?

Где начинаются проблемы?

Файлы (git)

База (миграции)

Dev

Prod

Заказчик:

"Но мы вот должны использовать этот сторонний модуль..."

Ад готовых решений

  1. Не всегда следует CodeStyle

  2. Не всегда хорошая архитектура

  3. Всегда - своя схема данных в базе

Что делать?

Зрелое сообщество

1. В любой непонятной ситуации пишите API

Configuration API

State API

Dev -> Prod (набор yml файлов конфигураций)

2. Запретите писать плохо

В друпале есть только 1 способ писать формы для админки

use DrupalCoreFormConfigFormBase;

class DemoForm extends ConfigFormBase {

// ...

public function buildForm(array $form, array &$form_state) {

  $config = $this->config('demo.settings');

  $form['email'] = array(
    '#type' => 'email',
    '#title' => $this->t('Your email address.'),
    '#default_value' => $config->get('demo.email_address')
  );

  return $form;
}

// ...

}

Что посмотреть / почитать

  • Блог Niklan - https://niklan.net/blog/190 (примеры с GitlabCI)
  • Наш блог - https://www.ra-don.ru/blog
  • https://www.lullabot.com/articles/a-successful-drupal-8-deployment (примеры с Jenkins)
  • https://nuvole.org/blog/2016/aug/19/optimal-deployment-workflow-composer-based-drupal-8-projects

Контент? Спасибо UUID

CLI? Пожалуйста

  • drush - для операционного управления
    • управление любыми настройками (модулями, блоками, правами доступа)
    • генерация пользователей/паролей
    • операции/миграции с БД
    • операции с очередями
    • etc
  • drupal console - генератор

Бонусы конфигураций

1. Config readonly

if (isset($_ENV['AH_SITE_ENVIRONMENT']) && $_ENV['AH_SITE_ENVIRONMENT'] === 'prod') {
  $settings['config_readonly'] = TRUE;
}

2.Features / UI diffs

Что с фронтом?

# Test API endpoints

test_api.get:
  path: 'my-api/get.json'
  defaults: { _controller: '\Drupal\test_api\Controller\TestAPIController::get_example' }
  methods:  [GET]
  requirements:
    _access: 'TRUE'

test_api.put:
  path: 'my-api/put.json'
  defaults: { _controller: '\Drupal\test_api\Controller\TestAPIController::put_example' }
  methods:  [PUT]
  requirements:
    _access: 'TRUE'

test_api.post:
  path: 'my-api/post.json'
  defaults: { _controller: '\Drupal\test_api\Controller\TestAPIController::post_example' }
  methods:  [POST]
  requirements:
    _access: 'TRUE'

test_api.delete:
  path: 'my-api/delete.json'
  defaults: { _controller: '\Drupal\test_api\Controller\TestAPIController::delete_example' }
  methods:  [DELETE]
  requirements:
    _access: 'TRUE'
<?php
/**
 * @file
 * Contains \Drupal\test_api\Controller\TestAPIController.
 */
namespace Drupal\test_api\Controller;
use Drupal\Core\Controller\ControllerBase;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\JsonResponse;
/**
 * Controller routines for test_api routes.
 */
class TestAPIController extends ControllerBase {
  /**
   * Callback for `my-api/get.json` API method.
   */
  public function get_example( Request $request ) {
    $response['data'] = 'Some test data to return';
    $response['method'] = 'GET';
    return new JsonResponse( $response );
  }
  /**
   * Callback for `my-api/put.json` API method.
   */
  public function put_example( Request $request ) {
    $response['data'] = 'Some test data to return';
    $response['method'] = 'PUT';
    return new JsonResponse( $response );
  }
  /**
   * Callback for `my-api/post.json` API method.
   */
  public function post_example( Request $request ) {
    // This condition checks the `Content-type` and makes sure to 
    // decode JSON string from the request body into array.
    if ( 0 === strpos( $request->headers->get( 'Content-Type' ), 'application/json' ) ) {
      $data = json_decode( $request->getContent(), TRUE );
      $request->request->replace( is_array( $data ) ? $data : [] );
    }
    $response['data'] = 'Some test data to return';
    $response['method'] = 'POST';
    return new JsonResponse( $response );
  }
  /**
   * Callback for `my-api/delete.json` API method.
   */
  public function delete_example( Request $request ) {
    $response['data'] = 'Some test data to return';
    $response['method'] = 'DELETE';
    return new JsonResponse( $response );
  }
}

ContentaCMS

сборка для фронтендеров

Демо:

  • https://contenta-angular.firebaseapp.com/home - angular
  • https://contenta-elm.firebaseapp.com/ - elm
  • https://contentanuxt.now.sh/ vue+ nuxt
  • https://github.com/contentacms - все демо
{#graphql
query {
  admin:userById(id: "1") {
    uid
    name
  }
  user:currentUserContext {
    uid
  }
}
#}

{% extends '@bartik/block.html.twig' %}
{% block content %}
  {% embed '@graphql_twig/query.html.twig' %}
    {% block content %}
      {% set admin = graphql.admin %}
      {% set user = graphql.user %}
      <div{{ content_attributes.addClass('content') }}>
        {{ content }} and
          {% if user.uid == admin.uid %}
            you, {{ admin.name }}.
          {% else %}
            you, appreciated anonymous visitor.
          {% endif %}
      </div>
    {% endblock %}
  {% endembed %}
{% endblock %}

https://www.drupal.org/project/graphql_twig

Спасибо!

Krasnodar Backend miniConf 2019

By Alexander Dubovskoy

Krasnodar Backend miniConf 2019

Aboud drupal for dev/front

  • 910