Skip to content

Commit

Permalink
Allow to deploy and alias when non essential APIs are down (#1435)
Browse files Browse the repository at this point in the history
* Try to purchase a domain only when there is no other choice

* Allow eventsStream to fail during deployment

* Allow to verify instantiation without events API
  • Loading branch information
javivelasco authored and rauchg committed Jul 3, 2018
1 parent 63e51a3 commit 59be596
Show file tree
Hide file tree
Showing 4 changed files with 119 additions and 82 deletions.
24 changes: 8 additions & 16 deletions src/providers/sh/commands/alias/assign-alias.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import deploymentShouldDowscale from './deployment-should-dowscale'
import fetchDeploymentFromAlias from './get-deployment-from-alias'
import getDeploymentDownscalePresets from './get-deployment-downscale-presets'
import getPreviousAlias from './get-previous-alias'
import purchaseDomainIfAvailable from './purchase-domain-if-available'
import setupDomain from './setup-domain'

// $FlowFixMe
Expand Down Expand Up @@ -47,29 +46,22 @@ async function assignAlias(output: Output, now: Now, deployment: Deployment, ali
// Check if the alias is a custom domain and if case we have a positive
// we have to configure the DNS records and certificate
if (alias.indexOf('.') !== -1 && !NOW_SH_REGEX.test(alias)) {
// In case the domain is avilable, we have to purchase
const purchased = await purchaseDomainIfAvailable(output, now, alias, contextName)
if (
(purchased instanceof Errors.DomainNotFound) ||
(purchased instanceof Errors.InvalidCoupon) ||
(purchased instanceof Errors.MissingCreditCard) ||
(purchased instanceof Errors.PaymentSourceNotFound) ||
(purchased instanceof Errors.UnsupportedTLD) ||
(purchased instanceof Errors.UsedCoupon) ||
(purchased instanceof Errors.UserAborted)
) {
return purchased
}

// Now the domain shouldn't be available and it might or might not belong to the user
const result = await setupDomain(output, now, alias, contextName)
if (
(result instanceof Errors.DNSPermissionDenied) ||
(result instanceof Errors.DomainNameserversNotFound) ||
(result instanceof Errors.DomainNotFound) ||
(result instanceof Errors.DomainNotVerified) ||
(result instanceof Errors.DomainPermissionDenied) ||
(result instanceof Errors.DomainVerificationFailed) ||
(result instanceof Errors.NeedUpgrade)
(result instanceof Errors.InvalidCoupon) ||
(result instanceof Errors.MissingCreditCard) ||
(result instanceof Errors.NeedUpgrade) ||
(result instanceof Errors.PaymentSourceNotFound) ||
(result instanceof Errors.UnsupportedTLD) ||
(result instanceof Errors.UsedCoupon) ||
(result instanceof Errors.UserAborted)
) {
return result
}
Expand Down
46 changes: 21 additions & 25 deletions src/providers/sh/commands/alias/set.js
Original file line number Diff line number Diff line change
Expand Up @@ -120,11 +120,16 @@ export default async function set(ctx: CLIContext, opts: CLIAliasOptions, args:
export type SetupDomainError =
Errors.DNSPermissionDenied |
Errors.DomainNameserversNotFound |
Errors.DomainNotFound |
Errors.DomainNotVerified |
Errors.DomainPermissionDenied |
Errors.DomainVerificationFailed |
Errors.InvalidCoupon |
Errors.MissingCreditCard |
Errors.NeedUpgrade |
Errors.PaymentSourceNotFound |
Errors.UnsupportedTLD |
Errors.UsedCoupon |
Errors.UserAborted

function handleSetupDomainErrorImpl<Other>(output: Output, error: SetupDomainError | Other): 1 | Other {
Expand Down Expand Up @@ -160,6 +165,22 @@ function handleSetupDomainErrorImpl<Other>(output: Output, error: SetupDomainErr
output.error(`You don't have permissions to access the DNS records for ${chalk.underline(error.meta.domain)}`)
return 1
} else if (error instanceof Errors.UserAborted) {
output.error(`User aborted`);
return 1
} else if (error instanceof Errors.DomainNotFound) {
output.error(`You should buy the domain before aliasing.`)
return 1
} else if (error instanceof Errors.InvalidCoupon) {
output.error(`The provided coupon ${error.meta.coupon} is invalid.`)
return 1
} else if (error instanceof Errors.MissingCreditCard) {
output.print('You have no credit cards on file. Please add one to purchase the domain.')
return 1
} else if (error instanceof Errors.UnsupportedTLD) {
output.error(`The TLD for domain name ${error.meta.name} is not supported.`)
return 1
} else if (error instanceof Errors.UsedCoupon) {
output.error(`The provided coupon ${error.meta.coupon} can't be used.`)
return 1
} else {
return error
Expand All @@ -171,20 +192,15 @@ type CreateAliasError =
Errors.DeploymentNotFound |
Errors.DeploymentPermissionDenied |
Errors.DomainConfigurationError |
Errors.DomainNotFound |
Errors.DomainPermissionDenied |
Errors.DomainsShouldShareRoot |
Errors.DomainValidationRunning |
Errors.InvalidAlias |
Errors.InvalidCoupon |
Errors.InvalidWildcardDomain |
Errors.MissingCreditCard |
Errors.NeedUpgrade |
Errors.RuleValidationFailed |
Errors.TooManyCertificates |
Errors.TooManyRequests |
Errors.UnsupportedTLD |
Errors.UsedCoupon |
Errors.VerifyScaleTimeout

function handleCreateAliasErrorImpl<OtherError>(output: Output, error: CreateAliasError | OtherError): 1 | OtherError {
Expand Down Expand Up @@ -233,36 +249,16 @@ function handleCreateAliasErrorImpl<OtherError>(output: Output, error: CreateAli
} else if (error instanceof Errors.TooManyRequests) {
output.error(`Too many requests detected for ${error.meta.api} API. Try again later.`)
return 1
} else if (error instanceof Errors.DomainNotFound) {
output.error(`You should buy the domain before aliasing.`)
return 1
} else if (error instanceof Errors.VerifyScaleTimeout) {
output.error(`Instance verification timed out (${ms(error.meta.timeout)})`)
output.log('Read more: https://err.sh/now-cli/verification-timeout')
return 1
} else if (error instanceof Errors.InvalidWildcardDomain) {
// this should never happen
output.error(`Invalid domain ${chalk.underline(error.meta.domain)}. Wildcard domains can only be followed by a root domain.`)
return 1
} else if (error instanceof Errors.DomainsShouldShareRoot) {
// this should never happen either
output.error(`All given common names should share the same root domain.`)
return 1
} else if (error instanceof Errors.InvalidCoupon) {
// this should never happen
output.error(`The provided coupon ${error.meta.coupon} is invalid.`)
return 1
} else if (error instanceof Errors.UsedCoupon) {
// this should never happen
output.error(`The provided coupon ${error.meta.coupon} can't be used.`)
return 1
} else if (error instanceof Errors.UnsupportedTLD) {
// this should never happen
output.error(`The TLD for domain name ${error.meta.name} is not supported.`)
return 1
} else if (error instanceof Errors.MissingCreditCard) {
output.print('You have no credit cards on file. Please add one to purchase the domain.')
return 1
} else {
return error
}
Expand Down
80 changes: 54 additions & 26 deletions src/providers/sh/commands/alias/setup-domain.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import psl from 'psl'
import getDomainInfo from './get-domain-info'
import getDomainNameservers from './get-domain-nameservers'
import maybeSetupDNSRecords from './maybe-setup-dns-records'
import purchaseDomainIfAvailable from './purchase-domain-if-available'
import verifyDomain from '../../util/domains/verify-domain'

// Types and errors
Expand All @@ -21,49 +22,76 @@ async function setupDomain(output: Output, now: Now, alias: string, contextName:

if (!info) {
output.debug(`Domain is unknown for ZEIT World`)
// If we have no info it means that it's an unknown domain. We have to check the
// nameservers to register and verify it as an external or non-external domain
const nameservers = await getDomainNameservers(now, domain)
if (nameservers instanceof Errors.DomainNameserversNotFound) {
return nameservers
}

output.log(
`Nameservers: ${nameservers && nameservers.length
? nameservers.map(ns => chalk.underline(ns)).join(', ')
: chalk.dim('none')}`
)
// If we find nameservers we have to try to add the domain
if (!(nameservers instanceof Errors.DomainNameserversNotFound)) {
output.log(
`Nameservers: ${nameservers && nameservers.length
? nameservers.map(ns => chalk.underline(ns)).join(', ')
: chalk.dim('none')}`
)

if (!nameservers.every(ns => ns.endsWith('.zeit.world'))) {
// If it doesn't have the nameserver pointing to now we have to create the
// domain knowing that it should be verified via a DNS TXT record.
const verified = await verifyDomain(now, alias, contextName, { isExternal: true })
const domainPointsToZeitWorld = !nameservers.every(ns => ns.endsWith('.zeit.world'));
const verified = await verifyDomain(now, alias, contextName, { isExternal: !domainPointsToZeitWorld })
if (
(verified instanceof Errors.DomainNotVerified) ||
(verified instanceof Errors.DomainPermissionDenied) ||
(verified instanceof Errors.DomainVerificationFailed) ||
(verified instanceof Errors.NeedUpgrade)
) {
return verified
} if (verified instanceof Errors.DomainVerificationFailed) {
// Verification fails when the domain is external so either its missing the TXT record
// or it's available to purchase, so we try to purchase it
const purchased = await purchaseDomainIfAvailable(output, now, alias, contextName)
if (
(purchased instanceof Errors.DomainNotFound) ||
(purchased instanceof Errors.InvalidCoupon) ||
(purchased instanceof Errors.MissingCreditCard) ||
(purchased instanceof Errors.PaymentSourceNotFound) ||
(purchased instanceof Errors.UnsupportedTLD) ||
(purchased instanceof Errors.UsedCoupon) ||
(purchased instanceof Errors.UserAborted)
) {
return purchased
}

if (purchased) {
const result = await maybeSetupDNSRecords(output, now, domain, subdomain)
if ((result instanceof Errors.DNSPermissionDenied)) {
return result
}
} else {
// If the domain was not available, return the verification error
return verified;
}
} else {
output.success(`Domain ${domain} added!`)
}

// If the domain was pointing to zeit world we always try to configure the DNS
if (domainPointsToZeitWorld) {
const result = await maybeSetupDNSRecords(output, now, domain, subdomain)
if ((result instanceof Errors.DNSPermissionDenied)) {
return result
}
}
} else {
// We have to create the domain knowing that the nameservers are zeit.world
output.debug(`Detected ${chalk.bold(chalk.underline('zeit.world'))} nameservers! Setting up domain...`)
const verified = await verifyDomain(now, alias, contextName, { isExternal: false })
// If we couldn't find nameservers we try to purchase the domain
const purchased = await purchaseDomainIfAvailable(output, now, alias, contextName)
if (
(verified instanceof Errors.DomainNotVerified) ||
(verified instanceof Errors.DomainPermissionDenied) ||
(verified instanceof Errors.DomainVerificationFailed) ||
(verified instanceof Errors.NeedUpgrade)
(purchased instanceof Errors.DomainNotFound) ||
(purchased instanceof Errors.InvalidCoupon) ||
(purchased instanceof Errors.MissingCreditCard) ||
(purchased instanceof Errors.PaymentSourceNotFound) ||
(purchased instanceof Errors.UnsupportedTLD) ||
(purchased instanceof Errors.UsedCoupon) ||
(purchased instanceof Errors.UserAborted)
) {
return verified
} else {
output.success(`Domain ${domain} added!`)
return purchased
}

// Since it's pointing to our nameservers we can configure the DNS records
// Since the domain was purchased we now try to configure the dns records.
const result = await maybeSetupDNSRecords(output, now, domain, subdomain)
if ((result instanceof Errors.DNSPermissionDenied)) {
return result
Expand Down
51 changes: 36 additions & 15 deletions src/providers/sh/commands/deploy/deploy.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ import stamp from '../../../../util/output/stamp'
import verifyDeploymentScale from '../../util/scale/verify-deployment-scale'
import verifyDeploymentShallow from '../../util/deploy/verify-deployment-shallow'
import zeitWorldTable from '../../util/zeit-world-table'
import type { Readable } from 'stream'
import type { NewDeployment, DeploymentEvent } from '../../util/types'
import type { CreateDeployError } from '../../util/deploy/create-deploy'

Expand Down Expand Up @@ -920,11 +921,8 @@ async function sync({ contextName, output, token, config: { currentTeam, user },
if (deployment.readyState !== 'READY') {
require('assert')(deployment) // mute linter
const instanceIndex = getInstanceIndex()
const eventsStream = await getEventsStream(now, deployment.deploymentId, { direction: 'forward', follow: true })
const eventsGenerator: AsyncGenerator<DeploymentEvent, void, void> = combineAsyncGenerators(
eventListenerToGenerator('data', eventsStream),
getStateChangeFromPolling(now, contextName, deployment.deploymentId, deployment.readyState)
)
const eventsStream = await maybeGetEventsStream(now, deployment)
const eventsGenerator = getEventsGenerator(now, contextName, deployment, eventsStream)

for await (const event of eventsGenerator) {
// Stop when the deployment is ready
Expand Down Expand Up @@ -952,15 +950,7 @@ async function sync({ contextName, output, token, config: { currentTeam, user },
if (!noVerify) {
output.log(`Verifying instantiation in ${joinWords(Object.keys(deployment.scale).map(dc => chalk.bold(dc)))}`)
const verifyStamp = stamp()

const verifyDeployment = deployment.blob === null
? verifyDeploymentShallow(output, now, deployment.url, deployment.scale)
: verifyDeploymentScale(output, now, deployment.deploymentId, deployment.scale)

const verifyDCsGenerator: AsyncGenerator<DeploymentEvent | [string, number], Errors.VerifyScaleTimeout, void> = raceAsyncGenerators(
eventListenerToGenerator('data', eventsStream),
verifyDeployment
)
const verifyDCsGenerator = getVerifyDCsGenerator(output, now, deployment, eventsStream)

for await (const dcOrEvent of verifyDCsGenerator) {
if (dcOrEvent instanceof Errors.VerifyScaleTimeout) {
Expand Down Expand Up @@ -1043,7 +1033,35 @@ function getScaleFromConfig(config = {}): Object {
return config.scale || {}
}

module.exports = main
async function maybeGetEventsStream(now: Now, deployment: NewDeployment) {
try {
return await getEventsStream(now, deployment.deploymentId, { direction: 'forward', follow: true })
} catch (error) {
return null
}
}

function getEventsGenerator(now: Now, contextName: string, deployment: NewDeployment, eventsStream: null | Readable): AsyncGenerator<DeploymentEvent, void, void> {
const stateChangeFromPollingGenerator = getStateChangeFromPolling(now, contextName, deployment.deploymentId, deployment.readyState)
if (eventsStream !== null) {
return combineAsyncGenerators(
eventListenerToGenerator('data', eventsStream),
stateChangeFromPollingGenerator
);
}

return stateChangeFromPollingGenerator
}

function getVerifyDCsGenerator(output: Output, now: Now, deployment: NewDeployment, eventsStream: Readable | null) {
const verifyDeployment = deployment.blob === null
? verifyDeploymentShallow(output, now, deployment.url, deployment.scale)
: verifyDeploymentScale(output, now, deployment.deploymentId, deployment.scale)

return eventsStream
? raceAsyncGenerators(eventListenerToGenerator('data', eventsStream), verifyDeployment)
: verifyDeployment
}

function handleCreateDeployError<OtherError>(output: Output, error: CreateDeployError | OtherError): 1 | OtherError {
if (error instanceof Errors.CantGenerateWildcardCert) {
Expand Down Expand Up @@ -1114,3 +1132,6 @@ function handleCreateDeployError<OtherError>(output: Output, error: CreateDeploy

return error
}

module.exports = main

0 comments on commit 59be596

Please sign in to comment.