Skip to content

Commit c86c957

Browse files
committed
feat: add lib and examples
1 parent f9e2a50 commit c86c957

File tree

11 files changed

+1207
-4
lines changed

11 files changed

+1207
-4
lines changed

.devcontainer/devcontainer.json

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,12 @@
66
"features": {
77
"ghcr.io/devcontainers/features/node:1": {}
88
},
9-
"postCreateCommand": "yarn install"
9+
"postCreateCommand": "yarn install",
10+
"customizations": {
11+
"vscode": {
12+
"extensions": [
13+
"esbenp.prettier-vscode"
14+
]
15+
}
16+
}
1017
}

examples/fs-access.ts

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
import { Gitpod } from '../src/client';
2+
import { Disposables } from '../src/lib/disposables';
3+
import { findMostUsedEnvironmentClass, EnvironmentState } from '../src/lib/environment';
4+
import { EnvironmentSpec } from '../src/resources/environments/environments';
5+
import { verifyContextUrl } from './scm-auth';
6+
import { generateKeyPair } from 'crypto';
7+
import { Client, SFTPWrapper } from 'ssh2';
8+
import { promisify } from 'util';
9+
10+
const generateKeyPairAsync = promisify(generateKeyPair);
11+
12+
/**
13+
* Examples:
14+
* - yarn ts-node examples/fs-access.ts
15+
* - yarn ts-node examples/fs-access.ts https://github.com/gitpod-io/empty
16+
*/
17+
async function main() {
18+
const contextUrl = process.argv[2];
19+
20+
return Disposables.with(async (disposables) => {
21+
const client = new Gitpod({
22+
logLevel: 'info',
23+
});
24+
25+
const envClass = await findMostUsedEnvironmentClass(client);
26+
if (!envClass) {
27+
console.error('Error: No environment class found. Please create one first.');
28+
process.exit(1);
29+
}
30+
console.log(`Found environment class: ${envClass.displayName} (${envClass.description})`);
31+
32+
console.log('Generating SSH key pair');
33+
const { publicKey, privateKey } = await generateKeyPairAsync('rsa', {
34+
modulusLength: 2048,
35+
publicKeyEncoding: {
36+
type: 'spki',
37+
format: 'pem',
38+
},
39+
privateKeyEncoding: {
40+
type: 'pkcs8',
41+
format: 'pem',
42+
},
43+
});
44+
45+
// Convert PEM public key to SSH format
46+
const sshPublicKey = publicKey
47+
.toString()
48+
.replace(/^-----BEGIN PUBLIC KEY-----\n/, '')
49+
.replace(/\n-----END PUBLIC KEY-----\n?$/, '')
50+
.replace(/\n/g, '');
51+
const sshPublicKeyFull = `ssh-rsa ${sshPublicKey}`;
52+
53+
console.log('Creating environment with SSH access');
54+
const keyId = 'fs-access-example';
55+
const spec: EnvironmentSpec = {
56+
desiredPhase: 'ENVIRONMENT_PHASE_RUNNING',
57+
machine: { class: envClass.id },
58+
sshPublicKeys: [
59+
{
60+
id: keyId,
61+
value: sshPublicKeyFull,
62+
},
63+
],
64+
};
65+
66+
if (contextUrl) {
67+
await verifyContextUrl(client, contextUrl, envClass.runnerId);
68+
spec.content = {
69+
initializer: {
70+
specs: [
71+
{
72+
contextUrl: {
73+
url: contextUrl,
74+
},
75+
},
76+
],
77+
},
78+
};
79+
}
80+
81+
console.log('Creating environment');
82+
const { environment } = await client.environments.create({ spec });
83+
disposables.add(() => client.environments.delete({ environmentId: environment.id }));
84+
85+
const env = new EnvironmentState(client, environment.id);
86+
disposables.add(() => env.close());
87+
88+
console.log('Waiting for environment to be running');
89+
await env.waitUntilRunning();
90+
91+
console.log('Waiting for SSH key to be applied');
92+
await env.waitForSshKeyApplied(keyId, sshPublicKeyFull);
93+
94+
console.log('Waiting for SSH URL');
95+
const sshUrl = await env.waitForSshUrl();
96+
97+
console.log(`Setting up SSH connection to ${sshUrl}`);
98+
// Parse ssh://username@host:port format
99+
const urlParts = sshUrl.split('://')[1];
100+
if (!urlParts) {
101+
throw new Error('Invalid SSH URL format');
102+
}
103+
104+
const [username, rest] = urlParts.split('@');
105+
if (!username || !rest) {
106+
throw new Error('Invalid SSH URL format: missing username or host');
107+
}
108+
109+
const [host, portStr] = rest.split(':');
110+
if (!host || !portStr) {
111+
throw new Error('Invalid SSH URL format: missing host or port');
112+
}
113+
114+
const port = parseInt(portStr, 10);
115+
if (isNaN(port)) {
116+
throw new Error('Invalid SSH URL format: invalid port number');
117+
}
118+
119+
const ssh = new Client();
120+
disposables.add(() => ssh.end());
121+
122+
await new Promise<void>((resolve, reject) => {
123+
ssh.on('ready', resolve);
124+
ssh.on('error', reject);
125+
126+
ssh.connect({
127+
host,
128+
port,
129+
username,
130+
privateKey,
131+
});
132+
});
133+
134+
console.log('Creating SFTP client');
135+
const sftp = await new Promise<SFTPWrapper>((resolve, reject) => {
136+
ssh.sftp((err, sftp) => {
137+
if (err) reject(err);
138+
else resolve(sftp);
139+
});
140+
});
141+
disposables.add(() => sftp.end());
142+
143+
console.log('Writing test file');
144+
const testContent = 'Hello from Gitpod TypeScript SDK!';
145+
await new Promise<void>((resolve, reject) => {
146+
sftp.writeFile('test.txt', Buffer.from(testContent), (err) => {
147+
if (err) reject(err);
148+
else resolve();
149+
});
150+
});
151+
152+
const content = await new Promise<string>((resolve, reject) => {
153+
sftp.readFile('test.txt', (err, data) => {
154+
if (err) reject(err);
155+
else resolve(data.toString());
156+
});
157+
});
158+
console.log(`File content: ${content}`);
159+
});
160+
}
161+
162+
if (require.main === module) {
163+
main().catch((error) => {
164+
console.error('Error:', error);
165+
process.exit(1);
166+
});
167+
}

