Ansible 기능 추가

This commit is contained in:
2025-05-21 01:08:10 +09:00
parent 69aca7ae85
commit ed5c62b1e5
10 changed files with 161 additions and 109 deletions

74
.github/workflows/build.yaml vendored Normal file
View File

@ -0,0 +1,74 @@
name: Build And Test
run-name: ${{ gitea.actor }} is runs ci pipeline
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
# paths-ignore:
# - LICENCE
# - 'docs/**'
# - 'helm/**'
jobs:
build-and-push:
runs-on: ubuntu-latest
steps:
- name: checkout source code
uses: actions/checkout@v3
- name: Retrieve version # tag version
id: img-ver
uses: juliangruber/read-file-action@v1
with:
path: ./version
- name: Install Docker // Docker 설치
run: |
curl -fsSL https://get.docker.com -o get-docker.sh
sh get-docker.sh
if: runner.os == 'Linux'
- name: Set up Docker Buildx
# uses: https://github.com/docker/setup-buildx-action@v1
uses: docker/setup-buildx-action@v1
- name: Login to Registry
run: docker login -u ${{ secrets.DOCKER_ID }} -p ${{ secrets.DOCKER_PW }} https://harbor.icurfer.com
- name: build
run: docker build -t harbor.icurfer.com/msa-demo/msa-django-ansible:${{ steps.img-ver.outputs.content }} .
- name: Push to Docker
run: docker push harbor.icurfer.com/msa-demo/msa-django-ansible:${{ steps.img-ver.outputs.content }}
## pre cd
- name: Setup Kustomize
uses: yokawasa/action-setup-kube-tools@v0.9.2
with:
kustomize: "3.7.0"
- name: Checkout kustomize repository
uses: actions/checkout@v3
with:
repository: "dev/cd-msa-django-ansible"
ref: main
token: ${{ secrets.ACTION_TOKEN }}
path: cd-msa-django-ansible
- name: Update Kubernetes resources
run: |
cd cd-msa-django-ansible/overlays/dev/
kustomize edit set image harbor.icurfer.com/msa-demo/msa-django-ansible:${{ steps.img-ver.outputs.content }}
cat kustomization.yaml
## cd commit
- name: Commit files
run: |
cd cd-msa-django-ansible
git config --global user.email "icurfer@gmail.com"
git config --global user.name "icurfer"
git commit -am "Update image tag"
git push -u origin main

View File

@ -1,5 +1,9 @@
# msa-django-ansible # msa-django-ansible
추후 개선필요
auth에 암호화되서 저장된 ssh키를 이쪽에서 호출하고 복호화는 ansible server에서 하도록 해야함...
```bash ```bash
python3 -m venv ./venv python3 -m venv ./venv
``` ```

View File

@ -0,0 +1,19 @@
# Generated by Django 4.2.14 on 2025-05-20 15:56
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('ansible', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='ansibletask',
name='author_email',
field=models.CharField(default='icurfer@gmail.com', max_length=150),
preserve_default=False,
),
]

View File

@ -1,11 +1,15 @@
# ansible/models.py
from django.db import models from django.db import models
class AnsibleTask(models.Model): class AnsibleTask(models.Model):
name = models.CharField(max_length=200) name = models.CharField(max_length=200)
playbook_content = models.TextField() # ✅ YAML 문자열 author_email = models.CharField(max_length=150)
inventory_content = models.TextField() # ✅ 인벤토리 형식 문자열 playbook_content = models.TextField()
status = models.CharField(max_length=50, default='pending') # 'pending', 'running', 'success', 'failed', 'error' inventory_content = models.TextField()
output = models.TextField(blank=True) # ✅ 실행 결과 로그 status = models.CharField(max_length=50, default='pending') # pending, running, success, failed, error
output = models.TextField(blank=True)
created_at = models.DateTimeField(auto_now_add=True) created_at = models.DateTimeField(auto_now_add=True)
def __str__(self): def __str__(self):

View File

