logo

Dodatni tipovi blokova (EBT) - Novo iskustvo rada sa Layout Builder-om❗

Dodatni tipovi blokova (EBT) – stilizovani, prilagodljivi tipovi blokova: slajdšouvi, kartice sa tabovima, kartice, akordeoni i mnogi drugi. Ugrađena podešavanja za pozadinu, DOM Box, javascript dodatke. Iskusite budućnost kreiranja rasporeda već danas.

Demo EBT moduli Preuzmite EBT module

❗Dodatni tipovi pasusa (EPT) – Novo iskustvo rada sa pasusima

Dodatni tipovi pasusa (EPT) – analogni skup modula zasnovan na pasusima.

Demo EPT moduli Preuzmite EPT module

GLightbox is a pure javascript lightbox (Colorbox alternative without jQuery)❗

It can display images, iframes, inline content and videos with optional autoplay for YouTube, Vimeo and even self-hosted videos.

Demo GLightbox Download GLightbox

Scroll

Drupal CI‑vođeno upravljanje konfiguracijom korišćenjem Jenkins‑a i GitLab CI‑ja

16/04/2026, by Ivan

1. Zašto je CI‑vođeno upravljanje konfiguracijom važno

Drupalov sistem konfiguracije je jedna od najvećih prednosti platforme — i jedan od njenih najpouzdanijih izvora problema. Mogućnost izvoza i uvoza svakog dela konfiguracije sajta kao YAML fajlova je moćna, ali samo ako se svi slažu oko toga ko je odgovoran za prenos tih fajlova između okruženja. U većini timova, taj dogovor zapravo nikada ne postoji.

Klasični problemi su dobro poznati svakome ko je isporučio Drupal sajt:

  • Odstupanje konfiguracije (config drift) — staging se razlikuje od produkcije, produkcija od lokalnog okruženja, i niko nije siguran koje okruženje je kanonsko.
  • "Radi na stagingu ali ne i na produkciji" — jer je neko izmenio prikaz ili formatter polja na stagingu i nikada ga nije izvezao.
  • Ručni drush cim koji kvari sadržaj — brzinski import u 23h koji je obrisao polje tipa sadržaja na koje se i dalje pozivaju aktivni čvorovi.

Osnovni uzrok svakog od ovih scenarija je isti: čovek odlučuje kada i da li će konfiguracija biti promovisana. Ljudi zaboravljaju. Preskaču korake pod pritiskom. Donose procene koje se kasnije pokažu kao pogrešne.

CI ne zaboravlja. Pipeline ili prolazi ili ne prolazi. Nema standup na koji mora da stigne. Ne zna da je izdanje za dvadeset minuta. Ta determinističnost je upravo ono što je potrebno upravljanju konfiguracijom.

Obećanja koja ovaj članak ispunjava:

  • Svaka promena konfiguracije je komitovana u Git pre nego što dotakne bilo koje zajedničko okruženje.
  • Pipeline, a ne developer, je odgovoran za validaciju i import konfiguracije.
  • Promocija između okruženja ne zahteva nijedan ručni korak.
  • Config drift je pad build‑a, a ne Slack poruka.
Pretpostavke: Koristite Drupal 10 ili 11, Git‑bazirani workflow sa feature granama i deploy‑ujete na najmanje dva zajednička okruženja (npr. staging i produkcija). Vaš tim koristi Jenkins ili GitLab CI — ili oba.

2. Osnovni principi koje smo naučili iz stvarnih projekata

Konfiguracija je kod

Ako menja ponašanje sajta, pripada u Git. Tačka. Prikaz, tip sadržaja, podešavanje performansi, stil slike — sve je to kod. Tretirajte config fajlove sa istom disciplinom kao PHP fajlove: pregledajte ih, verzionišite ih, nikada ih ne menjajte direktno u zajedničkom okruženju.

Nema ručnog drush cim u zajedničkim okruženjima

Pipeline uvozi konfiguraciju. Developeri ne. Ovo pravilo zvuči ekstremno dok prvi put ne doživite da neko pokrene drush cim na produkciji sa nekomitovanim lokalnim izmenama u svom radnom direktorijumu.

Pipeline mora brzo da padne pri config drift‑u

Import konfiguracije, a zatim njen trenutni export, ne bi trebalo da proizvede nikakav diff. Ako proizvede, build pada. Ovo jedno pravilo hvata više bagova nego bilo koja druga provera koju smo uveli.