examples/run-command.ts

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import { Gitpod } from '../src/client';
2+
import { Disposables } from '../src/lib/disposables';
3+
import { findMostUsedEnvironmentClass, waitForEnvironmentRunning } from '../src/lib/environment';
4+
import { runCommand } from '../src/lib/automation';
5+
import { EnvironmentSpec } from '../src/resources/environments/environments';
6+
import { verifyContextUrl } from './scm-auth';
7+
8+
/**
9+
* Examples:
10+
* - yarn ts-node examples/run-command.ts 'echo "Hello World!"'
11+
* - yarn ts-node examples/run-command.ts 'echo "Hello World!"' https://github.com/gitpod-io/empty
12+
*/
13+
async function main() {
14+
const args = process.argv.slice(2);
15+
if (args.length < 1) {
16+
console.log('Usage: yarn ts-node examples/run-command.ts "<COMMAND>" [CONTEXT_URL]');
17+
process.exit(1);
18+
}
19+
20+
const command = args[0];
21+
const contextUrl = args[1];
22+
23+
return Disposables.with(async (disposables) => {
24+
const client = new Gitpod({
25+
logLevel: 'info',
26+
});
27+
28+
const envClass = await findMostUsedEnvironmentClass(client);
29+
if (!envClass) {
30+
console.error('Error: No environment class found. Please create one first.');
31+
process.exit(1);
32+
}
33+
console.log(`Found environment class: ${envClass.displayName} (${envClass.description})`);
34+
35+
const spec: EnvironmentSpec = {
36+
desiredPhase: 'ENVIRONMENT_PHASE_RUNNING',
37+
machine: { class: envClass.id },
38+
};
39+
40+
if (contextUrl) {
41+
await verifyContextUrl(client, contextUrl, envClass.runnerId);
42+
spec.content = {
43+
initializer: {
44+
specs: [
45+
{
46+
contextUrl: {
47+
url: contextUrl,
48+
},
49+
},
50+
],
51+
},
52+
};
53+
}
54+
55+
console.log('Creating environment');
56+
const { environment } = await client.environments.create({ spec });
57+
disposables.add(() => client.environments.delete({ environmentId: environment.id }));
58+
59+
console.log('Waiting for environment to be ready');
60+
await waitForEnvironmentRunning(client, environment.id);
61+
62+
console.log('Running command');
63+
const lines = await runCommand(client, environment.id, command!);
64+
for await (const line of lines) {
65+
console.log(line);
66+
}
67+
});
68+
}
69+
70+
main().catch((error) => {
71+
console.error('Error:', error);
72+
process.exit(1);
73+
});

