diff --git a/.dockerignore b/.dockerignore index 0d4799d..9c5f9c9 100644 --- a/.dockerignore +++ b/.dockerignore @@ -7,23 +7,25 @@ *.sql.gz *.sqlite3 .cache -.env +.env* .project .idea .pydevproject -.idea/workspace.xml .DS_Store +build/ .git/ -.sass-cache -.vagrant/ +.gitlab/ +.gitignore +venv/ +fixtures/ +db/ +data.json __pycache__ dist -docs -env logs -src/{{ project_name }}/settings/local.py -src/node_modules -web/media -web/static/CACHE -stats Dockerfile +docker-compose* +.dockerignore +layouts +staticroot +.editorconfig diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index ef3bf26..70eee9c 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,7 +1,19 @@ -image: python:3-alpine +image: python:3.9-alpine + + +variables: + PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache" + +cache: + paths: + - .cache/pip + stages: - test + - style + - build + - deploy django_test: stage: test @@ -21,3 +33,35 @@ coverage: artifacts: paths: - public/coverage + +pylint: + stage: style + allow_failure: true + before_script: + - pip install -r requirements/dev.txt + script: + - pylint --reports=yes main + +docker: + stage: build + only: + - master + tags: + - docker + before_script: + - docker info + - docker login -u $REGISTRY_USERNAME -p $REGISTRY_PASSWORD gitlab.cazzzer.com:5050 + script: + - docker build . -t gitlab.cazzzer.com:5050/cazzzer/zendesk-access-controller:alpine + - docker push gitlab.cazzzer.com:5050/cazzzer/zendesk-access-controller:alpine + after_script: + - docker logout + +deploy: + stage: deploy + only: + - master + tags: + - cazzzer-internal + script: + - sudo -u cazzzer /home/cazzzer/deploy_access_controller.sh diff --git a/Dockerfile b/Dockerfile index 59c861e..a08d370 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,10 +1,26 @@ -FROM python:3.6 -COPY ./ /access_controller +FROM python:3.9 as builder +# enchant dependency for sphinx +RUN apt-get update && apt-get install -y python-enchant +# copy source files WORKDIR /access_controller/ -RUN pip install -r requirements/prod.txt -RUN python manage.py makemigrations +COPY ./ /access_controller +# dev requirements for building sphinx docs +RUN pip install -r requirements/dev.txt -r requirements/prod.txt +# build static and documentation files into build/webserver +RUN ./manage.py collectstatic --no-input && ./documentation.sh +# create production venv to copy to final image +# production venv is built here because `cryptography` requires rust which doesn't ship on alpine +RUN python -m venv venv && venv/bin/pip install -r requirements/prod.txt +# move files necessary to run the app to the build folder (including production venv) +RUN mv venv access_controller main manage.py build + +FROM python:3.9-alpine +WORKDIR /access_controller/ +COPY --from=builder ["/access_controller/build", "./"] +RUN apk update && apk add postgresql-libs && adduser -D -H -u 5579 actrl && chmod -R -w ./ +USER actrl EXPOSE 8000 -COPY start.sh /var/ -CMD bash /var/start.sh - - +CMD . venv/bin/activate && \ +python manage.py migrate && \ +cp -rT webserver srv && \ +daphne -b 0.0.0.0 access_controller.asgi:application diff --git a/README.rst b/README.rst index 8d95018..e69de29 100644 --- a/README.rst +++ b/README.rst @@ -1,230 +0,0 @@ - -Управление правами доступа --------------------------- - -Идея - написать программу(Web приложение), которая будет выдавать права пользователям системы по запросу самого -пользователя. Например, из 12 человек 3 сейчас работают с правами админа, по окончании рабочей смены они сдают -свои права (освобождают места) и другие пользователи могут запросить эти права в свое пользование. - -Оставшиеся 9 человек получают права легкого агента - без прав редактирования, а только чтение. - -Из технологий - программа должна взаимодействовать с api системы Zendesk(система обращений клиентов - жалобы), -проверять авторизованного пользователя на права(будет возможность менять права напрямую из Zendesk - нужна -синхронизация прав с приоритетом у Zendesk). - -Если руками в самом Zendesk права у пользователя отобрали или наоборот -присвоили, то наша программа обновляет статус пользователя в соответствии с данными синхронизации -(например, раз в минуту). - -Так же в идеале должна быть проверка, что пользователь сайта существует на сайте Zendesk(по токену). - -Сэндбокс Zendesk нам предоставит моя компания, библиотеку для работы с api уже подсказали. -Сама программа (наша) будет обладать админскими правами и реализовывать контроль и выдачу прав другим пользователям. - -*Итого:* - - -#. Реализовать авторизацию пользователей с проверкой по API на существование такого пользователя -#. Реализовать интерфейс со статистикой рабочих мест(занято, свободно, кто занимает) -#. Реализовать логирование действий(когда взял права, когда отдал - запись в файл и БД) -#. Реализовать передачу прав приложением по запросу от пользователя и замену прав пользователя - у которого права отбираются внутри Zendesk (на легкий агент) -#. Реализовать синхронизацию по API на проверку прав(не менялись ли в системе Zendesk) -#. Реализовать возможность добавить большее количество админских прав -#. Реализовать возможность добавления легких агентов(права только на просмотр) -#. Реализовать на общей странице текущую информацию о пользователе - текущие права, карточка пользователя - -Технологический стек: ---------------------- - - -* Python 3 -* Django 3 - -Quickstart ----------- - -Перед запуском необходимо создать ``.env`` файл. - -.. code-block:: bash - - cp .env.example .env - -Установить модули для работы js -.. code-block:: bash - - sudo apt install npm - cd main/control_page_js_modules/ - npm install - sudo npm -g install npx - npx webpack - -Заменить переменные в ``.env`` на актуальные. - -.. code-block:: bash - - sudo apt install make - pip install --upgrade pip - pip install -r requirements/dev.txt - ./manage.py migrate - ./manage.py loaddata data.json - ./manage.py runserver - -Перед запуском для тестирования: --------------------------------- - -Убедитесь, что вы зарегистрированы в песочнице ZenDesk, у вас назначена организация ``SYSTEM`` -Для админов ZenDesk дополнительно - создайте токен доступа в ZenDesk -При запуске в Docker убедитесь что папка, которая будет служить хранилищем для БД, открыта на запись и чтение - -Для запуска тестов страницы управления: -1. Установить npm и npx -.. code-block:: bash - sudo apt install npm - -2. Перейти в static папку со страницей управления: -.. code-block:: bash - cd main/control_page_js_modules/ - -3. Выполнить установку модулей для js -.. code-block:: bash - npm install - sudo npm -g install npx - npx webpack - -4. Тестирование в той же папке -.. code-block:: bash - npm test - - -Запуск на локальной машине: ---------------------------- - - -* Скопировать репозиторий на локальную машину -* Перейти в папку приложения -* Активировать виртуальное окружение -* Выполнить команду ``pip install -r requirements/dev.txt`` -* В файл ``.env`` добавить следующие переменные: - -.. code-block:: - - ACTRL_DEBUG={0/1} - включить режим дебага - ACTRL_HOST={HOSTNAME} - при запуске без дебага, надо указать домен на котором будет работать приложение - ACTRL_SECRET_KEY={DJANGO_SECRET_KEY} - секретный ключ сгенерированный Django - - ACTRL_EMAIL_HOST={SMTP_HOST} - домен почтового сервера через который приложение будет отправлять письма, например "smtp.gmail.com" - ACTRL_EMAIL_PORT={SMTP_PORT} - порт для почтового сервера, например 587, 465 , 2525 - ACTRL_EMAIL_TLS={USE_TLS} - использовать TLS для подключения к почтовому серверу, 0 или 1 - ACTRL_EMAIL_HOST_USER={USERNAME} - логин с которым приложение входит на почтовый сервер - ACTRL_EMAIL_HOST_PASSWORD={PASSWORD} - пароль/ключ с которым приложение входит на почтовый сервер - ACTRL_FROM_EMAIL={EMAIL} - адрес с которого приложение отправляет письма - ACTRL_SERVER_EMAIL={EMAIL} - адрес на который отвечают пользователя - - ACTRL_API_EMAIL={EMAIL} - почта админа в ZenDesk - ACTRL_API_PASSWORD={PASSWORD} - пароль админа ZenDesk - ACTRL_API_TOKEN={API_TOKEN} - API токен зендеск - ACTRL_ZENDESK_SUBDOMAIN={DOMAIN} - домен ZenDesk - - ENG_CROLE_ID={ENGINEER_CUSTOM_ROLE_ID} - id роли инженера( custom_role_id сотрдника смены) - LA_CROLE_ID={LIGHT_AGENT_CUSTOM_ROLE_ID} - id роли легкого агента (custom_role_id роли -легкий агент) - EMPL_GROUP={EMPLOYEE_GROUP_NAME} - имя группы которой принадлежат сотрудники ССКС - BUF_GROUP={BUFFER_GROUP_NAME} - имя буферной группы для передачи смен(через нее происходит управление тикетами) - ST_EMAIL={SOLVED_TICKETS_EMAIL} - почта на которую будут переназначятся закрытые тикеты - LICENSE_NO={LICENSE_NO} - количество лицензий, отображаемых как доступные в приложении - SHIFTH={SHIFT_HOURS} - количество часов в рабочей смене (нужно для статистики, пока не реализовано но требует указания значения) - - -* Выполнить команду ``python manage.py migrate`` -* Запустить приложение командой ``python manage.py runserver`` (можно указать в параметрах для файла manage.py) -* Перейти по ссылке в консоли (вероятнее всего откроется по адресу http://127.0.0.1:8000/) - -Запуск в Docker: ----------------- - -Требуется установленный и настроенный Docker - - -* Скопировать репозиторий на локальную машину -* В командной строке перейти в папку проекта -* Выполнить команду ``docker build --tag access_controller:latest .`` -* Выполнить команду - .. code-block:: bash - - docker run -d -p 8000:8000 \ - ACTRL_DEBUG={0/1} \ - ACTRL_HOST={HOSTNAME} \ - ACTRL_SECRET_KEY={DJANGO_SECRET_KEY} \ - ACTRL_EMAIL_HOST={SMTP_HOST} \ - ACTRL_EMAIL_PORT={SMTP_PORT} \ - ACTRL_EMAIL_TLS={USE_TLS} \ - ACTRL_EMAIL_HOST_USER={USERNAME} \ - ACTRL_EMAIL_HOST_PASSWORD={PASSWORD} \ - ACTRL_FROM_EMAIL={EMAIL} \ - ACTRL_SERVER_EMAIL={EMAIL} \ - ACTRL_API_EMAIL={EMAIL} \ - ACTRL_API_PASSWORD={PASSWORD} \ - ACTRL_API_TOKEN={API_TOKEN} \ - ACTRL_ZENDESK_SUBDOMAIN={DOMAIN} \ - ENG_CROLE_ID={ENGINEER_CUSTOM_ROLE_ID} \ - LA_CROLE_ID={LIGHT_AGENT_CUSTOM_ROLE_ID} \ - EMPL_GROUP={EMPLOYEE_GROUP_NAME} \ - BUF_GROUP={BUFFER_GROUP_NAME} \ - ST_EMAIL={SOLVED_TICKETS_EMAIL} \ - LICENSE_NO={LICENSE_NO} \ - SHIFTH={SHIFT_HOURS} \ - -v {ABSOLUTE_PATH_TO_DB}:/zendesk-access-controller/db \ - access_controller:latest - -* открываем запущенный контейнер в браузере (можно перейти по ссылке http://localhost:8000/) - -Запуск с тестовыми юзерами: ---------------------------- - -На локальной машине - перед запуском команды ``python manage.py runserver`` выполнить команду ``python manage.py loaddata data.json`` -Это создаст тестового админа и тестового пользователя в приложении для песочницы ZenDesk. - - -* Админ - ``admin@gmail.com`` / ``zendeskadmin`` -* Пользователь - ``123@test.ru`` / ``zendeskuser`` - -Не сработает если домен песочницы отличается от ``ngenix1612197338`` (на другом домене нужно будет создать сначала пользователей в песочнице с правами админа и легкого агента -с этими же email, назначить им организацию ``SYSTEM``\ ) - -Параметры тестовой песочницы: ------------------------------ - -Пример полной конфигурации можно найти в `.env.example <.env.example>`_. Почту и токен админа ZenDesk взять у руководителя (если вы не админ). - -Для проверки pylint используем: -------------------------------- - -pylint ../access_controller (каталог, где лежит проект) - -Для приведения файлов к стандарту PEP8 используем: --------------------------------------------------- - -autopep8 --in-place filename - -Для проверки орфографии: ------------------------- - -cd docs - -make spelling - -Для обновления документации: ----------------------------- - -m2r README.md - -cd docs - -make html - -Read more ---------- - - -* Zenpy: `http://docs.facetoe.com.au `_ -* Zendesk API: `https://developer.zendesk.com/rest_api/docs/ `_ diff --git a/access_controller/settings.py b/access_controller/settings.py index 96567e4..84a6e1e 100644 --- a/access_controller/settings.py +++ b/access_controller/settings.py @@ -15,8 +15,6 @@ from pathlib import Path from dotenv import load_dotenv # Build paths inside the project like this: BASE_DIR / 'subdir'. - - BASE_DIR = Path(__file__).resolve().parent.parent # Quick-start development settings - unsuitable for production @@ -29,7 +27,7 @@ load_dotenv() SECRET_KEY = os.getenv('ACTRL_SECRET_KEY', 'empty') # SECURITY WARNING: don't run with debug turned on in production! -DEBUG = bool(int(os.getenv('ACTRL_DEBUG', '1'))) +DEBUG = bool(int(os.getenv('ACTRL_DEBUG', '0'))) ALLOWED_HOSTS = [ '127.0.0.1', @@ -94,13 +92,24 @@ WSGI_APPLICATION = 'access_controller.wsgi.application' # Database # https://docs.djangoproject.com/en/3.1/ref/settings/#databases - -DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.sqlite3', - 'NAME': BASE_DIR / 'db' / 'zd_db.sqlite3' +if DEBUG: + DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': BASE_DIR / 'db' / 'zd_db.sqlite3' + } + } +else: + DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.postgresql', + 'NAME': os.getenv('ACTRL_DB_NAME'), + 'USER': os.getenv('ACTRL_DB_USER'), + 'PASSWORD': os.getenv('ACTRL_DB_PASSWORD'), + 'HOST': os.getenv('ACTRL_DB_HOST'), + 'PORT': os.getenv('ACTRL_DB_PORT'), + } } -} # Password validation # https://docs.djangoproject.com/en/3.1/ref/settings/#auth-password-validators @@ -137,7 +146,7 @@ USE_TZ = True # https://docs.djangoproject.com/en/3.1/howto/static-files/ STATIC_URL = '/static/' -STATIC_ROOT = os.path.join(BASE_DIR, 'staticroot') +STATIC_ROOT = BASE_DIR / 'build' / 'webserver' / 'static' STATICFILES_DIRS = [ os.path.join(BASE_DIR, 'static'), ] diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..60191d7 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,35 @@ +version: '3.8' + +services: + web: + image: gitlab.cazzzer.com:5050/cazzzer/zendesk-access-controller:alpine + env_file: + - .env.prod + volumes: + - static:/access_controller/srv + expose: + - 8000 + depends_on: + - db + + db: + image: postgres:13-alpine + volumes: + - postgres_data:/var/lib/postgresql/data/ + expose: + - 5432 + env_file: + - .env.prod.db + + nginx: + build: ./nginx + volumes: + - static:/srv/access_controller + ports: + - 8004:80 + depends_on: + - web + +volumes: + postgres_data: + static: diff --git a/documentation.sh b/documentation.sh index bfb2cd0..429a41f 100755 --- a/documentation.sh +++ b/documentation.sh @@ -1,7 +1,23 @@ -#!/usr/bin/env bash +#!/usr/bin/env sh -m2r README.md -cd docs -make html -cd .. +retry() { + retries=$1 + shift + + count=0 + until "$@"; do + exit=$? + count=$((count + 1)) + if [ $count -lt $retries ]; then + echo "Retry $count/$retries exited $exit, retrying..." + else + echo "Retry $count/$retries exited $exit, no more retries left." + return $exit + fi + done + return 0 +} + +m2r README.md --overwrite +sphinx-build -b html docs/source build/webserver/docs rm README.rst diff --git a/nginx/Dockerfile b/nginx/Dockerfile new file mode 100644 index 0000000..4580e8d --- /dev/null +++ b/nginx/Dockerfile @@ -0,0 +1,4 @@ +FROM nginx:1.21-alpine + +RUN rm /etc/nginx/conf.d/default.conf +COPY nginx.conf /etc/nginx/conf.d diff --git a/nginx/nginx.conf b/nginx/nginx.conf new file mode 100644 index 0000000..24d445d --- /dev/null +++ b/nginx/nginx.conf @@ -0,0 +1,21 @@ +upstream actrl { + server web:8000; +} + +server { + + listen 80; + + location / { + proxy_pass http://actrl; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header Host $host; + proxy_redirect off; + } + + location ~ /(static|docs)/ { + root /srv/access_controller/; + autoindex off; + } + +} diff --git a/requirements/prod.txt b/requirements/prod.txt index 479c608..6337a65 100644 --- a/requirements/prod.txt +++ b/requirements/prod.txt @@ -1,5 +1,6 @@ # Production specific dependencies -r common.txt +psycopg2==2.8.6 daphne==3.0.2 Twisted[tls,http2]==21.2.0 diff --git a/start.sh b/start.sh deleted file mode 100644 index 4041353..0000000 --- a/start.sh +++ /dev/null @@ -1,6 +0,0 @@ -cd /access_controller/ - -python manage.py migrate - -python manage.py collectstatic --noinput -daphne -b 0.0.0.0 access_controller.asgi:application