Dev

웹 사이트 내에서 문제 도커 컴포즈 파일 빌드하기

Wermut 2025. 2. 27. 02:36

 

upload.controller.js

 

// upload.controller.js

const UploadModel = require('../models/upload.js');
const jwt = require("jsonwebtoken");
const multer = require('multer');
const path = require('path'); 
const AdmZip = require('adm-zip'); 
const fs = require('fs');
const { spawn } = require('child_process');

const SECRET_key = process.env.SECRET_key;

const upload = multer({
    storage: multer.diskStorage({
      destination(req, file, done) {
        done(null, "../upload"); 
      },

      filename(req, file, done) {
        const ext = path.extname(file.originalname);

        if (ext !== '.zip') {
            return done(new Error('Only zip files are allowed.'));
        }

        done(null, path.basename(file.originalname, ext) + ext);
      },
    }),
    limits: {
        fileSize: 10000 * 1024 * 1024, // 10GB
    },
});

// 압축 해제 함수
async function extractZipFile(zipPath, outputDir) {
    return new Promise((resolve, reject) => {
        try {
            const zip = new AdmZip(zipPath);  // 압축 파일 열기
            zip.extractAllTo(outputDir, true);  // 지정된 디렉토리에 압축 해제

            console.log(`Extraction complete. Files are extracted to ${outputDir}`);
            resolve();  // 압축 해제 완료 후 resolve
        } catch (error) {
            reject(error);  // 에러 발생 시 reject
        }
    });
}

// 도커 빌드 함수
async function runDocker(serverFilePath) {
    const dockerComposePath = path.join(serverFilePath, 'docker-compose.yml');

    return new Promise((resolve, reject) => {
        const dockerCompose = spawn('docker-compose', ['-f', dockerComposePath, 'up', '--build']);

        dockerCompose.stdout.on('data', (data) => {
            console.log(`stdout: ${data}`);

            resolve();

        });

        dockerCompose.stderr.on('data', (data) => {
            console.error(`stderr: ${data}`);
        });
    });
}


exports.renderUploadPage = (req, res) => {
    try {
        const token = req.cookies.session;
        const verified = jwt.verify(token, SECRET_key);
        console.log(verified.class);

        if (verified.class === 1){
            res.render("upload");
        } else {
            return res.send(`<script>alert("Warning: Invalid Token"); window.location.href = '/';</script>`);
        }

    } catch (error) {
        console.error("JWT verification failed:", error);  
        return res.send(`<script>alert("Warning: Invalid Token"); window.location.href = '/';</script>`);
    }
};

exports.handleUpload = async (req, res) => {
    const { title, text, category, flag, port, score } = req.body;
    const releaseFileName = req.files.releaseFile[0].originalname;
    

    console.log(req.body); 
    console.log(req.files); 

    try {

        // JWT 인증
        const token = req.cookies.session;
        const verified = jwt.verify(token, SECRET_key);

        const usrIdx = await UploadModel.whatUsrIdx(verified.id);
        await UploadModel.uploadProblem({ usrIdx, title, text, category, flag, score, port, releaseFileName });

        if (req.body.category == "Web" || req.body.category == "Pwn") {
            const serverFile = req.files.serverFile[0];
            // serverFile zip 파일 압축 해제
            const outputDir = path.join(__dirname, '../workspace', serverFile.originalname);
            await extractZipFile(serverFile.path, outputDir); //압축해제

            console.log("success unzip");

            // docker-compose 파일 빌드
            await runDocker(outputDir);

            console.log("success build");
        }

        res.send(`<script>alert("upload success"); window.location.href = '/wargame';</script>`);
    } catch (error) {
        console.error("Problem Upload Fail:", error);
        return res.send(`<script>alert("Problem upload Fail. Please try again."); window.location.href = '/';</script>`);
    }
};

exports.uploadMiddleware = upload.fields([
    { name: 'releaseFile', maxCount: 1 },
    { name: 'serverFile', maxCount: 1 }  
]);

업로드 컨트롤러 파일 전문이다. 이 코드의 중점이 되는 handleUpload 함수의 역할은 다음과 같다.

 

handleUpload

  1.  관리자가 배포용 파일, 서버 빌드용 .zip 파일을 각각 업로드한다.
  2.  두 파일을 업로드 디렉터리에 업로드한다.
  3.  서버 빌드용 파일은 압축 해제 하여 workspace 디렉터리에 이동한다.
  4.  docker-compose 파일을 빌드한다.
  5.  빌드한 url 링크를 반환한다.

 

기능 구현 

 

 

1. 압축 해제 구현

// 압축 해제 함수


async function extractZipFile(zipPath, outputDir) {
    return new Promise((resolve, reject) => {
        try {
            const zip = new AdmZip(zipPath);  // 압축 파일 열기
            zip.extractAllTo(outputDir, true);  // 지정된 디렉터리에 압축 해제

            console.log(`Extraction complete. Files are extracted to ${outputDir}`);
            resolve();  // 압축 해제 완료 후 resolve
        } catch (error) {
            reject(error);  // 에러 발생 시 reject
        }
    });
}

 

입력된 서버파일을 지정된 디렉터리에 압축 해제하는 함수이다.

new AdmZip 함수를 통해 .zip 파일을 다루는 인스턴스를 생성하고 , zip.extractAllTo 함수를 통해 지정된 함수로 추출한다.

 

 

2. 도커 빌드 구현

// 도커 빌드 함수


async function runDocker(serverFilePath) {
    const dockerComposePath = path.join(serverFilePath, 'docker-compose.yml');

    return new Promise((resolve) => {
        const dockerCompose = spawn('docker-compose', ['-f', dockerComposePath, 'up', '--build']);

        dockerCompose.stdout.on('data', (data) => {
            console.log(`stdout: ${data}`);
            resolve();
        });

        dockerCompose.stderr.on('data', (data) => {
            console.error(`stderr: ${data}`);
        });
    });
}

 

압축 해제된 파일을 찾아 도커 컴포즈 파일을 빌드하는 함수이다. 

spawn 함수를 사용해 도커 컴포즈 파일을 빌드하며 빌드될 시 resolve()를 통해 빠져나온다.

 

이러한 과정을 통해 웹 사이트에서 정상적으로 도커파일을 빌드해 해당 주소로 접근하는 것이 가능하다.

 

web.zip
0.01MB

 

 

 

https://github.com/f10vv3r/wargame-docker/tree/main/wargame_git/wargame-gcp-master

 

wargame-docker/wargame_git/wargame-gcp-master at main · f10vv3r/wargame-docker

Contribute to f10vv3r/wargame-docker development by creating an account on GitHub.

github.com

 

이렇게 최종적으로 프로젝트를 마무리할 수 있었다.