Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add basic framework for k8s rest agent #177

Merged
merged 1 commit into from
Jul 31, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions build_image/docker/agent/k8s-rest-agent/Dockerfile.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
FROM python:3.8

COPY src/agent/k8s-rest-agent/requirements.txt /
COPY src/agent/k8s-rest-agent/pip /root/.pip

RUN pip install -r /requirements.txt

COPY src/agent/k8s-rest-agent/src /var/www/server
COPY src/agent/k8s-rest-agent/entrypoint.sh /
COPY src/agent/k8s-rest-agent/uwsgi/server.ini /etc/uwsgi/apps-enabled/
RUN mkdir /var/log/supervisor

ENV WEBROOT /
ENV WEB_CONCURRENCY 10
ENV DEBUG False
ENV UWSGI_WORKERS 1
ENV UWSGI_PROCESSES 1
ENV UWSGI_OFFLOAD_THREADS 10
ENV UWSGI_MODULE server.wsgi:application

WORKDIR /var/www/server
RUN python manage.py collectstatic --noinput

CMD bash /entrypoint.sh
24 changes: 24 additions & 0 deletions src/agent/k8s-rest-agent/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
FROM python:3.8

COPY requirements.txt /
COPY pip /root/.pip

RUN pip install -r /requirements.txt

COPY src /var/www/server
COPY entrypoint.sh /
COPY uwsgi/server.ini /etc/uwsgi/apps-enabled/
RUN mkdir /var/log/supervisor

ENV WEBROOT /
ENV WEB_CONCURRENCY 10
ENV DEBUG False
ENV UWSGI_WORKERS 1
ENV UWSGI_PROCESSES 1
ENV UWSGI_OFFLOAD_THREADS 10
ENV UWSGI_MODULE server.wsgi:application

WORKDIR /var/www/server
RUN python manage.py collectstatic --noinput

CMD bash /entrypoint.sh
11 changes: 11 additions & 0 deletions src/agent/k8s-rest-agent/entrypoint.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#!/usr/bin/env bash

if [[ "$RUN_TYPE" == "SERVER" ]]; then
uwsgi --ini /etc/uwsgi/apps-enabled/server.ini;
else
if [[ "$RUN_TYPE" == "TASK" ]]; then
celery -A server worker --autoscale=20,6 -l info
elif [[ "$RUN_TYPE" == "BEAT_TASK" ]]; then
celery -A server beat -l info --scheduler django_celery_beat.schedulers:DatabaseScheduler --pidfile=/opt/celeryd.pid
fi
fi
5 changes: 5 additions & 0 deletions src/agent/k8s-rest-agent/pip/pip.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[global]
index-url=http://mirrors.cloud.aliyuncs.com/pypi/simple/

[install]
trusted-host=mirrors.cloud.aliyuncs.com
18 changes: 18 additions & 0 deletions src/agent/k8s-rest-agent/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
Django>=3.0
uwsgi
enum34
djangorestframework
holdup>1.5.0,<=1.6.0
drf-yasg<=1.17.0
swagger_spec_validator<=2.4.1
psycopg2-binary
celery<5.0,>=4.4
redis
requests
supervisor
django-celery-beat
django-celery-results
django-3-jet
djangorestframework-jwt<=1.11.0
python-jwt # 需要安装,否则会出现token解码失败错误
shortuuid
Empty file.
3 changes: 3 additions & 0 deletions src/agent/k8s-rest-agent/src/api/admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from django.contrib import admin

# Register your models here.
5 changes: 5 additions & 0 deletions src/agent/k8s-rest-agent/src/api/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from django.apps import AppConfig


class ApiConfig(AppConfig):
name = "api"
67 changes: 67 additions & 0 deletions src/agent/k8s-rest-agent/src/api/auth.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import base64
import json
import logging