Iskusni developer je debagovao sporu stranicu pretrage na produkciji. Izmenio je podešavanja Search API indeksa preko UI‑ja, potvrdio poboljšanje performansi i zatvorio tiket. Dve nedelje kasnije, release je importovao konfiguraciju iz Git‑a — prepisavši njegove izmene iz UI‑ja — i performanse pretrage su se urušile. Niko nije povezao događaje tri dana. Nakon tog incidenta, pravilo je postalo eksplicitno: svaka UI izmena koja nije odmah izvezena i komitovana smatra se izgubljenom.
Na sajtu sa puno medijskog sadržaja, core.extension.yml je bio isključen iz izvoza jer "module ionako upravlja Composer." Tri meseca kasnije, hotfix je ponovo omogućio modul koji je namerno bio onemogućen u produkciji. Hotfix je bio ispravan — ali je naredni config import tiho ponovo onemogućio modul. Zbog tog incidenta sada core.extension.yml nazivamo najopasnijim fajlom koji možete ignorisati.

3. Arhitektura na visokom nivou

👨‍💻 Developer
drush cex
📁 Git repozitorijum
config/sync
⚙ CI pipeline
validacija + import
🌐 Dev
🌐 Stage
🌐 Prod

Konfiguracija teče samo u jednom smeru: od lokalnog export‑a developera, kroz Git, kroz CI pipeline i u svako okruženje.

Ključni uvid je razdvajanje odgovornosti:

AkterOdgovornostNikada nije odgovoran za
DeveloperExport konfiguracije, commit u Git, otvaranje MR/PRImport konfiguracije u bilo kom zajedničkom okruženju
CI pipelineValidacija, import, verifikacija, promocijaGenerisanje ili izmena config fajlova
OkruženjePokretanje sajtaGenerisanje, čuvanje ili export konfiguracije

Okruženja su potrošači konfiguracije, nikada proizvođači. Onog trenutka kada okruženje postane izvor istine za konfiguraciju, odstupanje postaje neizbežno.

4. Struktura repozitorijuma koja se skalira

Stablo direktorijuma koren projekta
project-root/
├── composer.json
├── composer.lock
├── Jenkinsfile
├── .gitlab-ci.yml
│
├── ci/
│   ├── drupal-config-check.sh   # Skripta za višekratnu validaciju
│   ├── drupal-deploy.sh
│   └── drupal-install.sh
│
├── config/
│   ├── sync/                        # Config se nalazi ovde — komitovan u Git
│   │   ├── core.extension.yml
│   │   ├── system.site.yml
│   │   └── ...
│   └── splits/                      # config_split override‑i po okruženju
│       ├── development/
│       ├── staging/
│       └── production/
│
└── web/
    ├── sites/
    │   └── default/
    │       ├── settings.php          # Komitovan, bez tajni
    │       ├── settings.local.php    # Ignorisan u Git‑u, lokalni override‑i
    │       └── settings.env.php      # Učitava CI iz env varijabli
    └── ...

Šta se nalazi u config/sync

Svi YAML fajlovi konfiguracije generisani pomoću drush cex. Ovaj direktorijum je jedini izvor istine za konfiguraciju sajta. Komituje se, pregleda i deploy‑uje kao i bilo koji drugi kod.

Šta nikada ne ide u config

  • Hostname‑ovi specifični za okruženje, API ključevi ili akreditivi — koristite environment varijable i settings.env.php.
  • Tajne bilo koje vrste — ubacite ih preko CI skladišta tajni (Jenkins Credentials ili GitLab CI Variables).
  • Sadržaj — ne koristite Default Content modul kao prečicu za ono što bi trebalo da bude config.
PHP web/sites/default/settings.php
// settings.php — komitovan u Git, nezavisan od okruženja
$settings['config_sync_directory'] = DRUPAL_ROOT . '/../config/sync';

// Učitaj vrednosti specifične za okruženje koje ubacuje CI ili host.
if (file_exists($app_root . '/' . $site_path . '/settings.env.php')) {
  include $app_root . '/' . $site_path . '/settings.env.php';
}

// Učitaj opcione lokalne override‑e (gitignored).
if (file_exists($app_root . '/' . $site_path . '/settings.local.php')) {
  include $app_root . '/' . $site_path . '/settings.local.php';
}
PHP web/sites/default/settings.env.php
// Generiše CI iz environment varijabli — nikada se ne komituje.
$databases['default']['default'] = [
  'driver'   => 'mysql',
  'host'     => getenv('DB_HOST'),
  'database' => getenv('DB_NAME'),
  'username' => getenv('DB_USER'),
  'password' => getenv('DB_PASS'),
  'port'     => getenv('DB_PORT') ?: 3306,
  'prefix'   => '',
];

