-
Notifications
You must be signed in to change notification settings - Fork 79
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge remote-tracking branch 'origin/main' into urls-wait-for-all-ser…
…vices
- Loading branch information
Showing
14 changed files
with
251 additions
and
74 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -30,6 +30,7 @@ jobs: | |
cache: yarn | ||
|
||
- run: yarn | ||
- run: yarn build | ||
|
||
- name: Set NPM token | ||
env: | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -28,3 +28,5 @@ oclif.manifest.json | |
|
||
packages/*/dist | ||
packages/*/tsconfig.tsbuildinfo | ||
|
||
.history |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
24 changes: 24 additions & 0 deletions
24
packages/cli-common/src/lib/common-flags/tunnel-server-flags.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
import { Flags } from '@oclif/core' | ||
import { InferredFlags } from '@oclif/core/lib/interfaces' | ||
|
||
export const tunnelServerFlags = { | ||
'tunnel-url': Flags.string({ | ||
summary: 'Tunnel url, specify ssh://hostname[:port] or ssh+tls://hostname[:port]', | ||
char: 't', | ||
default: 'ssh+tls://livecycle.run' ?? process.env.PREVIEW_TUNNEL_OVERRIDE, | ||
}), | ||
'tls-hostname': Flags.string({ | ||
summary: 'Override TLS server name when tunneling via HTTPS', | ||
required: false, | ||
}), | ||
'insecure-skip-verify': Flags.boolean({ | ||
summary: 'Skip TLS or SSH certificate verification', | ||
default: false, | ||
}), | ||
} as const | ||
|
||
export const parseTunnelServerFlags = (flags: Omit<InferredFlags<typeof tunnelServerFlags>, 'json'>) => ({ | ||
url: flags['tunnel-url'], | ||
tlsServerName: flags['tls-hostname'], | ||
insecureSkipVerify: flags['insecure-skip-verify'], | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,144 @@ | ||
import { Flags, ux } from '@oclif/core' | ||
import { envIdFlags, parseTunnelServerFlags, text, tunnelServerFlags } from '@preevy/cli-common' | ||
import { TunnelOpts, addBaseComposeTunnelAgentService, findComposeTunnelAgentUrl, findEnvId, getTunnelNamesToServicePorts, getUserCredentials, jwtGenerator, profileStore, queryEnvMetadata, readMetadata } from '@preevy/core' | ||
import { tunnelNameResolver } from '@preevy/common' | ||
import { inspect } from 'util' | ||
import DriverCommand from '../../driver-command.js' | ||
import { connectToTunnelServerSsh } from '../../tunnel-server-client.js' | ||
|
||
type MetadataSource = 'agent' | 'driver' | ||
type UnknownMetadata = Record<string, unknown> | ||
|
||
// eslint-disable-next-line no-use-before-define | ||
export default class EnvMetadataCommand extends DriverCommand<typeof EnvMetadataCommand> { | ||
static description = 'Show metadata for a preview environment' | ||
static enableJsonFlag = true | ||
|
||
static flags = { | ||
...envIdFlags, | ||
...tunnelServerFlags, | ||
source: Flags.custom<'driver' | 'agent'>({ | ||
summary: 'Show metadata from the driver, the agent, or the driver if the agent is not available', | ||
default: ['agent', 'driver'], | ||
multiple: true, | ||
delimiter: ',', | ||
multipleNonGreedy: true, | ||
})(), | ||
'fetch-timeout': Flags.integer({ | ||
default: 2500, | ||
summary: 'Timeout for fetching metadata from the agent in milliseconds', | ||
}), | ||
} as const | ||
|
||
async getComposeTunnelAgentUrl( | ||
envId: string, | ||
tunnelOpts: TunnelOpts, | ||
tunnelingKey: string | Buffer, | ||
) { | ||
const expectedTunnels = getTunnelNamesToServicePorts( | ||
addBaseComposeTunnelAgentService({ name: '' }), | ||
tunnelNameResolver({ envId }), | ||
) | ||
|
||
const { client: tunnelServerSshClient } = await connectToTunnelServerSsh({ | ||
tunnelOpts, | ||
profileStore: profileStore(this.store), | ||
tunnelingKey, | ||
log: this.logger, | ||
}) | ||
|
||
try { | ||
const expectedTunnelUrls = await tunnelServerSshClient.execTunnelUrl(Object.keys(expectedTunnels)) | ||
|
||
const expectedServiceUrls = Object.entries(expectedTunnels) | ||
.map(([tunnel, { name, port }]) => ({ name, port, url: expectedTunnelUrls[tunnel] })) | ||
|
||
return findComposeTunnelAgentUrl(expectedServiceUrls) | ||
} finally { | ||
void tunnelServerSshClient.end() | ||
} | ||
} | ||
|
||
#envId: string | undefined | ||
async envId() { | ||
if (!this.#envId) { | ||
const { flags } = this | ||
this.#envId = await findEnvId({ | ||
userSpecifiedEnvId: flags.id, | ||
userSpecifiedProjectName: flags.project, | ||
userModel: () => this.ensureUserModel(), | ||
log: this.logger, | ||
}) | ||
} | ||
return this.#envId | ||
} | ||
|
||
async getMetadataFromDriver() { | ||
return await this.withConnection(await this.envId(), readMetadata) | ||
} | ||
|
||
async getMetadataFromAgent() { | ||
const pStore = profileStore(this.store).ref | ||
const tunnelingKey = await pStore.tunnelingKey() | ||
const composeTunnelServiceUrl = await this.getComposeTunnelAgentUrl( | ||
await this.envId(), | ||
parseTunnelServerFlags(this.flags), | ||
tunnelingKey, | ||
) | ||
const credentials = await getUserCredentials(jwtGenerator(tunnelingKey)) | ||
// eslint-disable-next-line @typescript-eslint/return-await | ||
return await queryEnvMetadata({ | ||
composeTunnelServiceUrl, | ||
credentials, | ||
fetchTimeout: this.flags['fetch-timeout'], | ||
retryOpts: { retries: 2 }, | ||
}) | ||
} | ||
|
||
metadataFactories: Record<MetadataSource, () => Promise<UnknownMetadata>> = { | ||
driver: this.getMetadataFromDriver.bind(this), | ||
agent: this.getMetadataFromAgent.bind(this), | ||
} | ||
|
||
async getMetatdata() { | ||
const { flags: { source: sources } } = this | ||
const errors: { source: MetadataSource; error: unknown }[] = [] | ||
for (const source of sources) { | ||
try { | ||
this.logger.debug(`Fetching metadata from ${source}`) | ||
return { | ||
// eslint-disable-next-line no-await-in-loop | ||
metadata: await this.metadataFactories[source](), | ||
errors, | ||
source, | ||
} | ||
} catch (err) { | ||
errors.push({ source, error: err }) | ||
} | ||
} | ||
|
||
return { errors } | ||
} | ||
|
||
async run(): Promise<unknown> { | ||
const { metadata, source: metadataSource, errors } = await this.getMetatdata() | ||
|
||
if (!metadata) { | ||
throw new Error(`Could not get metadata: ${inspect(errors)}`) | ||
} | ||
|
||
if (errors.length) { | ||
for (const { source: errorSource, error } of errors) { | ||
this.logger.warn(`Error fetching metadata from ${errorSource}: ${error}`) | ||
} | ||
} | ||
|
||
if (this.jsonEnabled()) { | ||
return { ...metadata, _source: metadataSource } | ||
} | ||
|
||
this.logger.info(`Metadata from ${text.code(metadataSource)}`) | ||
this.logger.info(inspect(metadata, { depth: null, colors: text.supportsColor !== false })) | ||
return undefined | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.