from django.contrib.auth import get_user_model
from django.utils.translation import ugettext as _
from rest_framework import authentication
from rest_framework import exceptions
from rest_framework_jwt.authentication import (
JSONWebTokenAuthentication as CoreJSONWebTokenAuthentication,
)
from rest_framework_jwt.settings import api_settings

jwt_decode_handler = api_settings.JWT_DECODE_HANDLER
jwt_get_username_from_payload = api_settings.JWT_PAYLOAD_GET_USERNAME_HANDLER
User = get_user_model()

LOG = logging.getLogger(__name__)


class JSONWebTokenAuthentication(CoreJSONWebTokenAuthentication):
@staticmethod
def _get_or_create_user(user_id, payload=None):
if payload is None:
payload = {}

user, _ = User.objects.get_or_create(
id=user_id, username=user_id, defaults={"password": user_id}
)

return user

def authenticate_credentials(self, payload):
"""
Returns an active user that matches the payload's user id and email.
"""
username = jwt_get_username_from_payload(payload)

if not username:
msg = _("Invalid payload.")
raise exceptions.AuthenticationFailed(msg)

user = self._get_or_create_user(username, payload)

if not user.is_active:
msg = _("User account is disabled.")
raise exceptions.AuthenticationFailed(msg)

return user


class IstioJWTAuthentication(authentication.BaseAuthentication):
def authenticate(self, request):
token = request.META.get("HTTP_TOKEN", None)
if token is None:
return None

token += "=" * (-len(token) % 4)
token = base64.b64decode(token)
token = json.loads(token)
user_id = token.get("sub", None)
if user_id is None:
return None
user, _ = User.objects.get_or_create(
id=user_id, username=user_id, defaults={"password": user_id}
)
return user, None
Empty file.
Empty file.
14 changes: 14 additions & 0 deletions src/agent/k8s-rest-agent/src/api/management/commands/test_task.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
from django.core.management import BaseCommand
from api.tasks import example_task
from django_celery_beat.models import IntervalSchedule, PeriodicTask


class Command(BaseCommand):
help = "Test Task"

def handle(self, *args, **options):
interval = IntervalSchedule.objects.first()
PeriodicTask.objects.create(
interval=interval, name="example", task="server.tasks.example_task"
)
# example_task.delay()
Empty file.
1 change: 1 addition & 0 deletions src/agent/k8s-rest-agent/src/api/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .user import User, Profile
42 changes: 42 additions & 0 deletions src/agent/k8s-rest-agent/src/api/models/user.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
from django.contrib.auth.models import AbstractUser
from django.db import models
from django.db.models.signals import post_save
from api.utils.db_functions import make_uuid


class User(AbstractUser):
roles = []

id = models.UUIDField(
primary_key=True,
help_text="ID of user",
default=make_uuid,
editable=True,
)
username = models.CharField(default="", max_length=128, unique=True)

def __str__(self):
return self.username


class Profile(models.Model):
user = models.OneToOneField(
User, related_name="profile", on_delete=models.CASCADE
)
created_at = models.DateTimeField(auto_now_add=True)

def __str__(self):
return "%s's profile" % self.user

class Meta:
ordering = ("-created_at",)


def create_user_profile(sender, instance, created, **kwargs):
if created:
Profile.objects.create(user=instance)


post_save.connect(create_user_profile, sender=User)

# Create your models here.
Empty file.
Empty file.
40 changes: 40 additions & 0 deletions src/agent/k8s-rest-agent/src/api/routes/hello/views.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import logging
import os

from rest_framework import viewsets, status
from drf_yasg.utils import swagger_auto_schema
from rest_framework.decorators import action, permission_classes
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response

from api.auth import JSONWebTokenAuthentication, IstioJWTAuthentication
from api.utils.mixins import PermissionsPerMethodMixin

LOG = logging.getLogger(__name__)
APP_VERSION = os.getenv("APP_VERSION", "v1")


class HelloViewSet(PermissionsPerMethodMixin, viewsets.ViewSet):
authentication_classes = (IstioJWTAuthentication,)