$settings['hash_salt'] = getenv('DRUPAL_HASH_SALT');

// Kaže config_split‑u u kom se okruženju nalazimo.
$config['config_split.config_split.production']['status'] =
  (getenv('APP_ENV') === 'production');

5. Drupal konfiguracioni workflow

5.1 Lokalni razvoj

Lokalni workflow je jedino mesto gde petlja povratne sprege između izmena u UI‑ju i export‑a konfiguracije mora biti brza i rutinska. Svaki developer na projektu prati ista pravila:

  • Napravite UI ili kod izmene lokalno.
  • Pokrenite drush cex odmah — ne na kraju dana.
  • Pregledajte diff pomoću git diff config/sync.
  • Ili komitujte izmene ili ih odbacite pomoću git checkout -- config/sync.
  • Ne postoji međustanje.
Shell Workflow lokalnog razvoja
# Nakon izmena konfiguracije putem Drupal UI‑ja ili install hook‑ova:
drush cex --yes

# Pregledajte šta je promenjeno — tretirajte ovo kao pregled bilo koje izmene koda:
git diff config/sync

# Stage‑ujte i komitujte zajedno sa feature kodom:
git add config/sync
git commit -m "feat(search): add fulltext search API index config"

# Ili odbacite ako je promena bila eksperimentalna i nije spremna:
git checkout -- config/sync

Ovo sprovodimo pomoću laganog Git pre‑commit hook‑a koji upozorava (ali ne blokira) kada su PHP ili template fajlovi stage‑ovani bez odgovarajuće izmene u config/sync:

Shell .git/hooks/pre-commit
#!/bin/bash
# Upozori ako je modul/tema kod promenjen, ali config/sync nije.
CHANGED_CODE=$(git diff --cached --name-only | grep -E '\.(php|module|theme|install)$')
CHANGED_CONFIG=$(git diff --cached --name-only | grep '^config/sync')

if [[ -n "$CHANGED_CODE" ]] && [[ -z "$CHANGED_CONFIG" ]]; then
  echo "⚠  Upozorenje: PHP/module fajlovi su stage‑ovani, ali nema promena u config/sync."
  echo "   Da li ste zaboravili da pokrenete: drush cex ?"
  echo "   Nastavljamo dalje — ali proverite još jednom."
fi
exit 0

5.2 Feature grane

Konfiguracija se nalazi uz kod koji je zahteva u istoj grani i u istom pull/merge request‑u. Feature koji dodaje novi tip sadržaja treba da uključi i PHP install hook (ako postoji) i YAML fajlove za taj tip sadržaja u istom commitu.

Rano otkrivanje pokvarenih zavisnosti: Ako grana A doda novo polje, a grana B promeni prikaz tog istog polja, spajanje B pre A će proizvesti grešku pri import‑u konfiguracije. Ovo je upravo ispravan ishod — želite da pipeline ovo uhvati na grani, a ne na produkciji.

6. Odgovornosti CI‑ja: Šta pipeline mora da sprovede

Svaki pipeline koji dodiruje zajedničko Drupal okruženje mora redom da izvrši sledeće korake:

  1. Instalacija ili vraćanje baze — čista instalacija ili sanitizovani produkcioni snapshot.
  2. Pokretanje ažuriranja bazedrush updb.
  3. Import konfiguracijedrush cim --yes.
  4. Ponovni export konfiguracijedrush cex --yes.
  5. Provera da nema diff‑a — ako se bilo koji YAML fajl promenio, build pada.

Koraci 4 i 5 zajedno su najvažniji. Import, a zatim trenutni ponovni export, moraju proizvesti prazan diff. Ako ne:

  • Modul generiše konfiguraciju pri import‑u (često bag u modulu).
  • UUID config entiteta se ne poklapa sa onim u bazi.
  • Config schema je nepotpuna, zbog čega Drupal normalizuje vrednosti drugačije nego prilikom export‑a.
  • Developer je ručno menjao config fajlove i uneo nedoslednosti.
Shell ci/drupal-config-check.sh
#!/bin/bash
set -euo pipefail

echo "=== Pokretanje Drupal ažuriranja baze ==="
drush updb --yes

