Skip to content

Commit

Permalink
add multipart upload
Browse files Browse the repository at this point in the history
  • Loading branch information
persononomo committed Nov 9, 2023
1 parent cc7a020 commit 02fca16
Show file tree
Hide file tree
Showing 4 changed files with 226 additions and 51 deletions.
85 changes: 34 additions & 51 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,74 +1,57 @@
const core = require('@actions/core');
const axios = require('axios');
const fs = require('fs');
const {getFileSize} = require('./src/fileHelpers');
const sendFile = require('./src/sendFile');
const sendMultipartFile = require('./src/sendMultipartFile');
core.info(`> Starting to upload the app to Oversecured...`)

const API_KEY = core.getInput('access_token');
const INTEGRATION_ID = core.getInput('integration_id');
const BRANCH_NAME = core.getInput('branch_name') || 'main';
const APP_PATH = core.getInput('app_path');

const BASE_URL = 'https://api.oversecured.com/v1';
const ADD_VERSION = `${BASE_URL}/integrations/${INTEGRATION_ID}/branches/${BRANCH_NAME}/versions/add`;

async function run() {
try {
const API_KEY = core.getInput('access_token');
const INTEGRATION_ID = core.getInput('integration_id');
const BRANCH_NAME = core.getInput('branch_name') || 'main';
const appPath = core.getInput('app_path');

core.info(`App path: ${appPath}`)
core.info(`App path: ${APP_PATH}`)
core.info(`Integration ID: ${INTEGRATION_ID}`)
core.info(`Branch name: ${BRANCH_NAME}`)

const BASE_URL = 'https://api.oversecured.com/v1';
const ADD_VERSION = `${BASE_URL}/integrations/${INTEGRATION_ID}/branches/${BRANCH_NAME}/versions/add`;
const GET_SIGNED_LINK = `${BASE_URL}/upload/app`;

const apiSession = axios.create({
baseURL: BASE_URL,
headers: {'Authorization': API_KEY}
});

const fileName = appPath.split('/').pop();
const fileName = APP_PATH.split('/').pop();
const platform = getPlatform(fileName);

const signReq = {
'file_name': fileName,
'platform': platform
};
core.info('Requesting a signed url...')
const getUrlResponse = await apiSession.post(GET_SIGNED_LINK, signReq);
if (getUrlResponse.status !== 200) {
throw new Error(`Failed to get a signed url: ${getUrlResponse.data}`);
}
const signInfo = getUrlResponse.data;
core.info('Reading app file...')
let fileData
try {
fileData = fs.readFileSync(appPath);
} catch (error) {
throw new Error(`Failed to read the file: ${error.message}`);
}
core.info(`Uploading the file to Oversecured...`)
let putFileResponse
try {
putFileResponse = await axios.put(signInfo['url'], fileData, {
maxBodyLength: Infinity
});
} catch (error) {
throw new Error(`Failed to upload file: ${error.message}`);
}
if (putFileResponse.status !== 200) {
throw new Error(`Wrong response code: ${putFileResponse.status}`);
let bucket_key
let fileSize = getFileSize(APP_PATH);
if (fileSize > 500 * 1024 * 1024) { // 500 MB
bucket_key = await sendMultipartFile(apiSession, fileName, platform, APP_PATH)
} else {
bucket_key = await sendFile(apiSession, fileName, platform, APP_PATH)
}

core.info(`Creating a new version...`)
const addVersionReq = {
'file_name': fileName,
'bucket_key': signInfo['bucket_key']
};
if (bucket_key) {
core.info(`Creating a new version...`)
const addVersionReq = {
'file_name': fileName,
'bucket_key': bucket_key
};

let addVersionResponse = await apiSession.post(ADD_VERSION, addVersionReq);
if (addVersionResponse.status !== 200) {
throw new Error(`Failed to add version: ${addVersionResponse.data}`);
let addVersionResponse = await apiSession.post(ADD_VERSION, addVersionReq);
if (addVersionResponse.status !== 200) {
core.error(`Wrong response code: ${addVersionResponse.status}`);
core.error('> App upload failed!')
} else {
core.info('> App uploaded successfully!')
}
} else {
core.error('> App upload failed!')
}

core.info('> App uploaded successfully!')

} catch (error) {
core.setFailed(error.message);
}
Expand Down
37 changes: 37 additions & 0 deletions src/fileHelpers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
const fs = require('fs');

function getFileSize(path) {
return fs.statSync(path).size;
}

function readFileSync(path) {
try {
return fs.readFileSync(path);
} catch (error) {
throw new Error(`Failed to read the file: ${error.message}`);
}
}

function createChunkStream(filePath, start, end) {
try {
return fs.createReadStream(filePath, {start, end: end - 1});
} catch (error) {
throw new Error(`Failed to read the file chunk: ${error.message}`);
}
}

function streamToBuffer(stream) {
return new Promise((resolve, reject) => {
const chunks = [];
stream.on('data', (chunk) => chunks.push(chunk));
stream.on('error', reject);
stream.on('end', () => resolve(Buffer.concat(chunks)));
});
}

module.exports = {
getFileSize,
readFileSync,
createChunkStream,
streamToBuffer
}
44 changes: 44 additions & 0 deletions src/sendFile.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
const core = require('@actions/core');
const axios = require('axios');
const {readFileSync} = require('./fileHelpers');

const BASE_URL = 'https://api.oversecured.com/v1';
const GET_SIGNED_LINK = `${BASE_URL}/upload/app`;

async function sendFile(apiSession, fileName, platform, appPath) {
const signReq = {
'file_name': fileName,
'platform': platform
};

core.info('Reading app file...')
let fileData = readFileSync(appPath);

core.info('Requesting a signed url...')
const getUrlResponse = await apiSession.post(GET_SIGNED_LINK, signReq);
if (getUrlResponse.status !== 200) {
throw new Error(`Failed to get a signed url: ${getUrlResponse.data}`);
}
const {url, bucket_key} = getUrlResponse.data;

if (url && bucket_key) {
core.info(`Uploading the file to Oversecured...`)
let putFileResponse
try {
putFileResponse = await axios.put(url, fileData, {
maxBodyLength: Infinity
});
if (putFileResponse.status !== 200) {
core.error(`Wrong response code: ${putFileResponse.status}`);
return null
}
return bucket_key;
} catch (error) {
throw new Error(`Failed to upload file: ${error.message}`);
}
} else {
throw new Error(`Failed to get a signed url: ${getUrlResponse.data}`);
}
}

module.exports = sendFile;
111 changes: 111 additions & 0 deletions src/sendMultipartFile.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
const core = require('@actions/core');
const axios = require('axios');
const {getFileSize, createChunkStream, streamToBuffer} = require('./fileHelpers');

const BASE_URL = 'https://api.oversecured.com/v1';
const CREATE_MULTIPART_UPLOAD = `${BASE_URL}/upload/app/multi/create`;
const GET_PART_SIGNED_LINK = `${BASE_URL}/upload/app/multi/part`;
const COMPLETE_MULTIPART_UPLOAD = `${BASE_URL}/upload/app/multi/complete`;

async function sendMultipartFile(apiSession, fileName, platform, appPath) {
let {uploadId, key} = await createMultipartUpload(apiSession, fileName, platform);
const CHUNK_SIZE_MB = 300;
const FILE_SIZE = getFileSize(appPath);
const CHUNK_SIZE = CHUNK_SIZE_MB * 1024 * 1024; // 300MB
const CHUNKS_COUNT = Math.ceil(FILE_SIZE / CHUNK_SIZE);

core.info(`Uploading the file to Oversecured with ${CHUNKS_COUNT} parts(${CHUNK_SIZE_MB}MB each)...`)

for (let i = 0; i < CHUNKS_COUNT; i++) {
const start = i * CHUNK_SIZE;
const end = (i + 1) * CHUNK_SIZE;
const chunkStream = createChunkStream(appPath, start, end);

// Convert stream to buffer for axios
const chunkBuffer = await streamToBuffer(chunkStream);

try {
const {url} = await getSignedUrl(apiSession, key, uploadId, i + 1);
if (url) {
await uploadPart(i + 1, url, chunkBuffer);
}
} catch (error) {
core.error("Error while uploading part " + (i + 1))
}
}

await completeMultipartUpload(apiSession, uploadId, key)
return key
}


async function createMultipartUpload(apiSession, fileName, platform) {
core.info('Requesting a multipart upload id...')
const signReq = {
'file_name': fileName,
'platform': platform
};
const getMultipartUploadResponse = await apiSession.post(CREATE_MULTIPART_UPLOAD, signReq);
if (getMultipartUploadResponse.status !== 200) {
throw new Error(`Failed to get a multipart upload id: ${getMultipartUploadResponse.data}`);
}
const uploadId = getMultipartUploadResponse.data['upload_id'];
const key = getMultipartUploadResponse.data['key'];

return {uploadId, key}
}

async function getSignedUrl(apiSession, key, uploadId, partNumber) {
core.info(`Requesting a signed url for part ${partNumber}...`)
const signReq = {
'key': key,
'upload_id': uploadId,
'part_number': partNumber
};
try {
const getUrlResponse = await apiSession.post(GET_PART_SIGNED_LINK, signReq);
if (getUrlResponse.status !== 200) {
core.error("Error while getting signed url for part " + partNumber)
return null
}
return getUrlResponse.data;
} catch (error) {
throw new Error(`Failed to get a signed url: ${error.message} for part ${partNumber}`);
}
}

async function uploadPart(partNumber, signedUrl, chunk) {
core.info(`Uploading part ${partNumber}...`)
let putFileResponse
try {
putFileResponse = await axios.put(signedUrl, chunk, {
maxContentLength: Infinity,
maxBodyLength: Infinity,
timeout: 10 * 60 * 1000, // 10 minutes
headers: {
'Content-Length': chunk.length
},
});
} catch (error) {
throw new Error(`Failed to upload file: ${error.message}`);
}
if (putFileResponse.status !== 200) {
throw new Error(`Wrong response code: ${putFileResponse.status}`);
}
}

async function completeMultipartUpload(apiSession, uploadId, key) {
core.info('Requesting a complete multipart upload...')
const signReq = {
'upload_id': uploadId,
'key': key
};
const completeMultipartUploadResponse = await apiSession.post(COMPLETE_MULTIPART_UPLOAD, signReq);
if (completeMultipartUploadResponse.status !== 200) {
throw new Error(`Failed to complete a multipart upload: ${completeMultipartUploadResponse.data}`);
}

return true
}

module.exports = sendMultipartFile;

0 comments on commit 02fca16

Please sign in to comment.