examples/run-service.ts

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
import { Gitpod } from '../src/client';
2+
import { Disposables } from '../src/lib/disposables';
3+
import { findMostUsedEnvironmentClass, EnvironmentState } from '../src/lib/environment';
4+
import { runService } from '../src/lib/automation';
5+
import { EnvironmentSpec } from '../src/resources/environments/environments';
6+
import { verifyContextUrl } from './scm-auth';
7+
8+
/**
9+
* Examples:
10+
* - yarn ts-node examples/run-service.ts
11+
* - yarn ts-node examples/run-service.ts https://github.com/gitpod-io/empty
12+
*/
13+
async function main() {
14+
const contextUrl = process.argv[2];
15+
16+
return Disposables.with(async (disposables) => {
17+
const client = new Gitpod({
18+
logLevel: 'info',
19+
});
20+
21+
const envClass = await findMostUsedEnvironmentClass(client);
22+
if (!envClass) {
23+
console.error('Error: No environment class found. Please create one first.');
24+
process.exit(1);
25+
}
26+
console.log(`Found environment class: ${envClass.displayName} (${envClass.description})`);
27+
28+
const port = 8888;
29+
const spec: EnvironmentSpec = {
30+
desiredPhase: 'ENVIRONMENT_PHASE_RUNNING',
31+
machine: { class: envClass.id },
32+
ports: [
33+
{
34+
name: 'Lama Service',
35+
port,
36+
admission: 'ADMISSION_LEVEL_EVERYONE',
37+
},
38+
],
39+
};
40+
41+
if (contextUrl) {
42+
await verifyContextUrl(client, contextUrl, envClass.runnerId);
43+
spec.content = {
44+
initializer: {
45+
specs: [
46+
{
47+
contextUrl: {
48+
url: contextUrl,
49+
},
50+
},
51+
],
52+
},
53+
};
54+
}
55+
56+
console.log('Creating environment');
57+
const { environment } = await client.environments.create({ spec });
58+
disposables.add(() => client.environments.delete({ environmentId: environment.id }));
59+
60+
console.log('Waiting for environment to be ready');
61+
const env = new EnvironmentState(client, environment.id);
62+
disposables.add(() => env.close());
63+
await env.waitUntilRunning();
64+
65+
console.log('Starting Lama Service');
66+
const lines = await runService(
67+
client,
68+
environment.id,
69+
{
70+
name: 'Lama Service',
71+
description: 'Lama Service',
72+
reference: 'lama-service',
73+
},
74+
{
75+
commands: {
76+
start: `curl lama.sh | LAMA_PORT=${port} sh`,
77+
ready: `curl -s http://localhost:${port}`,
78+
},
79+
},
80+
);
81+
82+
const portUrl = await env.waitForPortUrl(port);
83+
console.log(`Lama Service is running at ${portUrl}`);
84+
85+
for await (const line of lines) {
86+
console.log(line);
87+
}
88+
});
89+
}
90+
91+
main().catch((error) => {
92+
console.error('Error:', error);
93+
process.exit(1);
94+
});

0 commit comments

Comments
 (0)