echo "=== Import konfiguracije iz config/sync ==="
drush cim --yes

echo "=== Ponovni export radi verifikacije da nema čekajućih izmena ==="
drush cex --yes

echo "=== Provera config drift‑a ==="
if ! git diff --exit-code config/sync; then
  echo ""
  echo "❌ FAIL: Detektovano odstupanje konfiguracije!"
  echo "   Config u Git‑u se ne poklapa sa onim što je Drupal proizveo nakon import+export."
  echo "   Diff je prikazan iznad. Ispravite lokalno pomoću 'drush cex' i komitujte."
  exit 1
fi

echo "✅ Konfiguracija je čista — nema odstupanja."
Zašto je ponovni export nakon import‑a neizostavan: Bez ponovnog export‑a znate samo da je import uspeo. Ne znate da li se importovana konfiguracija poklapa sa onim što je Drupal zapravo interno sačuvao. Samo ponovni export i diff zatvaraju taj jaz.
 

7. Jenkins implementacija (proverena u praksi)

7.1 Struktura Jenkinsfile‑a

Koristimo isključivo deklarativne pipeline‑e. Scripted pipeline‑i nude veću fleksibilnost, ali su deklarativni pipeline‑i lakši za čitanje, lakši za lintovanje pomoću jenkins-cli declarative-linter, i lakši za razumevanje novim članovima tima.


pipeline {
  agent { label 'drupal-php82' }

  options {
    buildDiscarder(logRotator(numToKeepStr: '20'))
    timeout(time: 30, unit: 'MINUTES')
    disableConcurrentBuilds()
  }

  environment {
    DRUPAL_ROOT       = "${WORKSPACE}/web"
    COMPOSER_HOME     = "${WORKSPACE}/.composer"
    APP_ENV           = 'ci'
    DB_CREDS          = credentials('drupal-ci-db') // Jenkins tajna
  }

  stages {

    stage('Checkout') {
      steps {
        checkout scm
        sh 'git log --oneline -5'
      }
    }

    stage('Composer Install') {
      steps {
        sh '''
          composer install \
            --no-interaction \
            --prefer-dist \
            --optimize-autoloader
        '''
      }
    }

    stage('Write Settings') {
      steps {
        // Generiše settings.env.php iz Jenkins kredencijala/env varijabli
        sh '''
          cat > web/sites/default/settings.env.php <<EOF
$databases['default']['default'] = [
  'driver'   => 'mysql',
  'host'     => '127.0.0.1',
  'database' => 'drupal_ci',
  'username' => '${DB_CREDS_USR}',
  'password' => '${DB_CREDS_PSW}',
  'port'     => 3306,
  'prefix'   => '',
];
\\$settings['hash_salt'] = '${DRUPAL_HASH_SALT}';
EOF
        '''
      }
    }

    stage('Site Install') {
      steps {
        sh '''
          drush site-install minimal \
            --yes \
            --existing-config \
            --account-name=admin \
            --account-pass="${DRUPAL_ADMIN_PASS}"
        '''
      }
    }

    stage('Validate Config') {
      steps {
        sh 'bash ci/drupal-config-check.sh'
      }
      post {
        failure {
          sh 'git diff config/sync || true'
          archiveArtifacts artifacts: 'config/sync/**/*.yml', allowEmptyArchive: true
        }
      }
    }

    stage('Deploy') {
      when {
        anyOf {
          branch 'main'
          branch 'release/*'
        }
      }
      steps {
        sh 'bash ci/drupal-deploy.sh'
      }
    }

  }

  post {
    always {
      sh 'drush cr || true'
      cleanWs()
    }
    failure {
      emailext(
        subject: "[FAIL] ${JOB_NAME} #${BUILD_NUMBER}",
        body: "Validacija konfiguracije nije uspela. Pogledajte: ${BUILD_URL}",
        recipientProviders: [[$class: 'DevelopersRecipientProvider']]
      )
    }
  }
}

7.2 Specifičnosti Jenkins‑a

Shared Libraries

Kada upravljate sa više od dva ili tri Drupal sajta, izdvojite logiku Drupal pipeline‑a u Jenkins Shared Library. Jenkinsfile svakog projekta tada postaje tanak omotač:

Groovy Jenkinsfile (tanak omotač koji koristi shared library)
@Library('drupal-pipeline-lib@v2') _

drupalPipeline(
  phpVersion:    '8.2',
  deployBranch:  'main',
  dbCredentials: 'drupal-ci-db',
  slackChannel:  '#deployments'
)