@ -1,8 +1,8 @@
# msa-django-ansible/serializers.py
from rest_framework import serializers from rest_framework import serializers
from .models import AnsibleTask from .models import AnsibleTask
# ✅ 기본 Serializer: 목록 / 생성용
class AnsibleTaskSerializer(serializers.ModelSerializer): class AnsibleTaskSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = AnsibleTask model = AnsibleTask
@ -18,7 +18,6 @@ class AnsibleTaskSerializer(serializers.ModelSerializer):
read_only_fields = ("id", "status", "output", "created_at") read_only_fields = ("id", "status", "output", "created_at")
# ✅ 상세용 Serializer: 실행 결과만 확인
class AnsibleTaskDetailSerializer(serializers.ModelSerializer): class AnsibleTaskDetailSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = AnsibleTask model = AnsibleTask

View File

@ -1,9 +1,20 @@
# services.py # msa-django-ansible/services.py
import os import os
import requests
import tempfile import tempfile
import subprocess import subprocess
from .models import AnsibleTask
from django.conf import settings from django.conf import settings
from .models import AnsibleTask
def get_ssh_key_from_auth_server(access_token: str) -> str:
url = settings.AUTH_VERIFY_URL + "/api/auth/ssh-key/view/"
print(url)
headers = {"Authorization": f"Bearer {access_token}"}
response = requests.get(url, headers=headers)
if response.status_code != 200:
raise Exception("🔐 Auth 서버에서 SSH 키 조회 실패")
return response.json().get("ssh_key")
def run_ansible_job(task: AnsibleTask, ssh_key: str): def run_ansible_job(task: AnsibleTask, ssh_key: str):
@ -16,12 +27,11 @@ def run_ansible_job(task: AnsibleTask, ssh_key: str):
tempfile.NamedTemporaryFile(delete=False, mode="w") as private_key_file: tempfile.NamedTemporaryFile(delete=False, mode="w") as private_key_file:
playbook_file.write(task.playbook_content.strip()) playbook_file.write(task.playbook_content.strip())
playbook_file.close()
inventory_file.write(task.inventory_content.strip()) inventory_file.write(task.inventory_content.strip())
inventory_file.close()
private_key_file.write(ssh_key.strip() + "\n") private_key_file.write(ssh_key.strip() + "\n")
playbook_file.close()
inventory_file.close()
private_key_file.close() private_key_file.close()
os.chmod(private_key_file.name, 0o600) os.chmod(private_key_file.name, 0o600)
@ -45,5 +55,4 @@ def run_ansible_job(task: AnsibleTask, ssh_key: str):
for f in [playbook_file.name, inventory_file.name, private_key_file.name]: for f in [playbook_file.name, inventory_file.name, private_key_file.name]:
if os.path.exists(f): if os.path.exists(f):
os.remove(f) os.remove(f)
task.save() task.save()

View File

@ -1,3 +1,4 @@
# ansible/urls.py
from django.urls import path, include from django.urls import path, include
from rest_framework.routers import DefaultRouter from rest_framework.routers import DefaultRouter
from .views import AnsibleTaskViewSet from .views import AnsibleTaskViewSet

View File

