diff --git a/README.md b/README.md index 688be55..a879a26 100644 --- a/README.md +++ b/README.md @@ -233,6 +233,21 @@ Example: https://webhooks.example.com/2 ``` +#### `deployment-failure-emails` + +The `deployment-failure-emails` input parameter allows you to define up to 3 custom email addresses that Forge will notify of all deployment failures. To notify multiple email addresses, enter them on separate lines. + +Example: + +```yaml +- uses: bakerkretzmar/laravel-deploy-preview@v2 + with: + forge-token: ${{ secrets.FORGE_TOKEN }} + servers: | + qa-1.acme.dev 60041 + deployment-failure-email: failure@example.com +``` + ### Databases This action creates a new database for each preview site and deletes the database when the preview site is deleted. If your Forge server has one of the default supported database engines installed (MySQL, MariaDB, or PostgreSQL), that database engine will be used and no additional configuration is necessary. diff --git a/action.yml b/action.yml index e4bd582..527b1d1 100644 --- a/action.yml +++ b/action.yml @@ -47,6 +47,9 @@ inputs: deployment-webhooks: description: URLs to send webhooks to after Forge deployments. Enter multiple URLs on separate lines. required: false + deployment-failure-emails: + description: Email addresses to notify if the Forge deployment fails. Enter multiple emails on separate lines. + required: false outputs: site-url: description: The URL of the deployed preview site. diff --git a/src/action.ts b/src/action.ts index 4eb1347..912b12a 100644 --- a/src/action.ts +++ b/src/action.ts @@ -11,6 +11,7 @@ export async function createPreview({ certificate, name, webhooks, + failureEmails, aliases, isolated, username, @@ -24,6 +25,7 @@ export async function createPreview({ certificate?: { type: 'clone'; certificate: number } | { type: 'existing'; certificate: string; key: string } | false; name?: string; webhooks: string[]; + failureEmails?: string[]; aliases: string[]; isolated: boolean; username?: string; @@ -117,6 +119,16 @@ export async function createPreview({ core.info('Setting up webhooks.'); await Promise.all(webhooks.map((url) => site.createWebhook(url))); + if (failureEmails?.length) { + core.info('Setting up deployment failure notifications.'); + if (failureEmails.length > 3) { + core.warning( + `Only 3 emails can be notified of deployment failures, found ${failureEmails.length}. Only the first 3 will be used.`, + ); + } + await site.setDeploymentFailureEmails(failureEmails.slice(0, 3)); + } + core.info('Deploying site.'); await site.deploy(); diff --git a/src/forge.ts b/src/forge.ts index eb3fb47..073af15 100644 --- a/src/forge.ts +++ b/src/forge.ts @@ -243,6 +243,10 @@ export class Forge { .webhook; } + static async setDeploymentFailureEmails(server: number, site: number, emails: string[]) { + await this.post(`servers/${server}/sites/${site}/deployment-failure-emails`, { emails }); + } + static token(token: string) { this.#token = token; } @@ -264,7 +268,7 @@ export class Forge { if (this.#debug > 0) { console.log(`> ${config.method?.toUpperCase()} /${config.url}`); if (this.#debug > 1 && config.data) { - console.log(JSON.stringify(config.data, null, 2)); + console.log(config.data); } } return config; @@ -278,7 +282,7 @@ export class Forge { }`, ); if (this.#debug > 1 && response.data) { - console.log(JSON.stringify(response.data, null, 2)); + console.log(response.data); } } return response; @@ -299,6 +303,16 @@ export class Forge { const [, server, site] = error.response.config.url.match(/servers\/(\d+)\/sites\/(\d+)/); detail = `A previously requested SSL certificate was not found. This may mean that automatically obtaining and installing a Let’s Encrypt certificate failed. Please review any error output in your Forge dashboard and then try again: https://forge.laravel.com/servers/${server}/sites/${site}.`; } + if (this.#debug > 0) { + console.log( + `< ${error.response?.config.method.toUpperCase()} /${error.response?.config.url} ${error.response?.status} ${ + error.response?.statusText + }`, + ); + if ((this.#debug > 1 || error.response?.status === 422) && error.response?.data) { + console.log(error.response.data); + } + } return Promise.reject(new ForgeError(error, detail)); }, ); @@ -448,6 +462,10 @@ export class Site { await Forge.createWebhook(this.server_id, this.id, url); } + async setDeploymentFailureEmails(emails: string[]) { + await Forge.setDeploymentFailureEmails(this.server_id, this.id, emails); + } + // TODO figure out a way to safely+reliably figure the name out internally so it doesn't need to be passed in // Environment file?? async deleteDatabase(name: string) { diff --git a/src/main.ts b/src/main.ts index dfbc379..028b8f0 100644 --- a/src/main.ts +++ b/src/main.ts @@ -43,6 +43,7 @@ export async function run() { const noCertificate = core.getBooleanInput('no-certificate', { required: false }); const webhooks = core.getMultilineInput('deployment-webhooks', { required: false }); + const failureEmails = core.getMultilineInput('deployment-failure-emails', { required: false }); let certificate: | { type: 'clone'; certificate: number } @@ -107,6 +108,7 @@ export async function run() { environment, certificate, webhooks, + failureEmails, aliases, isolated, username,