Čišćenje radnog prostora

Uvek pozovite cleanWs() u post { always } bloku. Jenkins agenti akumuliraju stanje iz prethodnih build‑ova — settings.env.php ili vendor direktorijum iz prethodnog build‑a mogu tiho uticati na trenutni build. Budite nemilosrdni: očistite radni prostor.

Nikada nemojte ponovo koristiti bazu između pokretanja pipeline‑a osim ako eksplicitno testirate upgrade putanje. Baza zaostala iz prethodnog build‑a može maskirati UUID nepodudaranja, nedostajuće migracije i probleme sa redosledom instalacije modula.

8. GitLab CI implementacija

8.1 Struktura .gitlab-ci.yml

YAML .gitlab-ci.yml
image: php:8.2-cli

stages:
  - build
  - test
  - validate-config
  - deploy

variables:
  COMPOSER_CACHE_DIR: "$CI_PROJECT_DIR/.cache/composer"
  APP_ENV: "ci"
  MYSQL_DATABASE: "drupal_ci"
  MYSQL_ROOT_PASSWORD: "root"

.cache-composer: &cache-composer
  cache:
    key:
      files: [composer.lock]
    paths:
      - .cache/composer
      - vendor/
    policy: pull

composer:install:
  stage: build
  cache:
    key:
      files: [composer.lock]
    paths:
      - .cache/composer
      - vendor/
    policy: pull-push
  script:
    - composer install --no-interaction --prefer-dist --optimize-autoloader
  artifacts:
    paths:
      - vendor/
      - web/core/
      - web/modules/contrib/
    expire_in: 1 hour

phpunit:unit:
  stage: test
  <<: *cache-composer
  script:
    - ./vendor/bin/phpunit --testsuite=unit --log-junit=reports/phpunit.xml
  artifacts:
    reports:
      junit: reports/phpunit.xml

drupal:validate-config:
  stage: validate-config
  <<: *cache-composer
  services:
    - name: mysql:8.0
      alias: mysql
  variables:
    DB_HOST: mysql
    DB_NAME: $MYSQL_DATABASE
    DB_USER: root
    DB_PASS: $MYSQL_ROOT_PASSWORD
  before_script:
    - bash ci/write-settings-env.sh
    - drush site-install minimal --yes --existing-config
  script:
    - bash ci/drupal-config-check.sh
  artifacts:
    when: on_failure
    paths:
      - config/sync/
    expire_in: 3 days

deploy:staging:
  stage: deploy
  environment:
    name: staging
    url: https://staging.example.com
  rules:
    - if: '$CI_COMMIT_BRANCH == "develop"'
  script:
    - bash ci/drupal-deploy.sh staging

deploy:production:
  stage: deploy
  environment:
    name: production
    url: https://example.com
  rules:
    - if: '$CI_COMMIT_BRANCH == "main"'
      when: manual
  script:
    - bash ci/drupal-deploy.sh production

8.2 Prednosti GitLab CI‑ja

GitLab CI ima nekoliko karakteristika koje ga čine posebno pogodnim za ovaj workflow:

  • First‑class artifacts — neuspešni config export‑i se automatski čuvaju i dostupni su za pregled u MR UI‑ju bez dodatne konfiguracije.
  • Nativni cache vezan za composer.lock — Composer instalacije su jeftine nakon prvog pokretanja.
  • Vidljivost Merge Request pipeline‑a — developeri vide status validacije konfiguracije direktno na MR‑u pre merge‑a.
  • Services blok — MySQL se pokreće kao sidecar bez dodatnog infrastrukturnog opterećenja.
  • Okruženja + manuelne kapije — pravilo when: manual na production deploy‑u omogućava promociju jednim klikom uz ljudsku proveru, bez potrebe za posebnim approval workflow‑om.
Shell ci/drupal-deploy.sh
#!/bin/bash
set -euo pipefail

TARGET_ENV="${1:-staging}"
echo "=== Deploy na: ${TARGET_ENV} ==="

rsync -az --delete \
  --exclude='.git' \
  --exclude='web/sites/default/files' \
  --exclude='web/sites/default/settings.env.php' \
  ./ "deploy@${TARGET_ENV}.example.com:/var/www/drupal/"

