mirror of
https://github.com/trympet/nextcloud-artifacts-action.git
synced 2025-04-24 20:16:08 +02:00
more cleanup
This commit is contained in:
parent
2b05098faf
commit
7ca2f4d4fb
@ -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
84
dist/index.js
vendored
@ -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
2
dist/index.js.map
vendored
File diff suppressed because one or more lines are too long
@ -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) {
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
Loading…
x
Reference in New Issue
Block a user