@ -1,101 +1,47 @@
from rest_framework.views import APIView from rest_framework import viewsets, status
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework import status
from rest_framework.permissions import IsAuthenticated from rest_framework.permissions import IsAuthenticated
from rest_framework_simplejwt.views import TokenObtainPairView from rest_framework.decorators import action
from .models import AnsibleTask
from .serializers import RegisterSerializer, CustomTokenObtainPairSerializer from .serializers import AnsibleTaskSerializer, AnsibleTaskDetailSerializer
from cryptography.fernet import Fernet from .services import run_ansible_job, get_ssh_key_from_auth_server
from django.conf import settings
import base64
import hashlib
# Fernet 키를 settings.SECRET_KEY에서 파생
hashed = hashlib.sha256(settings.SECRET_KEY.encode()).digest()
fernet_key = base64.urlsafe_b64encode(hashed[:32])
fernet = Fernet(fernet_key)
class RegisterView(APIView): class AnsibleTaskViewSet(viewsets.ModelViewSet):
def post(self, request): serializer_class = AnsibleTaskSerializer
serializer = RegisterSerializer(data=request.data)
if serializer.is_valid():
user = serializer.save()
return Response({"message": "User registered successfully."}, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
class MeView(APIView):
permission_classes = [IsAuthenticated] permission_classes = [IsAuthenticated]
def get(self, request): def get_queryset(self):
user = request.user # ✅ 현재 로그인한 사용자의 email로 필터
serializer = RegisterSerializer(user) return AnsibleTask.objects.filter(author_email=self.request.user.email).order_by("-created_at")
return Response(serializer.data)
def put(self, request): def get_serializer_class(self):
user = request.user if self.action == "retrieve":
serializer = RegisterSerializer(user, data=request.data, partial=True) return AnsibleTaskDetailSerializer
if serializer.is_valid(): return AnsibleTaskSerializer
serializer.save()
return Response(serializer.data)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
class CustomTokenObtainPairView(TokenObtainPairView): # ✅ author_email 저장
serializer_class = CustomTokenObtainPairSerializer task = serializer.save(author_email=request.user.email)
return Response(self.get_serializer(task).data, status=status.HTTP_201_CREATED)
# try:
# token = request.headers.get("Authorization", "").replace("Bearer ", "")
# ssh_key = get_ssh_key_from_auth_server(token)
# run_ansible_job(task, ssh_key)
# return Response(self.get_serializer(task).data, status=status.HTTP_201_CREATED)
# except Exception as e:
# return Response({"error": f"작업 실행 실패: {str(e)}"}, status=500)
@action(detail=True, methods=["post"], url_path="run")
class SSHKeyUploadView(APIView): def run_task(self, request, pk=None):
permission_classes = [IsAuthenticated] task = self.get_object()
def post(self, request):
private_key = request.data.get("private_key")
key_name = request.data.get("key_name")
if not private_key or not key_name:
return Response(
{"error": "private_key와 key_name 모두 필요합니다."},
status=status.HTTP_400_BAD_REQUEST
)
user = request.user
try: try:
encrypted_key = fernet.encrypt(private_key.encode()).decode() # ✅ decode 추가 token = request.headers.get("Authorization", "").replace("Bearer ", "")
user.encrypted_private_key = encrypted_key ssh_key = get_ssh_key_from_auth_server(token)
user.encrypted_private_key_name = key_name run_ansible_job(task, ssh_key)
user.save(update_fields=["encrypted_private_key", "encrypted_private_key_name"]) return Response(self.get_serializer(task).data)
return Response({"message": "SSH key 저장 완료."})
except Exception as e: except Exception as e:
return Response({"error": str(e)}, status=500) return Response({"error": f"작업 실행 실패: {str(e)}"}, status=500)
def delete(self, request):
user = request.user
user.encrypted_private_key = None
user.encrypted_private_key_name = None
user.last_used_at = None
user.save(update_fields=["encrypted_private_key", "encrypted_private_key_name", "last_used_at"])
return Response({"message": "SSH key deleted."}, status=200)
class SSHKeyInfoView(APIView):
permission_classes = [IsAuthenticated]
def get(self, request):
user = request.user
return Response({
"has_key": bool(user.encrypted_private_key),
"encrypted_private_key_name": user.encrypted_private_key_name,
"last_used_at": user.last_used_at
})
# ✅ 실제 암호화된 키를 반환하는 API
class SSHKeyRetrieveView(APIView):
permission_classes = [IsAuthenticated]
def get(self, request):
user = request.user
if not user.encrypted_private_key:
return Response({"error": "SSH 키가 등록되어 있지 않습니다."}, status=404)
return Response({"ssh_key": user.encrypted_private_key})

View File

@ -52,11 +52,6 @@ else:
# SECURITY WARNING: keep the secret key used in production secret! # SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = os.environ.get('SECRET_KEY', 'django-insecure-ec9me^z%x7-2vwee5#qq(kvn@^cs!!22_*f-im(320_k5-=0j5') SECRET_KEY = os.environ.get('SECRET_KEY', 'django-insecure-ec9me^z%x7-2vwee5#qq(kvn@^cs!!22_*f-im(320_k5-=0j5')
# Fernet은 32바이트 base64 인코딩된 키를 요구하므로, Django SECRET_KEY를 기반으로 키 생성
hashed = hashlib.sha256(SECRET_KEY.encode()).digest()
FERNET_KEY = base64.urlsafe_b64encode(hashed[:32]) # 32 bytes → base64로 인코딩
FERNET = Fernet(FERNET_KEY)
# SECURITY WARNING: don't run with debug turned on in production! # SECURITY WARNING: don't run with debug turned on in production!
DEBUG = int(os.environ.get('DEBUG', 1)) DEBUG = int(os.environ.get('DEBUG', 1))

1
version Normal file
View File

@ -0,0 +1 @@
0.0.1