echo "=== Pokretanje post-deploy komandi na ${TARGET_ENV} ==="
ssh "deploy@${TARGET_ENV}.example.com" bash -s <<'REMOTE'
  set -e
  cd /var/www/drupal
  drush updb --yes
  drush cim --yes
  drush cex --yes
  git diff --exit-code config/sync || (echo "❌ Config drift na targetu!"; exit 1)
  drush cr
  echo "✅ Deploy završen."
REMOTE

9. Konfiguracija specifična za okruženje bez varanja

Nije svaka konfiguraciona vrednost ista u svim okruženjima. Search backend‑i, nivo logovanja, caching slojevi i endpoint‑i za spoljne API‑je se legitimno razlikuju. Pitanje je kako upravljati tim razlikama bez kompromitovanja integriteta vašeg config pipeline‑a.

Pravi alati: config_split i config_ignore

config_split omogućava definisanje skupova konfiguracije koji su aktivni samo u određenim okruženjima. Svaki split se nalazi u sopstvenom direktorijumu i aktivira se putem settings.php ili settings.env.php na osnovu environment varijable.

YAML config/sync/config_split.config_split.development.yml
langcode: en
status: true
id: development
label: Development
description: 'Config aktivan samo u lokalnim/dev okruženjima'
folder: '../config/splits/development'
module:
  devel: 0
  kint: 0
  dblog: 0
theme: {  }
blacklist: {  }
graylist: {  }
PHP web/sites/default/settings.env.php (aktivacija okruženja)
$app_env = getenv('APP_ENV') ?: 'production';

// Aktiviraj ispravan config_split na osnovu okruženja.
$config['config_split.config_split.development']['status']  = ($app_env === 'development');
$config['config_split.config_split.staging']['status']     = ($app_env === 'staging');
$config['config_split.config_split.production']['status']   = ($app_env === 'production');

Primeri split‑ova iz prakse

Config stavkaDev splitStage splitProd split
Search API backendDatabase backendSolr (mali)Solr (prod klaster)
Logovanje grešakadblog, verbosesyslogsyslog + eksterni APM
Performance moduliOnemogućeniOmogućeniOmogućeni + CDN konfiguracija
Mail transportMailhog / null mailerMailpitSMTP / SendGrid

Neispravni pristupi — nemojte raditi sledeće:

  • Direktno menjanje konfiguracije u produkcionoj bazi.
  • Korišćenje if ($settings['environment'] === 'prod') uslova unutar settings.php za zamenu config vrednosti — ovo potpuno zaobilazi config sistem i stvara nevidljivo runtime odstupanje.
  • Održavanje posebnih Git grana po okruženju sa različitim config fajlovima.

10. Promocija između okruženja

Promocija nije deployment. Deployment premešta kod i konfiguraciju u okruženje. Promocija premešta validirani artefakt — nešto što je već prošlo CI — u sledeće okruženje u lancu.

U praksi, to znači da isti Git SHA koji je validiran na dev‑u stiže i do produkcije. Nema commit‑a u poslednjem trenutku. Nema hotfix‑eva koji preskaču CI. Nema cherry‑pick‑ova koji zaobilaze config proveru.

✅ CI validira SHA
na feature grani / main
🌐 Dev
auto‑deploy nakon merge‑a
🌐 Stage
auto‑deploy iz develop
🌐 Prod
manuelna kapija na main

Isti validirani artefakt prolazi kroz okruženja. CI se izvršava jednom; rezultat se propagira.

Filozofija nepromenljivih build‑ova znači: ono što je CI validirao je ono što se deploy‑uje. Ako je hotfix zaista neophodan, prolazi kroz fast‑track granu sa sopstvenim CI pokretanjem — ne zaobilazi validaciju.

Sprečavanje hotfix‑eva u poslednjem trenutku da preskoče config provere: Zaštitite svoju main granu obaveznom proverom statusa pipeline‑a. GitLab "protected branches" i Jenkins "GitHub Branch Source" plugin to podržavaju. Ako pipeline nije prošao, grana ne može biti deploy‑ovana na produkciju.

11. Rukovanje specifičnim slučajevima (naići ćete na ovo)

UUID nepodudaranja

Config entiteti imaju UUID‑eve. Ako instalirate svež sajt i zatim pokušate import konfiguracije iz druge instalacije, Drupal će odbiti zbog UUID nepodudaranja. Rešenje je da uvek instalirate sa --existing-config ili da postavite UUID sajta nakon instalacije:

Shell
# Pročitaj UUID iz komitovanog config‑a i primeni ga na novu instalaciju:
SITE_UUID=$(grep "^uuid:" config/sync/system.site.yml | awk '{print $2}')
drush config-set "system.site" uuid "$SITE_UUID" --yes
drush cim --yes

