-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
12 changed files
with
1,303 additions
and
4 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
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,36 @@ | ||
import { Disposables } from '../src/lib/disposables'; | ||
|
||
/** | ||
* A utility function that wraps the main logic with proper signal handling and cleanup. | ||
* It ensures that disposables are cleaned up properly when the process is interrupted. | ||
* | ||
* @param fn The main function that receives disposables and returns a promise | ||
* @returns A promise that resolves to the result of the function | ||
*/ | ||
export async function withCleanup<T>(fn: (disposables: Disposables) => Promise<T>): Promise<T> { | ||
let disposablesCleanup: (() => Promise<void>) | undefined; | ||
|
||
// Setup signal handlers for cleanup | ||
const signalHandler = async (signal: NodeJS.Signals) => { | ||
console.log(`\nReceived ${signal}. Cleaning up...`); | ||
if (disposablesCleanup) { | ||
await disposablesCleanup(); | ||
} | ||
process.exit(0); | ||
}; | ||
|
||
process.on('SIGINT', signalHandler); | ||
process.on('SIGTERM', signalHandler); | ||
process.on('SIGQUIT', signalHandler); | ||
|
||
try { | ||
return await Disposables.with(async (disposables) => { | ||
// Store cleanup function for signal handlers | ||
disposablesCleanup = () => disposables.cleanup(); | ||
return await fn(disposables); | ||
}); | ||
} catch (error) { | ||
console.error('Error:', error); | ||
process.exit(1); | ||
} | ||
} |
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,162 @@ | ||
import { Gitpod } from '../src/client'; | ||
import { findMostUsedEnvironmentClass, EnvironmentState } from '../src/lib/environment'; | ||
import { EnvironmentSpec } from '../src/resources/environments/environments'; | ||
import { verifyContextUrl } from './scm-auth'; | ||
import { generateKeyPairSync } from 'crypto'; | ||
import { Client, SFTPWrapper } from 'ssh2'; | ||
import { withCleanup } from './cleanup'; | ||
import * as sshpk from 'sshpk'; | ||
|
||
/** | ||
* Examples: | ||
* - yarn ts-node examples/fs-access.ts | ||
* - yarn ts-node examples/fs-access.ts https://github.com/gitpod-io/empty | ||
*/ | ||
async function main() { | ||
const contextUrl = process.argv[2]; | ||
|
||
await withCleanup(async (disposables) => { | ||
const client = new Gitpod({ | ||
logLevel: 'info', | ||
}); | ||
|
||
const envClass = await findMostUsedEnvironmentClass(client); | ||
if (!envClass) { | ||
console.error('Error: No environment class found. Please create one first.'); | ||
process.exit(1); | ||
} | ||
console.log(`Found environment class: ${envClass.displayName} (${envClass.description})`); | ||
|
||
console.log('Generating SSH key pair'); | ||
const { publicKey: pemPublicKey, privateKey: pemPrivateKey } = generateKeyPairSync('rsa', { | ||
modulusLength: 2048, | ||
publicKeyEncoding: { | ||
type: 'spki', | ||
format: 'pem', | ||
}, | ||
privateKeyEncoding: { | ||
type: 'pkcs8', | ||
format: 'pem', | ||
}, | ||
}); | ||
|
||
// Convert PEM keys to OpenSSH format | ||
const keyObject = sshpk.parseKey(pemPublicKey, 'pem'); | ||
const publicKey = keyObject.toString('ssh'); | ||
|
||
const privateKeyObject = sshpk.parsePrivateKey(pemPrivateKey, 'pem'); | ||
const privateKey = privateKeyObject.toString('ssh'); | ||
|
||
console.log('Creating environment with SSH access'); | ||
const keyId = 'fs-access-example'; | ||
const spec: EnvironmentSpec = { | ||
desiredPhase: 'ENVIRONMENT_PHASE_RUNNING', | ||
machine: { class: envClass.id }, | ||
sshPublicKeys: [ | ||
{ | ||
id: keyId, | ||
value: publicKey, | ||
}, | ||
], | ||
}; | ||
|
||
if (contextUrl) { | ||
await verifyContextUrl(client, contextUrl, envClass.runnerId); | ||
spec.content = { | ||
initializer: { | ||
specs: [ | ||
{ | ||
contextUrl: { | ||
url: contextUrl, | ||
}, | ||
}, | ||
], | ||
}, | ||
}; | ||
} | ||
|
||
console.log('Creating environment'); | ||
const { environment } = await client.environments.create({ spec }); | ||
disposables.add(() => client.environments.delete({ environmentId: environment.id })); | ||
|
||
const env = new EnvironmentState(client, environment.id); | ||
disposables.add(() => env.close()); | ||
|
||
console.log('Waiting for environment to be running'); | ||
await env.waitUntilRunning(); | ||
|
||
console.log('Waiting for SSH key to be applied'); | ||
await env.waitForSshKeyApplied(keyId, publicKey); | ||
|
||
console.log('Waiting for SSH URL'); | ||
const sshUrl = await env.waitForSshUrl(); | ||
|
||
console.log(`Setting up SSH connection to ${sshUrl}`); | ||
// Parse ssh://username@host:port format | ||
const urlParts = sshUrl.split('://')[1]; | ||
if (!urlParts) { | ||
throw new Error('Invalid SSH URL format'); | ||
} | ||
|
||
const [username, rest] = urlParts.split('@'); | ||
if (!username || !rest) { | ||
throw new Error('Invalid SSH URL format: missing username or host'); | ||
} | ||
|
||
const [host, portStr] = rest.split(':'); | ||
if (!host || !portStr) { | ||
throw new Error('Invalid SSH URL format: missing host or port'); | ||
} | ||
|
||
const port = parseInt(portStr, 10); | ||
if (isNaN(port)) { | ||
throw new Error('Invalid SSH URL format: invalid port number'); | ||
} | ||
|
||
const ssh = new Client(); | ||
disposables.add(() => ssh.end()); | ||
|
||
await new Promise<void>((resolve, reject) => { | ||
ssh.on('ready', resolve); | ||
ssh.on('error', reject); | ||
|
||
ssh.connect({ | ||
host, | ||
port, | ||
username, | ||
privateKey, | ||
}); | ||
}); | ||
|
||
console.log('Creating SFTP client'); | ||
const sftp = await new Promise<SFTPWrapper>((resolve, reject) => { | ||
ssh.sftp((err, sftp) => { | ||
if (err) reject(err); | ||
else resolve(sftp); | ||
}); | ||
}); | ||
disposables.add(() => sftp.end()); | ||
|
||
console.log('Writing test file'); | ||
const testContent = 'Hello from Gitpod TypeScript SDK!'; | ||
await new Promise<void>((resolve, reject) => { | ||
sftp.writeFile('test.txt', Buffer.from(testContent), (err) => { | ||
if (err) reject(err); | ||
else resolve(); | ||
}); | ||
}); | ||
|
||
const content = await new Promise<string>((resolve, reject) => { | ||
sftp.readFile('test.txt', (err, data) => { | ||
if (err) reject(err); | ||
else resolve(data.toString()); | ||
}); | ||
}); | ||
console.log(`File content: ${content}`); | ||
}); | ||
} | ||
|
||
main().catch((error) => { | ||
console.error('Error:', error); | ||
process.exit(1); | ||
}); |
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,73 @@ | ||
import { Gitpod } from '../src/client'; | ||
import { findMostUsedEnvironmentClass, waitForEnvironmentRunning } from '../src/lib/environment'; | ||
import { runCommand } from '../src/lib/automation'; | ||
import { EnvironmentSpec } from '../src/resources/environments/environments'; | ||
import { verifyContextUrl } from './scm-auth'; | ||
import { withCleanup } from './cleanup'; | ||
|
||
/** | ||
* Examples: | ||
* - yarn ts-node examples/run-command.ts 'echo "Hello World!"' | ||
* - yarn ts-node examples/run-command.ts 'echo "Hello World!"' https://github.com/gitpod-io/empty | ||
*/ | ||
async function main() { | ||
const args = process.argv.slice(2); | ||
if (args.length < 1) { | ||
console.log('Usage: yarn ts-node examples/run-command.ts "<COMMAND>" [CONTEXT_URL]'); | ||
process.exit(1); | ||
} | ||
|
||
const command = args[0]; | ||
const contextUrl = args[1]; | ||
|
||
await withCleanup(async (disposables) => { | ||
const client = new Gitpod({ | ||
logLevel: 'info', | ||
}); | ||
|
||
const envClass = await findMostUsedEnvironmentClass(client); | ||
if (!envClass) { | ||
console.error('Error: No environment class found. Please create one first.'); | ||
process.exit(1); | ||
} | ||
console.log(`Found environment class: ${envClass.displayName} (${envClass.description})`); | ||
|
||
const spec: EnvironmentSpec = { | ||
desiredPhase: 'ENVIRONMENT_PHASE_RUNNING', | ||
machine: { class: envClass.id }, | ||
}; | ||
|
||
if (contextUrl) { | ||
await verifyContextUrl(client, contextUrl, envClass.runnerId); | ||
spec.content = { | ||
initializer: { | ||
specs: [ | ||
{ | ||
contextUrl: { | ||
url: contextUrl, | ||
}, | ||
}, | ||
], | ||
}, | ||
}; | ||
} | ||
|
||
console.log('Creating environment'); | ||
const { environment } = await client.environments.create({ spec }); | ||
disposables.add(() => client.environments.delete({ environmentId: environment.id })); | ||
|
||
console.log('Waiting for environment to be ready'); | ||
await waitForEnvironmentRunning(client, environment.id); | ||
|
||
console.log('Running command'); | ||
const lines = await runCommand(client, environment.id, command!); | ||
for await (const line of lines) { | ||
console.log(line); | ||
} | ||
}); | ||
} | ||
|
||
main().catch((error) => { | ||
console.error('Error:', error); | ||
process.exit(1); | ||
}); |
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,94 @@ | ||
import { Gitpod } from '../src/client'; | ||
import { findMostUsedEnvironmentClass, EnvironmentState } from '../src/lib/environment'; | ||
import { runService } from '../src/lib/automation'; | ||
import { EnvironmentSpec } from '../src/resources/environments/environments'; | ||
import { verifyContextUrl } from './scm-auth'; | ||
import { withCleanup } from './cleanup'; | ||
|
||
/** | ||
* Examples: | ||
* - yarn ts-node examples/run-service.ts | ||
* - yarn ts-node examples/run-service.ts https://github.com/gitpod-io/empty | ||
*/ | ||
async function main() { | ||
const contextUrl = process.argv[2]; | ||
|
||
await withCleanup(async (disposables) => { | ||
const client = new Gitpod({ | ||
logLevel: 'info', | ||
}); | ||
|
||
const envClass = await findMostUsedEnvironmentClass(client); | ||
if (!envClass) { | ||
console.error('Error: No environment class found. Please create one first.'); | ||
process.exit(1); | ||
} | ||
console.log(`Found environment class: ${envClass.displayName} (${envClass.description})`); | ||
|
||
const port = 8888; | ||
const spec: EnvironmentSpec = { | ||
desiredPhase: 'ENVIRONMENT_PHASE_RUNNING', | ||
machine: { class: envClass.id }, | ||
ports: [ | ||
{ | ||
name: 'Lama Service', | ||
port, | ||
admission: 'ADMISSION_LEVEL_EVERYONE', | ||
}, | ||
], | ||
}; | ||
|
||
if (contextUrl) { | ||
await verifyContextUrl(client, contextUrl, envClass.runnerId); | ||
spec.content = { | ||
initializer: { | ||
specs: [ | ||
{ | ||
contextUrl: { | ||
url: contextUrl, | ||
}, | ||
}, | ||
], | ||
}, | ||
}; | ||
} | ||
|
||
console.log('Creating environment'); | ||
const { environment } = await client.environments.create({ spec }); | ||
disposables.add(() => client.environments.delete({ environmentId: environment.id })); | ||
|
||
console.log('Waiting for environment to be ready'); | ||
const env = new EnvironmentState(client, environment.id); | ||
disposables.add(() => env.close()); | ||
await env.waitUntilRunning(); | ||
|
||
console.log('Starting Lama Service'); | ||
const lines = await runService( | ||
client, | ||
environment.id, | ||
{ | ||
name: 'Lama Service', | ||
description: 'Lama Service', | ||
reference: 'lama-service', | ||
}, | ||
{ | ||
commands: { | ||
start: `curl lama.sh | LAMA_PORT=${port} sh`, | ||
ready: `curl -s http://localhost:${port}`, | ||
}, | ||
}, | ||
); | ||
|
||
const portUrl = await env.waitForPortUrl(port); | ||
console.log(`Lama Service is running at ${portUrl}`); | ||
|
||
for await (const line of lines) { | ||
console.log(line); | ||
} | ||
}); | ||
} | ||
|
||
main().catch((error) => { | ||
console.error('Error:', error); | ||
process.exit(1); | ||
}); |
Oops, something went wrong.