Projekt napisany w Django i Bitbucket pipelines – moje perypetie z Continous Integration

Projekt napisany w Django i Bitbucket pipelines – moje perypetie z Continous Integration

W pracy nadszedł czas na Continuous integration (CI) w projektach nieco większych niż zwykła strona www. Zwykle w swojej pracy jako repozytorium kodu używam Bitbucketa więc postanowiłem daleko nie szukać narzędzi do Continuous integration (CI). Zamiast poznawać TravisCI czy CircleCI, więc mój wybór padł na bitbucketowe pipelines. Niestety zaraz na starcie, przy samych migracjach pojawił się problem wraz z błędem

django.db.utils.OperationalError: (1366, "Incorrect string value: '\\xC5\\xBCytko...' for column 'name' at row 1")

Jasnym było, że polski projekt to i polskie znaki diakrytyczne były problemem (pole ‘użytkownik’). Baza tworzy się domyślnie w latin1_swedish_ci. Długo walczyłem szukając informacji jak utworzyć bazę danych z odpowiednim zestawem znaków utf8. Oczywiście zacząłem od szukania informacji jak zrobić to domyślnie, coś ponad CHARSET: UTF8 w opcjach bazy danych, bo to i tak nic nie dawało – baza i tak była tworzona w szwedzkim kodowaniu:

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'test_pipelines',
        'USER': 'test_user',
        'PASSWORD': 'test_user_password',
        'HOST': '127.0.0.1',
        'PORT': '3306',
        'OPTIONS': {
               "init_command": "SET default_storage_engine=MyISAM",
        },
        'TEST': {
            'NAME': 'test_pipelines',
            'CHARSET': 'UTF8',
        },
    }
}

Nie chciało mi się wierzyć, że nie ma czegoś ponad porady z linku https://confluence.atlassian.com/bitbucket/test-with-databases-in-bitbucket-pipelines-856697462.html:

definitions: 
  services: 
    mysql: 
      image: mysql 
      environment: 
        MYSQL_DATABASE: 'pipelines'
        MYSQL_RANDOM_ROOT_PASSWORD: 'yes' 
        MYSQL_USER: 'test_user'
        MYSQL_PASSWORD: 'test_user_password'

Znalazłem w końcu dwa rozwiązania problemu. Uruchomienie mysqld z parametrami jak poniżej, zgodnie z instrukcjami pod adresem https://kierenpitts.com/blog/2017/05/testing-django-applications-with-bitbucket-pipelines-and-mysql/

definitions:
  services:
    mysql:
      image: mysql
      command: mysqld --character-set-server=utf8 --collation-server=utf8_polish_ci --default-storage-engine=MyISAM
      environment:
        MYSQL_DATABASE: 'test_pipelines' 
        MYSQL_RANDOM_ROOT_PASSWORD: 'yes' 
        MYSQL_USER: 'test_user' 
        MYSQL_PASSWORD: 'test_user_password'

Niestety to rozwiązanie nie zadziałało. W ‘buildzie’ nie widać, żeby taka baza z takim kodowaniem się tworzyła.

Pozostało mi rozwiązanie drugie. Utworzyć bazę i skryptem ustawić odpowiednie kodowanie, zgodnie z tym co zostało opisane tu (https://josefottosson.se/change-collation-to-utf-8-on-all-tables-with-django-mysql/). Skrypty muszą się uruchomić zanim zostanie uruchomiana migracja. Tak więc powstał skrypt zmieniający bazę na utf8_polish_ci:


import sys
from project import settings
from django.db import connection
import os

sys.path.append(settings.BASE_DIR)

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "project.settings_pipelines")

def init():
    cursor = connection.cursor()
    tables = connection.introspection.table_names()
    sql = "ALTER DATABASE pipelines CHARACTER SET utf8 COLLATE utf8_polish_ci;"
    cursor.execute(sql)
    for table in tables:
        print "Fixing table: %s" %table
        sql = "ALTER TABLE %s CONVERT TO CHARACTER SET utf8;" %(table)
        cursor.execute(sql)
        print "Table %s set to utf8"%table

    print "DONE!"

init() 

Ostatecznie mój plik bitbucket-pipelines.yml wygląda mniej więcej tak:


image: python:2.7

pipelines:
  default:
    - step:
        caches:
          - pip
        services:
          - mysql
        script: # Modify the commands below to build your repository.
          - pip install --upgrade pip
          - pip install six
          - pip install -r requirements.txt
          - export DJANGO_SETTINGS_MODULE=project.settings_pipelines
          - python pipeline_database_conversion.py
          - python manage.py migrate --settings=project.settings_pipelines
          - python manage.py migrate --database=historical --settings=project.settings_pipelines
  
definitions:
  services:
    mysql:
      image: mysql
      environment:
        MYSQL_DATABASE: 'pipelines'
        MYSQL_RANDOM_ROOT_PASSWORD: 'yes'
        MYSQL_USER: 'test_user'
        MYSQL_PASSWORD: 'test_user_password'
        MYSQL_DEFAULT_CHARACTER_SET: 'utf8'

Proszę się nie dziwić dwóm migracjom, ponieważ ten projekt korzysta z 2 dwóch różnych baz danych (na devie czy produkcji), jednak na potrzeby testów utworzyłem tylko jedną bazę która zawiera wszystkie tabele. Ostatecznie testy z użyciem pytest działają aż miło.

Co do skryptu zmieniający kodowanie w bazie, prawdopodobnie większości osobom wystarczy część bez pętli iterującej po tabelach:

def init():
    cursor = connection.cursor()
    tables = connection.introspection.table_names()
    sql = "ALTER DATABASE pipelines CHARACTER SET utf8 COLLATE utf8_polish_ci;"
    cursor.execute(sql)

Dojście do momentu, kiedy wszystko działa zajęło mi bardzo dużo czasu, dlatego postanowiłem to opisać. Mam nadzieję, że pomoże to innym django/python developerom w łatwiejszej integracji bitbucketa i piplines, zwłaszcza tym polskiego pochodzenia.