Redosled omogućavanja/onemogućavanja modula

Kada omogućavate novi modul putem config‑a, Drupal automatski poštuje redosled zavisnosti tokom drush cim. Međutim, ako install hook modula generiše podrazumevanu konfiguraciju koja je u konfliktu sa onim u config/sync, možda ćete morati da obrišete tu podrazumevanu konfiguraciju i ponovo je importujete. Pipeline će ovo detektovati kao drift.

Konfiguracija zavisna od sadržaja

Neka konfiguracija referencira sadržaj — na primer blok koji referencira stavku menija po ID‑ju, ili view koji filtrira po taksonomskom terminu. Ove reference se mogu razlikovati između okruženja ako je sadržaj kreiran drugačije. Rešenje je upravljanje tim sadržajem putem migracija ili default content modula, a ne kroz config.

Multisite specifičnosti

U multisite postavci, svaki sajt ima sopstveni config sync direktorijum. CI pipeline mora pokrenuti config proveru za svaki sajt, ne samo za primarni. Koristite petlju:

Shellci/multisite-config-check.sh
#!/bin/bash
set -euo pipefail

for SITE_DIR in web/sites/*/; do
  SITE=$(basename "$SITE_DIR")
  [[ "$SITE" == "default" ]] && continue
  [[ "$SITE" == "simpletest" ]] && continue

  echo "--- Provera config‑a za sajt: $SITE ---"
  drush --uri="${SITE}" updb --yes
  drush --uri="${SITE}" cim --yes
  drush --uri="${SITE}" cex --yes

  CONFIG_DIR="config/${SITE}/sync"
  if ! git diff --exit-code "${CONFIG_DIR}"; then
    echo "❌ Config drift na sajtu: $SITE"
    exit 1
  fi
done
echo "✅ Svi sajtovi su čisti."

Nadogradnja Drupal Core‑a uz izmene konfiguracije

Core ažuriranja ponekad dolaze sa izmenama u schema‑i koje utiču na postojeći config. Uvek pokrenite drush updb pre drush cim, i pokrenite drush cex nakon toga kako biste zabeležili sve schema‑normalizovane izmene. Komitujte te izmene kao deo grane za core nadogradnju — nemojte da vas iznenade u pipeline‑u.

12. Operativne sigurnosne mreže

Config Read‑Only u produkciji

Razmotrite omogućavanje Config Readonly modula u produkciji. On sprečava svaku izmenu konfiguracije putem admin UI‑ja — čvrsta blokada koja na nivou aplikacije pojačava pravilo "nema ručnih izmena".

PHPweb/sites/default/settings.env.php
if (getenv('APP_ENV') === 'production') {
  $settings['config_readonly'] = TRUE;
}

Post‑deploy verifikacija

ShellPost-deploy provere
# Proveri da nema zaostalih config izmena nakon deploy‑a:
drush config-status 2>&1 | grep -v 'No differences' && { \
  echo "❌ Neočekivane config razlike nakon deploy‑a"; exit 1; \
} || echo "✅ Config status je čist"

# Proveri da su cache‑evi zagrejani:
drush cr
drush php-eval "echo \Drupal::state()->get('system.cron_last');"

Rollback strategija

Rollback je Git operacija, ne operacija nad bazom. Ako deployment izazove problem:

  1. Identifikujte poslednji ispravan commit SHA.
  2. Pokrenite pipeline za taj SHA.
  3. Pipeline ponovo deploy‑uje prethodno validirani artefakt.

Ručna operacija nad bazom da bi se poništio config import nikada nije rešenje — zaobilazi sve sigurnosne provere i obično pravi više odstupanja nego što ih rešava.

Audit: Ko je menjao config, kada i zašto

Pošto svaka izmena konfiguracije ide kroz Git, vaš audit trag je Git istorija. Koristite smislene commit poruke i sprovedite ih pomoću commit‑msg hook‑a ili CI lint koraka:

ShellPrimer commit istorije
$ git log --oneline config/sync/views.view.articles.yml

a3f8c12 feat(views): add taxonomy filter to articles view (PROJ-421)
9e1b307 fix(views): remove exposed sort causing query timeout (PROJ-389)
4d22a91 chore(config): export after Search API Solr 4.3.0 upgrade

13. Uobičajene greške koje više ne pravimo

GreškaZašto boliRešenje
Ručno pokretanje drush cim na staging‑u ili produkcijiImportuje nepoznato stanje — moguće uključujući nekomitovane lokalne izmeneSamo pipeline importuje config u zajedničkim okruženjima
Dozvoljavanje UI izmena na staging‑uStaging postaje izvor istine, stvarajući odstupanje od Git‑aOmogućite config_readonly u svim zajedničkim okruženjima
Oslanjanje na developere da se "sete da export‑uju"Neće, pod pritiskomPre‑commit hook‑ovi upozoravaju; CI failure nameće
Ignorisanje config drift‑aTiho odstupanje se akumulira dok se nešto spektakularno ne pokvariTrojka drush cim → cex → git diff --exit-code je obavezna
Posebne grane po okruženju sa različitim config‑omMerge postaje noćna mora konflikata; nema jedinstvenog izvora istineJedna config grana, podeljena pomoću config_split
Preskakanje drush updb pre drush cimSchema ažuriranja mogu uzrokovati import greške ili tihi gubitak podatakaUvek: updb → cim → cex → diff

14. Rezultati nakon primene ovog pristupa

Nakon implementacije ovog pristupa kroz portfolio Drupal sajtova — od jednog malog sajta do agencije koja upravlja desetinama instalacija — rezultati su bili dosledni:

~80%
Smanjenje deployment incidenata vezanih za config
< 1 dan
Onboarding novog developera na config workflow
0
Ručnih drush cim pokretanja na produkciji tokom 18 meseci
100%
Config izmene povezane sa Git commit‑om i MR‑om

Osim brojki, kulturna promena je najvažnija. Diskusije o konfiguraciji prelaze sa Slack‑a u Git istoriju. Umesto "da li je neko menjao stilove slika na staging‑u?", pitanje postaje "da li za to postoji commit?" — i uvek postoji, ili se izmena nije desila.

Novi developeri više ne moraju da uče nepisana pravila o tome "koje okruženje je trenutno kanonsko." Odgovor je uvek Git. Pipeline to sprovodi.

15. Završne preporuke

  • Počnite striktno, opuštajte samo sa razlogom. Lakše je ublažiti pravilo koje se pokaže kao nepotrebno nego pooštriti ono koje nikada nije postojalo.
  • Tretirajte svaki config diff kao pad build‑a. Bez izuzetaka, bez "popravićemo nakon release‑a."
  • CI je izvor istine za ono što se deploy‑uje — ne laptop developera, ne staging okruženje, ne Slack odluka.
  • Sekvenca updb → cim → cex → diff je nepovrediva. Ako preskočite neki korak, letite naslepo.
  • Koristite config_split za stvarne razlike među okruženjima. Ne koristite uslove u settings.php kao prečicu.
  • Omogućite config_readonly u svim zajedničkim okruženjima. Učinite slučajne UI izmene nemogućim, a ne samo nepoželjnim.
  • Arhivirajte neuspele config export‑e kao CI artefakte. Developeri moraju videti tačno šta je odstupilo, ne samo da diff postoji.
  • Rollback radite putem Git‑a, nikada putem operacija nad bazom. Pipeline je najsigurniji put u oba smera.
Ovaj pristup se skalira. Bilo da ste solo developer na jednom sajtu ili agencija koja upravlja sa pedeset Drupal instalacija, pravila su ista. Pipeline ne mari za veličinu tima ili pritisak release‑a. Ili prolazi ili pada. Ta doslednost je poenta.

I Jenkins i GitLab CI su jednako sposobni da sprovedu ovu disciplinu. GitLab CI zahteva manje infrastrukture i ima bolju nativnu podršku za artefakte; Jenkins nudi veću fleksibilnost za kompleksna enterprise okruženja i odličnu podršku za shared library‑je za standardizaciju pipeline‑a kroz mnoge projekte. Izaberite alat koji odgovara vašoj organizaciji — principi iz ovog članka važe bez obzira na izbor.

Cilj nikada nije bio da upravljanje konfiguracijom učinimo komplikovanim. Cilj je da ga učinimo dosadnim: rutinskim korakom koji uvek radi, o kome niko ne mora da razmišlja, koji nikada ne izaziva incident u 23h. Sa pravilno implementiranim CI pipeline‑om, upravo to dobijate.

Tehnička i arhitektonska pitanja
Ivan Abramenko, Principal Drupal Architect
ivan.abramenko@drupalbook.org
Upiti u vezi projekata
projects@drupalbook.org