Skip to content
Merged
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
51 changes: 51 additions & 0 deletions packages/cli/src/commands/pg/connection-pooling/attach.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import {color} from '@heroku-cli/color'
import {Command, flags} from '@heroku-cli/command'
import {Args, ux} from '@oclif/core'
import * as Heroku from '@heroku-cli/schema'
import tsheredoc from 'tsheredoc'
import {essentialPlan} from '../../../lib/pg/util.js'
import {utils} from '@heroku/heroku-cli-util'
import {nls} from '../../../nls.js'

const heredoc = tsheredoc.default

export default class Attach extends Command {
static topic = 'pg'
static description = 'add an attachment to a database using connection pooling'
static examples = [heredoc`
$ heroku pg:connection-pooling:attach postgresql-something-12345
`]

static flags = {
as: flags.string({description: 'name for add-on attachment'}),
app: flags.app({required: true}),
remote: flags.remote(),
}

static args = {
database: Args.string({description: `${nls('pg:database:arg:description')} ${nls('pg:database:arg:description:default:suffix')}`}),
}

public async run(): Promise<void> {
const {flags, args} = await this.parse(Attach)
const {app} = flags
const dbResolver = new utils.pg.DatabaseResolver(this.heroku)
const {addon: db} = await dbResolver.getAttachment(app, args.database)

if (essentialPlan(db))
ux.error('You can’t perform this operation on Essential-tier databases.')

ux.action.start(`Enabling Connection Pooling on ${color.yellow(db.name)} to ${color.magenta(app)}`)
const {body: attachment} = await this.heroku.post<Required<Heroku.AddOnAttachment>>(`/client/v11/databases/${encodeURIComponent(db.name)}/connection-pooling`, {
body: {name: flags.as, credential: 'default', app}, hostname: utils.pg.host(),
})
ux.action.stop()

ux.action.start(`Setting ${color.cyan(attachment.name)} config vars and restarting ${color.magenta(app)}`)
const {body: releases} = await this.heroku.get<Required<Heroku.Release>[]>(
`/apps/${app}/releases`,
{partial: true, headers: {Range: 'version ..; max=1, order=desc'}},
)
ux.action.stop(`done, v${releases[0].version}`)
}
}
46 changes: 46 additions & 0 deletions packages/cli/src/commands/pg/credentials/create.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import {color} from '@heroku-cli/color'
import {Command, flags} from '@heroku-cli/command'
import {Args, ux} from '@oclif/core'
import tsheredoc from 'tsheredoc'
import {utils} from '@heroku/heroku-cli-util'
import {essentialPlan} from '../../../lib/pg/util.js'
import {nls} from '../../../nls.js'

const heredoc = tsheredoc.default

