Skip to content
This repository has been archived by the owner on Aug 21, 2024. It is now read-only.

Commit

Permalink
ir 2253 fix project zip archiver (#10437)
Browse files Browse the repository at this point in the history
* ir 2253 fix project zip archiver

* console log

* fix job args

* remove storage arg
  • Loading branch information
HexaField authored Jun 24, 2024
1 parent c826939 commit 33030f4
Show file tree
Hide file tree
Showing 7 changed files with 51 additions and 57 deletions.
3 changes: 1 addition & 2 deletions packages/common/src/schemas/media/archiver.schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,7 @@ export const archiverPath = 'archiver'
export const archiverMethods = ['get'] as const

export const archiverQueryProperties = Type.Object({
directory: Type.Optional(Type.String()),
storageProviderName: Type.Optional(Type.String()),
project: Type.Optional(Type.String()),
isJob: Type.Optional(Type.Boolean()),
jobId: Type.Optional(Type.String())
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -318,10 +318,9 @@ const FileBrowserContentPanel: React.FC<FileBrowserContentPanelProps> = (props)
const showBackButton = selectedDirectory.value !== originalPath

const handleDownloadProject = async () => {
const url = selectedDirectory.value
const data = await Engine.instance.api
.service(archiverPath)
.get(null, { query: { directory: url } })
.get(null, { query: { project: projectName } })
.catch((err: Error) => {
NotificationService.dispatchNotify(err.message, { variant: 'warning' })
return null
Expand Down
46 changes: 13 additions & 33 deletions packages/server-core/src/media/recursive-archiver/archiver.class.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,29 +48,15 @@ const DIRECTORY_ARCHIVE_TIMEOUT = 60 * 10 //10 minutes

export interface ArchiverParams extends KnexAdapterParams<ArchiverQuery> {}

const archive = async (app: Application, directory, params?: ArchiverParams): Promise<string> => {
if (directory.at(0) === '/') directory = directory.slice(1)
if (!directory.startsWith('projects/') || ['projects', 'projects/'].includes(directory)) {
return Promise.reject(new Error('Cannot archive non-project directories'))
}

const split = directory.split('/')
let projectName
if (split[split.length - 1].length === 0) projectName = split[split.length - 2]
else projectName = split[split.length - 1]
projectName = projectName.toLowerCase()

const archive = async (app: Application, projectName: string, params?: ArchiverParams): Promise<string> => {
if (!params) params = {}
if (!params.query) params.query = {}
const storageProviderName = params.query.storageProviderName?.toString()

delete params.query.storageProviderName
const storageProvider = getStorageProvider()

const storageProvider = getStorageProvider(storageProviderName)
logger.info(`Archiving ${projectName}`)

logger.info(`Archiving ${directory} using ${storageProviderName}`)

const result = await storageProvider.listFolderContent(directory)
const result = await storageProvider.listFolderContent(`projects/${projectName}`)

const zip = new JSZip()

Expand All @@ -91,7 +77,7 @@ const archive = async (app: Application, directory, params?: ArchiverParams): Pr

logger.info(`Added ${result[i].key} to archive`)

const dir = result[i].key.substring(result[i].key.indexOf('/') + 1)
const dir = result[i].key.replace(`projects/${projectName}/`, '')
zip.file(dir, blobPromise)
}

Expand All @@ -107,7 +93,7 @@ const archive = async (app: Application, directory, params?: ArchiverParams): Pr
ContentType: 'archive/zip'
})

logger.info(`Archived ${directory} to ${zipOutputDirectory}`)
logger.info(`Archived ${projectName} to ${zipOutputDirectory}`)

if (params.query.jobId) {
const date = await getDateTimeSql()
Expand All @@ -131,17 +117,11 @@ export class ArchiverService implements ServiceInterface<string, ArchiverParams>
async get(id: NullableId, params?: ArchiverParams) {
if (!params) throw new BadRequest('No directory specified')

const directory = params?.query?.directory!.toString()
delete params.query?.directory
const project = params?.query?.project!.toString()!
delete params.query?.project

if (!config.kubernetes.enabled || params?.query?.isJob) return archive(this.app, directory, params)
if (!config.kubernetes.enabled || params?.query?.isJob) return archive(this.app, project, params)
else {
const split = directory!.split('/')
let projectName
if (split[split.length - 1].length === 0) projectName = split[split.length - 2]
else projectName = split[split.length - 1]
projectName = projectName.toLowerCase()

const date = await getDateTimeSql()
const newJob = await this.app.service(apiJobPath).create({
name: '',
Expand All @@ -150,11 +130,11 @@ export class ArchiverService implements ServiceInterface<string, ArchiverParams>
returnData: '',
status: 'pending'
})
const jobBody = await getDirectoryArchiveJobBody(this.app, directory!, projectName, newJob.id)
const jobBody = await getDirectoryArchiveJobBody(this.app, project, newJob.id)
await this.app.service(apiJobPath).patch(newJob.id, {
name: jobBody.metadata!.name
})
const jobLabelSelector = `etherealengine/directoryField=${projectName},etherealengine/release=${process.env.RELEASE_NAME},etherealengine/directoryArchiver=true`
const jobLabelSelector = `etherealengine/projectField=${project},etherealengine/release=${process.env.RELEASE_NAME},etherealengine/directoryArchiver=true`
const jobFinishedPromise = createExecutorJob(
this.app,
jobBody,
Expand All @@ -166,11 +146,11 @@ export class ArchiverService implements ServiceInterface<string, ArchiverParams>
await jobFinishedPromise
const job = await this.app.service(apiJobPath).get(newJob.id)

logger.info(`Archived ${directory} to ${job.returnData}`)
logger.info(`Archived ${project} to ${job.returnData}`)

return job.returnData
} catch (err) {
console.log('Error: Directory was not properly archived', directory, err)
console.log('Error: Directory was not properly archived', project, err)
throw new BadRequest('Directory was not properly archived')
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,17 +23,41 @@ All portions of the code written by the Ethereal Engine team are Copyright © 20
Ethereal Engine. All Rights Reserved.
*/

import { disallow } from 'feathers-hooks-common'
import { disallow, discardQuery, iff, iffElse, isProvider } from 'feathers-hooks-common'

import logRequest from '@etherealengine/server-core/src/hooks/log-request'
import { BadRequest } from '@feathersjs/errors'
import { HookContext } from '@feathersjs/feathers'
import checkScope from '../../hooks/check-scope'
import resolveProjectId from '../../hooks/resolve-project-id'
import setLoggedinUserInBody from '../../hooks/set-loggedin-user-in-body'
import verifyProjectPermission from '../../hooks/verify-project-permission'
import verifyScope from '../../hooks/verify-scope'
import { ArchiverService } from './archiver.class'

// Don't remove this comment. It's needed to format import lines nicely.
const ensureProject = async (context: HookContext<ArchiverService>) => {
if (context.method !== 'get') throw new BadRequest(`${context.path} service only works for data in get`)

if (!context.params.query.project) throw new BadRequest('Project is required')
}

export default {
before: {
all: [logRequest()],
find: [disallow()],
get: [],
get: [
ensureProject,
iff(
isProvider('external'),
iffElse(
checkScope('static_resource', 'write'),
[],
[verifyScope('editor', 'write'), resolveProjectId(), verifyProjectPermission(['owner', 'editor'])]
)
),
setLoggedinUserInBody('userId'),
discardQuery('projectId')
],
create: [disallow()],
update: [disallow()],
patch: [disallow()],
Expand Down
14 changes: 4 additions & 10 deletions packages/server-core/src/projects/project/project-helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1154,30 +1154,24 @@ export const getCronJobBody = (project: ProjectType, image: string): object => {

export async function getDirectoryArchiveJobBody(
app: Application,
directory: string,
projectName: string,
jobId: string,
storageProviderName?: string
jobId: string
): Promise<k8s.V1Job> {
const command = [
'npx',
'cross-env',
'ts-node',
'--swc',
'scripts/archive-directory.ts',
`--directory`,
directory,
`--project`,
projectName,
'--jobId',
jobId
]
if (storageProviderName) {
command.push('--storageProviderName')
command.push(storageProviderName)
}

const labels = {
'etherealengine/directoryArchiver': 'true',
'etherealengine/directoryField': projectName,
'etherealengine/projectField': projectName,
'etherealengine/release': process.env.RELEASE_NAME || ''
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ export const useValidProjectForFileBrowser = (projectName: string) => {
allowed: true
}
})
return projects.data.find((project) => projectName.startsWith(`/projects/${project}/`))?.name ?? ''
return projects.data.find((project) => projectName.startsWith(`/projects/${project.name}/`))?.name ?? ''
}

function GeneratingThumbnailsProgress() {
Expand Down Expand Up @@ -348,10 +348,9 @@ const FileBrowserContentPanel: React.FC<FileBrowserContentPanelProps> = (props)
const showBackButton = selectedDirectory.value.split('/').length > props.originalPath.split('/').length

const handleDownloadProject = async () => {
const url = selectedDirectory.value
const data = await Engine.instance.api
.service(archiverPath)
.get(null, { query: { directory: url } })
.get(null, { query: { project: projectName } })
.catch((err: Error) => {
NotificationService.dispatchNotify(err.message, { variant: 'warning' })
return null
Expand Down
7 changes: 3 additions & 4 deletions scripts/archive-directory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,18 +51,17 @@ db.url = process.env.MYSQL_URL ?? `mysql://${db.username}:${db.password}@${db.ho
cli.enable('status')

const options = cli.parse({
directory: [false, 'Directory to archive', 'string'],
storageProviderName: [false, 'Storage Provider Name', 'string'],
project: [false, 'Project to archive', 'string'],
jobId: [false, 'ID of Job record', 'string']
})

cli.main(async () => {
try {
const app = createFeathersKoaApp(ServerMode.API, serverJobPipe)
await app.setup()
const { directory, jobId, storageProviderName } = options
const { project, jobId } = options
await app.service(archiverPath).get(null, {
query: { storageProviderName: storageProviderName || undefined, isJob: true, directory, jobId }
query: { isJob: true, project, jobId }
})
cli.exit(0)
} catch (err) {
Expand Down

0 comments on commit 33030f4

Please sign in to comment.