diff --git a/.github/workflows/deploy.yaml b/.github/workflows/deploy.yaml
index 549bf81..f92e6ec 100644
--- a/.github/workflows/deploy.yaml
+++ b/.github/workflows/deploy.yaml
@@ -1,4 +1,4 @@
-name: Docker hub
+name: Build & deploy
on:
push:
tags:
diff --git a/README.md b/README.md
index 49cbb0d..3085305 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+# Allure Docker Deploy
+
+---
+
![Deployment](https://github.com/cybersokari/allure-docker-deploy/actions/workflows/deploy.yaml/badge.svg?branch=main)
![](https://img.shields.io/docker/pulls/sokari/allure-docker-deploy)
-# Allure Docker Deploy
-_An easy-to-use Docker solution for sharing and backing up Allure test reports_
+
+_An easy-to-use serverless Docker solution for sharing and backing up Allure test reports_
This [Docker image](https://hub.docker.com/r/sokari/allure-docker-deploy) lets you share [Allure test reports](https://allurereport.org/) seamlessly via an ephemeral URL. It also backs up all report history and retries to Firebase Cloud Storage, enabling unique URLs for each test run and previewing previous reports.
diff --git a/assets/allure-logo.png b/assets/allure-logo.png
new file mode 100644
index 0000000..0e420e2
Binary files /dev/null and b/assets/allure-logo.png differ
diff --git a/assets/docker-logo.png b/assets/docker-logo.png
new file mode 100644
index 0000000..600476c
Binary files /dev/null and b/assets/docker-logo.png differ
diff --git a/assets/firebase-logo.png b/assets/firebase-logo.png
new file mode 100644
index 0000000..4cfb7da
Binary files /dev/null and b/assets/firebase-logo.png differ
diff --git a/worker/app/cloud-storage.ts b/worker/app/cloud-storage.ts
index 0c164e1..13913ac 100644
--- a/worker/app/cloud-storage.ts
+++ b/worker/app/cloud-storage.ts
@@ -34,7 +34,7 @@ export class CloudStorage {
validation: process.env.DEBUG !== 'true',
destination: `${storageHomeDir}/${destinationFilePath}`,
});
- counter.incrementFilesUploaded()
+ await counter.incrementFilesUploaded()
} catch (error) {
console.error(`Failed to upload ${filePath}:`, error);
}
diff --git a/worker/app/counter.ts b/worker/app/counter.ts
index b0a4b4a..d36f6c6 100644
--- a/worker/app/counter.ts
+++ b/worker/app/counter.ts
@@ -1,14 +1,24 @@
+import { Mutex } from 'async-mutex';
class Counter {
private startTime: number | null = null;
private processed = 0
private uploaded = 0
- incrementFilesProcessed() {
- this.processed ++
+ // Mutex for protecting critical sections
+ private mutex = new Mutex();
+
+ async incrementFilesProcessed(): Promise {
+ await this.mutex.runExclusive(() => {
+ this.processed++;
+ });
}
- incrementFilesUploaded() {
- this.uploaded++
+
+ async incrementFilesUploaded(): Promise {
+ await this.mutex.runExclusive(() => {
+ this.uploaded++;
+ });
}
+
get filesUploaded(){
return this.uploaded
}
diff --git a/worker/app/report-builder.ts b/worker/app/report-builder.ts
index a35adde..661bc8a 100644
--- a/worker/app/report-builder.ts
+++ b/worker/app/report-builder.ts
@@ -1,7 +1,7 @@
import {REPORTS_DIR, STAGING_PATH} from "../index";
const allure = require('allure-commandline')
-import {deploy} from "./site-builder";
+import {changePermissionsRecursively, createFirebaseJson, publishToFireBaseHosting} from "./site-builder";
import * as path from "node:path";
import * as fs from 'fs/promises'
import counter from "./counter";
@@ -17,10 +17,13 @@ class ReportBuilder {
public setTtl() {
clearTimeout(this.timeOut)
- this.timeOut = setTimeout(this.generateAndHost, this.ttl * 1000)
+ this.timeOut = setTimeout(async () => {
+ await this.generate()
+ return await this.host()
+ }, this.ttl * 1000)
}
- public async generateAndHost() {
+ public async generate() {
// History files can exist in reports directory in WATCH_MODE
// due to multiple call to generateAndHost, so we try to move
@@ -53,7 +56,7 @@ class ReportBuilder {
let success = false
await new Promise((resolve, reject) => {
generation.on('exit', async function (exitCode: number) {
- success = exitCode == 0
+ success = exitCode === 0
if (success) {
resolve('success')
} else {
@@ -62,14 +65,6 @@ class ReportBuilder {
}
})
})
- if (success){
- try {
- return await deploy()
- } catch (e) {
- console.log(`Hosting deployment failed: ${e}`)
- }
- }
- return null
}
// Move from '/allure-results' mount to staging
@@ -79,7 +74,7 @@ class ReportBuilder {
const destinationFilePath = path.join(STAGING_PATH, path.basename(file));
await fs.mkdir(path.dirname(destinationFilePath), {recursive: true});// recursive, don't throw
await fs.copyFile(file, destinationFilePath);
- counter.incrementFilesProcessed()
+ await counter.incrementFilesProcessed()
} catch (e) {
console.warn(`Failed to move ${path.basename(file)} to staging area: ${e}`)
}
@@ -88,6 +83,13 @@ class ReportBuilder {
return this
}
+ public async host() {
+ await createFirebaseJson()
+ // Grant execution permission to website files
+ await changePermissionsRecursively(REPORTS_DIR, 0o755)
+ return await publishToFireBaseHosting()
+ }
+
}
export default new ReportBuilder()
\ No newline at end of file
diff --git a/worker/app/site-builder.ts b/worker/app/site-builder.ts
index 5fae8bd..3cb392f 100644
--- a/worker/app/site-builder.ts
+++ b/worker/app/site-builder.ts
@@ -10,7 +10,7 @@ import counter from "./counter";
const exec = util.promisify(require('child_process').exec)
-async function createFirebaseJson() {
+export async function createFirebaseJson() {
const hosting = {
"hosting": {
"public": ".",
@@ -30,7 +30,7 @@ async function createFirebaseJson() {
}
}
-async function changePermissionsRecursively(dirPath: string, mode: fsSync.Mode) {
+export async function changePermissionsRecursively(dirPath: string, mode: fsSync.Mode) {
await fs.chmod(dirPath, mode);
const files = await fs.readdir(dirPath);
@@ -47,7 +47,7 @@ async function changePermissionsRecursively(dirPath: string, mode: fsSync.Mode)
}
}
-async function publishToFireBaseHosting() {
+export async function publishToFireBaseHosting() {
if (process.env.DEBUG === 'true') {
console.warn('DEBUG=true: Skipping live deployment')
return
@@ -118,12 +118,6 @@ export function writeGitHubSummary({summaryPath = '', url = ''}) {
}
}
-export async function deploy() {
- await createFirebaseJson()
- // Grant execution permission to website files
- await changePermissionsRecursively(REPORTS_DIR, 0o755)
- return await publishToFireBaseHosting()
-}
diff --git a/worker/app/util.ts b/worker/app/util.ts
index 79bd164..97db4fe 100644
--- a/worker/app/util.ts
+++ b/worker/app/util.ts
@@ -46,16 +46,19 @@ export async function getAllFiles(dirPath: string): Promise {
export function validateWebsiteExpires(expires: string): boolean {
// Check if input is empty
if (!expires) {
- console.error('Error: WEBSITE_EXPIRES cannot be empty');
return false;
}
+ if(expires.trim().length > 3){
+ return false;
+ }
+
+
// Regex to validate format: number followed by h/d/w
const validFormatRegex = /^(\d+)([hdw])$/;
const match = expires.match(validFormatRegex);
if (!match) {
- console.error('Error: Invalid WEBSITE_EXPIRES format. Use format like 12h, 7d, or 2w');
return false;
}
diff --git a/worker/index.ts b/worker/index.ts
index 26c0674..3b0fb08 100644
--- a/worker/index.ts
+++ b/worker/index.ts
@@ -73,7 +73,8 @@ function main(): void {
ReportBuilder.stageFiles(await getAllFiles(MOUNTED_PATH)),
cloudStorage?.stageRemoteFiles()
])
- const url = await ReportBuilder.generateAndHost()
+ await ReportBuilder.generate()
+ const url = await ReportBuilder.host()
if(keepHistory){
await cloudStorage?.uploadHistory()
}