export default class Create extends Command {
static topic = 'pg'
static description = 'create credential within database\nExample:\n\n heroku pg:credentials:create postgresql-something-12345 --name new-cred-name\n'
static flags = {
name: flags.string({char: 'n', required: true, description: 'name of the new credential within the database'}),
app: flags.app({required: true}),
remote: flags.remote(),
}

static args = {
database: Args.string({description: `${nls('pg:database:arg:description')} ${nls('pg:database:arg:description:default:suffix')}`}),
}

public async run(): Promise<void> {
const {flags, args} = await this.parse(Create)
const {app, name} = flags
const dbResolver = new utils.pg.DatabaseResolver(this.heroku)
const {addon: db} = await dbResolver.getAttachment(app, args.database)
if (essentialPlan(db)) {
throw new Error("You can't create a custom credential on Essential-tier databases.")
}

const data = {name}
ux.action.start(`Creating credential ${color.cyan.bold(name)}`)

await this.heroku.post(`/postgres/v0/databases/${db.name}/credentials`, {hostname: utils.pg.host(), body: data})
ux.action.stop()

const attachCmd = `heroku addons:attach ${db.name} --credential ${name} -a ${app}`
const psqlCmd = `heroku pg:psql ${db.name} -a ${app}`
ux.stdout(heredoc(`

Please attach the credential to the apps you want to use it in by running ${color.cyan.bold(attachCmd)}.
Please define the new grants for the credential within Postgres: ${color.cyan.bold(psqlCmd)}.`))
}
}
Original file line number Diff line number Diff line change
@@ -1,27 +1,26 @@
/*
import color from '@heroku-cli/color'
import {color} from '@heroku-cli/color'
import {Command, flags} from '@heroku-cli/command'
import {Args, ux} from '@oclif/core'
import * as Heroku from '@heroku-cli/schema'
import {essentialPlan} from '../../../lib/pg/util'
import {essentialPlan} from '../../../lib/pg/util.js'
import {utils} from '@heroku/heroku-cli-util'
import confirmCommand from '../../../lib/confirmCommand'
import {nls} from '../../../nls'
import ConfirmCommand from '../../../lib/confirmCommand.js'
import {nls} from '../../../nls.js'

export default class Destroy extends Command {
static topic = 'pg';
static description = 'destroy credential within database';
static example = '$ heroku pg:credentials:destroy postgresql-transparent-56874 --name cred-name -a woodstock-production';
static topic = 'pg'
static description = 'destroy credential within database'
static example = '$ heroku pg:credentials:destroy postgresql-transparent-56874 --name cred-name -a woodstock-production'
static flags = {
name: flags.string({char: 'n', required: true, description: 'unique identifier for the credential'}),
confirm: flags.string({char: 'c'}),
confirm: flags.string({char: 'c', description: 'set to app name to bypass confirm prompt'}),
app: flags.app({required: true}),
remote: flags.remote(),
};
}

static args = {
database: Args.string({description: `${nls('pg:database:arg:description')} ${nls('pg:database:arg:description:default:suffix')}`}),
};
}

public async run(): Promise<void> {
const {flags, args} = await this.parse(Destroy)
Expand All @@ -44,12 +43,11 @@ export default class Destroy extends Command {
throw new Error(`Credential ${name} must be detached from the app${credAttachmentApps.length > 1 ? 's' : ''} ${credAttachmentApps.map(appName => color.app(appName || ''))
.join(', ')} before destroying.`)

await confirmCommand(app, confirm)
await new ConfirmCommand().confirm(app, confirm)
ux.action.start(`Destroying credential ${color.cyan.bold(name)}`)
await this.heroku.delete(`/postgres/v0/databases/${db.name}/credentials/${encodeURIComponent(name)}`, {hostname: utils.pg.host()})
ux.action.stop()
ux.log(`The credential has been destroyed within ${db.name}.`)
ux.log(`Database objects owned by ${name} will be assigned to the default credential.`)
ux.stdout(`The credential has been destroyed within ${db.name}.`)
ux.stdout(`Database objects owned by ${name} will be assigned to the default credential.`)
}
}
*/
Original file line number Diff line number Diff line change
@@ -1,25 +1,26 @@
/*
import {Command, flags} from '@heroku-cli/command'
import {Args, ux} from '@oclif/core'
import {utils} from '@heroku/heroku-cli-util'
import {essentialPlan} from '../../../lib/pg/util'
import confirmCommand from '../../../lib/confirmCommand'
import heredoc from 'tsheredoc'
import {nls} from '../../../nls'
import {essentialPlan} from '../../../lib/pg/util.js'
import ConfirmCommand from '../../../lib/confirmCommand.js'
import tsheredoc from 'tsheredoc'
import {nls} from '../../../nls.js'

const heredoc = tsheredoc.default

export default class RepairDefault extends Command {
static topic = 'pg';
static description = 'repair the permissions of the default credential within database';
static example = '$ heroku pg:credentials:repair-default postgresql-something-12345';
static topic = 'pg'
static description = 'repair the permissions of the default credential within database'
static example = '$ heroku pg:credentials:repair-default postgresql-something-12345'
static flags = {
confirm: flags.string({char: 'c'}),
confirm: flags.string({char: 'c', description: 'set to app name to bypass confirm prompt'}),
app: flags.app({required: true}),
remote: flags.remote(),
};
}

static args = {
database: Args.string({description: `${nls('pg:database:arg:description')} ${nls('pg:database:arg:description:default:suffix')}`}),
};
}

public async run(): Promise<void> {
const {flags, args} = await this.parse(RepairDefault)
Expand All @@ -29,7 +30,7 @@ export default class RepairDefault extends Command {
const {addon: db} = await dbResolver.getAttachment(app, database)
if (essentialPlan(db))
throw new Error("You can't perform this operation on Essential-tier databases.")
await confirmCommand(app, confirm, heredoc(`
await new ConfirmCommand().confirm(app, confirm, heredoc(`
Destructive Action
Ownership of all database objects owned by additional credentials will be transferred to the default credential.
This command will also grant the default credential admin option for all additional credentials.
Expand All @@ -39,4 +40,3 @@ export default class RepairDefault extends Command {
ux.action.stop()
}
}
*/
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
/*
import color from '@heroku-cli/color'
import {color} from '@heroku-cli/color'
import {Command, flags} from '@heroku-cli/command'
import {APIClient} from '@heroku-cli/command/lib/api-client'
import {APIClient} from '@heroku-cli/command'
import type {AddOnAttachment} from '@heroku-cli/schema'
import {Args, ux} from '@oclif/core'
import confirmCommand from '../../../lib/confirmCommand'
import ConfirmCommand from '../../../lib/confirmCommand.js'
import {utils} from '@heroku/heroku-cli-util'
import {legacyEssentialPlan} from '../../../lib/pg/util'
import {nls} from '../../../nls'
import {nls} from '../../../nls.js'

export default class Rotate extends Command {
static topic = 'pg'
Expand All @@ -18,7 +16,7 @@ export default class Rotate extends Command {
description: 'which credential to rotate (default credentials if not specified and --all is not used)',
}),
all: flags.boolean({description: 'rotate all credentials', exclusive: ['name']}),
confirm: flags.string({char: 'c'}),
confirm: flags.string({char: 'c', description: 'set to app name to bypass confirm prompt'}),
force: flags.boolean({description: 'forces rotating the targeted credentials'}),
app: flags.app({required: true}),
remote: flags.remote(),
Expand All @@ -39,7 +37,7 @@ export default class Rotate extends Command {
throw new Error('cannot pass both --all and --name')
}

if (legacyEssentialPlan(db) && cred !== 'default') {
if (utils.pg.isLegacyEssentialDatabase(db) && cred !== 'default') {
throw new Error('Legacy Essential-tier databases do not support named credentials.')
}

Expand Down Expand Up @@ -74,7 +72,7 @@ export default class Rotate extends Command {
warnings.push(`This command will affect the app${(attachments.length > 1) ? 's' : ''} ${uniqueAttachments}.`)
}

await confirmCommand(app, confirm, `Destructive Action\n${warnings.join('\n')}`)
await new ConfirmCommand().confirm(app, confirm, `Destructive Action\n${warnings.join('\n')}`)
const options: APIClient.Options = {
hostname: utils.pg.host(),
body: {forced: force ?? undefined},
Expand All @@ -93,4 +91,3 @@ export default class Rotate extends Command {
ux.action.stop()
}
}
*/
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
/*
import color from '@heroku-cli/color'
import {color} from '@heroku-cli/color'
import {Command, flags} from '@heroku-cli/command'
import {Args, ux} from '@oclif/core'
import {legacyEssentialPlan} from '../../../lib/pg/util'
import {utils} from '@heroku/heroku-cli-util'
import {URL} from 'url'
import type {CredentialInfo} from '../../../lib/pg/types'
import heredoc from 'tsheredoc'
import {nls} from '../../../nls'
import type {CredentialInfo} from '../../../lib/pg/types.js'
import tsheredoc from 'tsheredoc'
import {nls} from '../../../nls.js'

const heredoc = tsheredoc.default

export default class Url extends Command {
static topic = 'pg'
Expand All @@ -32,7 +32,7 @@ export default class Url extends Command {
const {database} = args
const dbResolver = new utils.pg.DatabaseResolver(this.heroku)
const {addon: db} = await dbResolver.getAttachment(app, database)
if (legacyEssentialPlan(db) && name !== 'default') {
if (utils.pg.isLegacyEssentialDatabase(db) && name !== 'default') {
ux.error('Legacy Essential-tier databases do not support named credentials.')
}

Expand Down Expand Up @@ -62,7 +62,7 @@ export default class Url extends Command {
connUrl.password = creds.password
}

ux.log(heredoc(`
ux.stdout(heredoc(`
Connection information for ${color.yellow(name)} credential.
Connection info string:
"dbname=${creds.database} host=${creds.host} port=${creds.port} user=${creds.user} password=${creds.password} sslmode=require"
Expand All @@ -71,4 +71,3 @@ export default class Url extends Command {
`))
}
}
*/
7 changes: 5 additions & 2 deletions packages/cli/src/lib/pg/util.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import {color} from '@heroku-cli/color'
import type {AddOnAttachment} from '@heroku-cli/schema'
import {hux} from '@heroku/heroku-cli-util'
import {hux, utils, pg} from '@heroku/heroku-cli-util'
import {renderAttachment} from '../../commands/addons/index.js'
import {multiSortCompareFn} from '../utils/multisort.js'
import type {CredentialsInfo} from './types.js'
import {utils} from '@heroku/heroku-cli-util'

export function essentialPlan(addon: pg.ExtendedAddonAttachment['addon'] | pg.ExtendedAddon) {
return utils.pg.isEssentialDatabase(addon) || utils.pg.isLegacyEssentialDatabase(addon)
}

export function formatResponseWithCommands(response: string): string {
return response.replace(/`(.*?)`/g, (_, word) => color.cmd(word))
Expand Down
51 changes: 0 additions & 51 deletions packages/cli/src/oldCommands/pg/connection-pooling/attach.ts

This file was deleted.

Loading
Loading