more cleanup

This commit is contained in:
Trym Lund Flogard 2021-06-05 14:21:53 +02:00
parent 2b05098faf
commit 7ca2f4d4fb
6 changed files with 40 additions and 83 deletions

View File

@ -15,8 +15,8 @@ export class InputsDouble implements Inputs {
return '' return ''
} }
get Endpoint(): string { get Endpoint(): URL {
return process.env['ENDPOINT']! return new URL(process.env['ENDPOINT']!)
} }
get Username(): string { get Username(): string {

84
dist/index.js vendored
View File

@ -29,30 +29,28 @@ Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.ActionInputs = void 0; exports.ActionInputs = void 0;
const core = __importStar(__nccwpck_require__(2186)); const core = __importStar(__nccwpck_require__(2186));
const NoFileOption_1 = __nccwpck_require__(4295); const NoFileOption_1 = __nccwpck_require__(4295);
const url_1 = __nccwpck_require__(8835);
class ActionInputs { class ActionInputs {
get ArtifactName() { get ArtifactName() {
return core.getInput('name'); return core.getInput('name', { required: false }) || 'Nextcloud Artifact';
} }
get ArtifactPath() { get ArtifactPath() {
return core.getInput('path'); return core.getInput('path', { required: true });
}
get Retention() {
return core.getInput('retention-days');
} }
get Endpoint() { get Endpoint() {
return core.getInput('nextcloud-url'); return new url_1.URL(core.getInput('nextcloud-url', { required: true }));
} }
get Username() { get Username() {
return core.getInput('nextcloud-username'); return core.getInput('nextcloud-username', { required: true });
} }
get Password() { get Password() {
return core.getInput('nextcloud-password'); return core.getInput('nextcloud-password', { required: true });
} }
get Token() { get Token() {
return core.getInput('token', { required: true }); return core.getInput('token', { required: true });
} }
get NoFileBehvaior() { get NoFileBehvaior() {
const notFoundAction = core.getInput('if-no-files-found') || NoFileOption_1.NoFileOption.warn; const notFoundAction = core.getInput('if-no-files-found', { required: false }) || NoFileOption_1.NoFileOption.warn;
const noFileBehavior = NoFileOption_1.NoFileOption[notFoundAction]; const noFileBehavior = NoFileOption_1.NoFileOption[notFoundAction];
if (!noFileBehavior) { if (!noFileBehavior) {
core.setFailed(`Unrecognized ${'ifNoFilesFound'} input. Provided: ${notFoundAction}. Available options: ${Object.keys(NoFileOption_1.NoFileOption)}`); core.setFailed(`Unrecognized ${'ifNoFilesFound'} input. Provided: ${notFoundAction}. Available options: ${Object.keys(NoFileOption_1.NoFileOption)}`);
@ -360,21 +358,22 @@ class NextcloudArtifact {
name: 'Nextcloud Artifacts', name: 'Nextcloud Artifacts',
status: 'in_progress', status: 'in_progress',
output: { output: {
title: 'Nextcloud Artifacts', title: `Nextcloud (${this.name})`,
summary: '' summary: 'Uploading...'
}, },
...github.context.repo ...github.context.repo
}); });
const client = new NextcloudClient_1.NextcloudClient(this.inputs.Endpoint, this.name, files.rootDirectory, this.inputs.Username, this.inputs.Password); const client = new NextcloudClient_1.NextcloudClient(this.inputs.Endpoint, this.name, files.rootDirectory, this.inputs.Username, this.inputs.Password);
try { try {
const shareableUrl = await client.uploadFiles(files.filesToUpload); const shareableUrl = await client.uploadFiles(files.filesToUpload);
core.info(`Nextcloud shareable URL: ${shareableUrl}`);
const resp = await this.octokit.rest.checks.update({ const resp = await this.octokit.rest.checks.update({
check_run_id: createResp.data.id, check_run_id: createResp.data.id,
conclusion: 'success', conclusion: 'success',
status: 'completed', status: 'completed',
output: { output: {
title: 'Nextcloud Artifacts', title: `Nextcloud (${this.name})`,
summary: `${this.name}: ${shareableUrl}` summary: shareableUrl
}, },
...github.context.repo ...github.context.repo
}); });
@ -383,9 +382,8 @@ class NextcloudArtifact {
core.info(`Check run HTML: ${resp.data.html_url}`); core.info(`Check run HTML: ${resp.data.html_url}`);
} }
catch (error) { catch (error) {
core.error(error);
await this.trySetFailed(createResp.data.id); await this.trySetFailed(createResp.data.id);
core.setFailed('Failed to update check'); core.setFailed(error);
} }
} }
async trySetFailed(checkId) { async trySetFailed(checkId) {
@ -395,7 +393,7 @@ class NextcloudArtifact {
conclusion: 'failure', conclusion: 'failure',
status: 'completed', status: 'completed',
output: { output: {
title: 'Nextcloud Artifacts', title: `Nextcloud (${this.name})`,
summary: 'Check failed.' summary: 'Check failed.'
}, },
...github.context.repo ...github.context.repo
@ -486,7 +484,7 @@ class NextcloudClient {
this.password = password; this.password = password;
this.guid = uuid_1.v4(); this.guid = uuid_1.v4();
this.headers = { Authorization: 'Basic ' + btoa_1.default(`${this.username}:${this.password}`) }; this.headers = { Authorization: 'Basic ' + btoa_1.default(`${this.username}:${this.password}`) };
this.davClient = webdav.createClient(`${this.endpoint}/remote.php/dav/files/${this.username}`, { this.davClient = webdav.createClient(`${this.endpoint.href}remote.php/dav/files/${this.username}`, {
username: this.username, username: this.username,
password: this.password password: this.password
}); });
@ -498,8 +496,7 @@ class NextcloudClient {
const zip = await this.zipFiles(spec); const zip = await this.zipFiles(spec);
core.info('Uploading to Nextcloud...'); core.info('Uploading to Nextcloud...');
const filePath = await this.upload(zip); const filePath = await this.upload(zip);
core.info(`File path: ${filePath}`); core.info(`Remote file path: ${filePath}`);
core.info('Sharing file...');
return await this.shareFile(filePath); return await this.shareFile(filePath);
} }
uploadSpec(files) { uploadSpec(files) {
@ -510,58 +507,25 @@ class NextcloudClient {
if (!fsSync.lstatSync(this.rootDirectory).isDirectory()) { if (!fsSync.lstatSync(this.rootDirectory).isDirectory()) {
throw new Error(`this.rootDirectory ${this.rootDirectory} is not a valid directory`); throw new Error(`this.rootDirectory ${this.rootDirectory} is not a valid directory`);
} }
// Normalize and resolve, this allows for either absolute or relative paths to be used
let root = path.normalize(this.rootDirectory); let root = path.normalize(this.rootDirectory);
root = path.resolve(root); root = path.resolve(root);
/*
Example to demonstrate behavior
Input:
artifactName: my-artifact
rootDirectory: '/home/user/files/plz-upload'
artifactFiles: [
'/home/user/files/plz-upload/file1.txt',
'/home/user/files/plz-upload/file2.txt',
'/home/user/files/plz-upload/dir/file3.txt'
]
Output:
specifications: [
['/home/user/files/plz-upload/file1.txt', 'my-artifact/file1.txt'],
['/home/user/files/plz-upload/file1.txt', 'my-artifact/file2.txt'],
['/home/user/files/plz-upload/file1.txt', 'my-artifact/dir/file3.txt']
]
*/
for (let file of files) { for (let file of files) {
if (!fsSync.existsSync(file)) { if (!fsSync.existsSync(file)) {
throw new Error(`File ${file} does not exist`); throw new Error(`File ${file} does not exist`);
} }
if (!fsSync.lstatSync(file).isDirectory()) { if (!fsSync.lstatSync(file).isDirectory()) {
// Normalize and resolve, this allows for either absolute or relative paths to be used
file = path.normalize(file); file = path.normalize(file);
file = path.resolve(file); file = path.resolve(file);
if (!file.startsWith(root)) { if (!file.startsWith(root)) {
throw new Error(`The rootDirectory: ${root} is not a parent directory of the file: ${file}`); throw new Error(`The rootDirectory: ${root} is not a parent directory of the file: ${file}`);
} }
// Check for forbidden characters in file paths that will be rejected during upload
const uploadPath = file.replace(root, ''); const uploadPath = file.replace(root, '');
/*
uploadFilePath denotes where the file will be uploaded in the file container on the server. During a run, if multiple artifacts are uploaded, they will all
be saved in the same container. The artifact name is used as the root directory in the container to separate and distinguish uploaded artifacts
path.join handles all the following cases and would return 'artifact-name/file-to-upload.txt
join('artifact-name/', 'file-to-upload.txt')
join('artifact-name/', '/file-to-upload.txt')
join('artifact-name', 'file-to-upload.txt')
join('artifact-name', '/file-to-upload.txt')
*/
specifications.push({ specifications.push({
absolutePath: file, absolutePath: file,
uploadPath: path.join(this.artifact, uploadPath) uploadPath: path.join(this.artifact, uploadPath)
}); });
} }
else { else {
// Directories are rejected by the server during upload
core.debug(`Removing ${file} from rawSearchResults because it is a directory`); core.debug(`Removing ${file} from rawSearchResults because it is a directory`);
} }
} }
@ -584,7 +548,6 @@ class NextcloudClient {
core.info(`files: ${await fs.readdir(path.join(artifactPath, this.artifact))}`); core.info(`files: ${await fs.readdir(path.join(artifactPath, this.artifact))}`);
const archivePath = path.join(artifactPath, `${this.artifact}.zip`); const archivePath = path.join(artifactPath, `${this.artifact}.zip`);
await this.zip(path.join(artifactPath, this.artifact), archivePath); await this.zip(path.join(artifactPath, this.artifact), archivePath);
core.info(`archive stat: ${(await fs.stat(archivePath)).size}`);
return archivePath; return archivePath;
} }
async zip(dirpath, destpath) { async zip(dirpath, destpath) {
@ -597,13 +560,11 @@ class NextcloudClient {
} }
async upload(file) { async upload(file) {
const remoteFileDir = `/artifacts/${this.guid}`; const remoteFileDir = `/artifacts/${this.guid}`;
core.info('Checking directory...');
if (!(await this.davClient.exists(remoteFileDir))) { if (!(await this.davClient.exists(remoteFileDir))) {
core.info('Creating directory...');
await this.davClient.createDirectory(remoteFileDir, { recursive: true }); await this.davClient.createDirectory(remoteFileDir, { recursive: true });
} }
const remoteFilePath = `${remoteFileDir}/${this.artifact}.zip`; const remoteFilePath = `${remoteFileDir}/${this.artifact}.zip`;
core.info(`Transferring file... (${file})`); core.debug(`Transferring file... (${file})`);
const fileStat = await fs.stat(file); const fileStat = await fs.stat(file);
const fileStream = fsSync.createReadStream(file); const fileStream = fsSync.createReadStream(file);
const fileStreamPromise = new Promise((resolve, reject) => { const fileStreamPromise = new Promise((resolve, reject) => {
@ -616,17 +577,16 @@ class NextcloudClient {
remoteStream.on('error', e => reject(e)).on('finish', () => resolve()); remoteStream.on('error', e => reject(e)).on('finish', () => resolve());
}); });
fileStream.pipe(remoteStream); fileStream.pipe(remoteStream);
// see: https://github.com/nodejs/node/issues/22088
const timer = setTimeout(() => { }, 20000); const timer = setTimeout(() => { }, 20000);
await fileStreamPromise; await Promise.all([fileStreamPromise, remoteStreamPromise]);
await remoteStreamPromise; // HACK: Nextcloud has not fully processed the file, despite returning 200.
// Wait for file to be processed // Waiting for 1s seems to do the trick.
await new Promise(resolve => setTimeout(resolve, 1000)); await new Promise(resolve => setTimeout(resolve, 1000));
clearTimeout(timer); clearTimeout(timer);
return remoteFilePath; return remoteFilePath;
} }
async shareFile(remoteFilePath) { async shareFile(remoteFilePath) {
const url = this.endpoint + `/ocs/v2.php/apps/files_sharing/api/v1/shares`; const url = `${this.endpoint.href}ocs/v2.php/apps/files_sharing/api/v1/shares`;
const body = { const body = {
path: remoteFilePath, path: remoteFilePath,
shareType: 3, shareType: 3,
@ -648,7 +608,7 @@ class NextcloudClient {
core.debug(`Match groups:\n${JSON.stringify(match === null || match === void 0 ? void 0 : match.groups)}`); core.debug(`Match groups:\n${JSON.stringify(match === null || match === void 0 ? void 0 : match.groups)}`);
const sharableUrl = ((match === null || match === void 0 ? void 0 : match.groups) || {})['share_url']; const sharableUrl = ((match === null || match === void 0 ? void 0 : match.groups) || {})['share_url'];
if (!sharableUrl) { if (!sharableUrl) {
throw new Error('Failed to parse sharable URL.'); throw new Error(`Failed to parse or find sharable URL:\n${result}`);
} }
return sharableUrl; return sharableUrl;
} }

2
dist/index.js.map vendored

File diff suppressed because one or more lines are too long

View File

@ -1,30 +1,27 @@
import * as core from '@actions/core' import * as core from '@actions/core'
import { NoFileOption } from './NoFileOption' import { NoFileOption } from './NoFileOption'
import { Inputs } from './Inputs' import { Inputs } from './Inputs'
import { URL } from 'url'
export class ActionInputs implements Inputs { export class ActionInputs implements Inputs {
get ArtifactName(): string { get ArtifactName(): string {
return core.getInput('name') return core.getInput('name', { required: false }) || 'Nextcloud Artifact'
} }
get ArtifactPath(): string { get ArtifactPath(): string {
return core.getInput('path') return core.getInput('path', { required: true })
} }
get Retention(): string { get Endpoint(): URL {
return core.getInput('retention-days') return new URL(core.getInput('nextcloud-url', { required: true }))
}
get Endpoint(): string {
return core.getInput('nextcloud-url')
} }
get Username(): string { get Username(): string {
return core.getInput('nextcloud-username') return core.getInput('nextcloud-username', { required: true })
} }
get Password(): string { get Password(): string {
return core.getInput('nextcloud-password') return core.getInput('nextcloud-password', { required: true })
} }
get Token(): string { get Token(): string {
@ -32,7 +29,7 @@ export class ActionInputs implements Inputs {
} }
get NoFileBehvaior(): NoFileOption { get NoFileBehvaior(): NoFileOption {
const notFoundAction = core.getInput('if-no-files-found') || NoFileOption.warn const notFoundAction = core.getInput('if-no-files-found', { required: false }) || NoFileOption.warn
const noFileBehavior: NoFileOption = NoFileOption[notFoundAction as keyof typeof NoFileOption] const noFileBehavior: NoFileOption = NoFileOption[notFoundAction as keyof typeof NoFileOption]
if (!noFileBehavior) { if (!noFileBehavior) {

View File

@ -1,3 +1,4 @@
import { URL } from 'url'
import { NoFileOption } from './NoFileOption' import { NoFileOption } from './NoFileOption'
export interface Inputs { export interface Inputs {
@ -5,9 +6,7 @@ export interface Inputs {
readonly ArtifactPath: string readonly ArtifactPath: string
readonly Retention: string readonly Endpoint: URL
readonly Endpoint: string
readonly Username: string readonly Username: string

View File

@ -7,6 +7,7 @@ import fetch, { HeadersInit } from 'node-fetch'
import btoa from 'btoa' import btoa from 'btoa'
import { v4 as uuidv4 } from 'uuid' import { v4 as uuidv4 } from 'uuid'
import * as webdav from 'webdav' import * as webdav from 'webdav'
import { URL } from 'url'
const fs = fsSync.promises const fs = fsSync.promises
@ -21,7 +22,7 @@ export class NextcloudClient {
private davClient private davClient
constructor( constructor(
private endpoint: string, private endpoint: URL,
private artifact: string, private artifact: string,
private rootDirectory: string, private rootDirectory: string,
private username: string, private username: string,
@ -29,7 +30,7 @@ export class NextcloudClient {
) { ) {
this.guid = uuidv4() this.guid = uuidv4()
this.headers = { Authorization: 'Basic ' + btoa(`${this.username}:${this.password}`) } this.headers = { Authorization: 'Basic ' + btoa(`${this.username}:${this.password}`) }
this.davClient = webdav.createClient(`${this.endpoint}/remote.php/dav/files/${this.username}`, { this.davClient = webdav.createClient(`${this.endpoint.href}remote.php/dav/files/${this.username}`, {
username: this.username, username: this.username,
password: this.password password: this.password
}) })
@ -150,7 +151,7 @@ export class NextcloudClient {
} }
private async shareFile(remoteFilePath: string): Promise<string> { private async shareFile(remoteFilePath: string): Promise<string> {
const url = this.endpoint + `/ocs/v2.php/apps/files_sharing/api/v1/shares` const url = `${this.endpoint.href}ocs/v2.php/apps/files_sharing/api/v1/shares`
const body = { const body = {
path: remoteFilePath, path: remoteFilePath,
shareType: 3, shareType: 3,
@ -174,7 +175,7 @@ export class NextcloudClient {
core.debug(`Match groups:\n${JSON.stringify(match?.groups)}`) core.debug(`Match groups:\n${JSON.stringify(match?.groups)}`)
const sharableUrl = (match?.groups || {})['share_url'] const sharableUrl = (match?.groups || {})['share_url']
if (!sharableUrl) { if (!sharableUrl) {
throw new Error('Failed to parse sharable URL.') throw new Error(`Failed to parse or find sharable URL:\n${result}`)
} }
return sharableUrl return sharableUrl