@swagger_auto_schema(
operation_summary="Hello world", operation_description="Hello world"
)
def list(self, request):
return Response(
{"hello": "world %s" % APP_VERSION}, status=status.HTTP_200_OK
)

@swagger_auto_schema(operation_summary="hello world need auth")
@action(
methods=["get"],
url_path="need-auth",
url_name="need-auth",
detail=False,
)
# @permission_classes((IsAuthenticated,))
def need_auth(self, request):
LOG.info("request user %s", request.user)
return Response(
{"hello": "auth world %s" % APP_VERSION}, status=status.HTTP_200_OK
)
1 change: 1 addition & 0 deletions src/agent/k8s-rest-agent/src/api/tasks/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from api.tasks.task.example import example_task
Empty file.
12 changes: 12 additions & 0 deletions src/agent/k8s-rest-agent/src/api/tasks/task/example.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import logging

from server.celery import app


LOG = logging.getLogger(__name__)


@app.task(name="example_task")
def example_task():
LOG.info("example task")
return True
3 changes: 3 additions & 0 deletions src/agent/k8s-rest-agent/src/api/tests.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from django.test import TestCase

# Create your tests here.
Empty file.
2 changes: 2 additions & 0 deletions src/agent/k8s-rest-agent/src/api/utils/common/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
from .swagger import with_common_response
from .db import paginate_list
26 changes: 26 additions & 0 deletions src/agent/k8s-rest-agent/src/api/utils/common/db.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
from django.core.paginator import Paginator
from django.db.models import Func


class Round(Func):
function = "ROUND"
arity = 2


def paginate_list(data=None, page=1, per_page=10, limit=None):
if not data:
data = []

total = len(data)

if per_page != -1:
p = Paginator(data, per_page)
last_page = p.page_range[-1]
page = page if page <= last_page else last_page
data = p.page(page)
total = p.count
else:
if limit:
data = data[:limit]

return data, total
62 changes: 62 additions & 0 deletions src/agent/k8s-rest-agent/src/api/utils/common/swagger.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
from drf_yasg import openapi
from rest_framework import serializers
from rest_framework import status

from api.utils.serializers import BadResponseSerializer

basic_type_info = [
(serializers.CharField, openapi.TYPE_STRING),
(serializers.BooleanField, openapi.TYPE_BOOLEAN),
(serializers.IntegerField, openapi.TYPE_INTEGER),
(serializers.FloatField, openapi.TYPE_NUMBER),
(serializers.FileField, openapi.TYPE_FILE),
(serializers.ImageField, openapi.TYPE_FILE),
]


def to_form_paras(self):
custom_paras = []
for field_name, field in self.fields.items():
type_str = openapi.TYPE_STRING
for field_class, type_format in basic_type_info:
if isinstance(field, field_class):
type_str = type_format
help_text = getattr(field, "help_text")
default = getattr(field, "default", None)
required = getattr(field, "required")
if callable(default):
custom_paras.append(
openapi.Parameter(
field_name,
openapi.IN_FORM,
help_text,
type=type_str,
required=required,
)
)
else:
custom_paras.append(
openapi.Parameter(
field_name,
openapi.IN_FORM,
help_text,
type=type_str,
required=required,
default=default,
)
)
return custom_paras


def with_common_response(responses=None):
if responses is None:
responses = {}

responses.update(
{
status.HTTP_400_BAD_REQUEST: BadResponseSerializer,
status.HTTP_500_INTERNAL_SERVER_ERROR: "Internal Error",
}
)

return responses
14 changes: 14 additions & 0 deletions src/agent/k8s-rest-agent/src/api/utils/db_functions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import uuid
import shortuuid


def make_uuid():
return str(uuid.uuid4())


def make_uuid_hex():
return uuid.uuid4().hex


def make_short_uuid():
return shortuuid.ShortUUID().random(length=16)
Loading