Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 17 additions & 1 deletion packages/cli/src/commands/deploy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
MaintenanceWindow, PrivateLocation, PrivateLocationCheckAssignment, PrivateLocationGroupAssignment,
Project, ProjectData,
Session, StatusPage, StatusPageService,
StatusPageV3Component, StatusPageV3AutomationRule,
StatusPageV3, StatusPageV3Component, StatusPageV3AutomationRule,
} from '../constructs/index.js'
import chalk from 'chalk'
import { splitConfigFilePath, getGitInformation } from '../services/util.js'
Expand Down Expand Up @@ -334,6 +334,7 @@ export default class Deploy extends AuthCommand {
)
if (!preview) {
this.style.actionSuccess()
await this.printSensitiveOutputs(data, project)
}
if (preview || output) {
this.log(this.formatPreview(data, project, verbose))
Expand Down Expand Up @@ -373,6 +374,21 @@ export default class Deploy extends AuthCommand {
}
}

private async printSensitiveOutputs (data: ProjectDeployResponse, project: Project): Promise<void> {
for (const change of data.diff.filter(({ sensitiveOutput }) => sensitiveOutput === 'status-page-password')) {
const construct = project.data[change.type as keyof ProjectData][change.logicalId]
if (!(construct instanceof StatusPageV3) || !change.physicalId) continue

try {
const { data: generated } = await api.statusPages.regeneratePasswordV3(String(change.physicalId))
this.log(chalk.yellow.bold(`Password for status page "${construct.name}" (shown once): ${generated.password}`))
this.log(chalk.yellow('Store this password securely. It cannot be retrieved again.'))
} catch {
this.warn(`Password protection was enabled for status page "${construct.name}", but the generated password could not be retrieved. Regenerate it in the Checkly web app.`)
}
}
}

private collectDeletions (previewData: ProjectDeployResponse): Array<{ resourceType: string, logicalId: string }> {
return (previewData?.diff ?? [])
.filter(change =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ const page: StatusPageV3Resource = {
description: 'All systems',
defaultTheme: 'DARK',
allowIndexing: false,
isPrivate: true,
}

const group: StatusPageV3ComponentResource = {
Expand Down Expand Up @@ -114,6 +115,7 @@ describe('StatusPageV3 codegen', () => {
expect(pageSource).toContain('description: \'All systems\'')
expect(pageSource).toContain('defaultTheme: \'DARK\'')
expect(pageSource).toContain('allowIndexing: false')
expect(pageSource).toContain('isPrivate: true')
expect(pageSource).not.toContain('cards')

const groupSource = sources['resources/status-pages/components/platform.check.ts']
Expand Down
2 changes: 2 additions & 0 deletions packages/cli/src/constructs/__tests__/status-page-v3.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ describe('StatusPageV3', () => {
defaultTheme: 'DARK',
termsOfServiceLink: 'https://acme.example/terms',
allowIndexing: false,
isPrivate: true,
})

expect(page.type).toBe('status-page')
Expand All @@ -34,6 +35,7 @@ describe('StatusPageV3', () => {
defaultTheme: 'DARK',
termsOfServiceLink: 'https://acme.example/terms',
allowIndexing: false,
isPrivate: true,
version: 3,
}))
expect(page.synthesize()).not.toHaveProperty('cards')
Expand Down
5 changes: 5 additions & 0 deletions packages/cli/src/constructs/status-page-v3-codegen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export interface StatusPageV3Resource {
footerText?: string | null
googleAnalyticsTag?: string | null
allowIndexing?: boolean | null
isPrivate?: boolean | null
}

const construct = 'StatusPageV3'
Expand Down Expand Up @@ -138,6 +139,10 @@ export class StatusPageV3Codegen extends Codegen<StatusPageV3Resource> {
if (resource.allowIndexing === false) {
builder.boolean('allowIndexing', false)
}

if (resource.isPrivate === true) {
builder.boolean('isPrivate', true)
}
})
})
}))
Expand Down
8 changes: 8 additions & 0 deletions packages/cli/src/constructs/status-page-v3.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,11 @@ export interface StatusPageV3Props {
* Whether search engines may index the public page. Defaults to true.
*/
allowIndexing?: boolean
/**
* Protect the status page with a generated password. The password is shown
* once after the deployment that enables protection.
*/
isPrivate?: boolean
}

/**
Expand Down Expand Up @@ -117,6 +122,7 @@ export class StatusPageV3 extends Construct {
footerText?: string
googleAnalyticsTag?: string
allowIndexing?: boolean
isPrivate?: boolean

// Same resource type as the v2 page: both live in one table and are told
// apart by the `version` discriminator synthesized below.
Expand Down Expand Up @@ -146,6 +152,7 @@ export class StatusPageV3 extends Construct {
this.footerText = props.footerText
this.googleAnalyticsTag = props.googleAnalyticsTag
this.allowIndexing = props.allowIndexing
this.isPrivate = props.isPrivate

Session.registerConstruct(this)
}
Expand Down Expand Up @@ -177,6 +184,7 @@ export class StatusPageV3 extends Construct {
footerText: this.footerText,
googleAnalyticsTag: this.googleAnalyticsTag,
allowIndexing: this.allowIndexing,
isPrivate: this.isPrivate,
version: 3,
}
}
Expand Down
1 change: 1 addition & 0 deletions packages/cli/src/rest/projects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export interface Change {
physicalId?: string | number
type: string
action: string
sensitiveOutput?: 'status-page-password'
}

export interface ResourceSync {
Expand Down
8 changes: 8 additions & 0 deletions packages/cli/src/rest/status-pages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ export interface PaginatedStatusPages {
length: number
}

export interface GeneratedStatusPagePassword {
password: string
}

class StatusPages {
api: AxiosInstance
constructor (api: AxiosInstance) {
Expand All @@ -52,6 +56,10 @@ class StatusPages {
const response = await this.api.get<StatusPage>(`/v1/status-pages/${id}`)
return response.data
}

regeneratePasswordV3 (id: string) {
return this.api.post<GeneratedStatusPagePassword>(`/v3/status-pages/${id}/password`)
}
}

export default StatusPages
Loading