Skip to content

Commit 87d8af7

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

File tree

11 files changed

+1201
-4
lines changed

11 files changed

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

examples/run-command.ts

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
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+
26+
const envClass = await findMostUsedEnvironmentClass(client);
27+
if (!envClass) {
28+
console.error('Error: No environment class found. Please create one first.');
29+
process.exit(1);
30+
}
31+
console.log(`Found environment class: ${envClass.displayName} (${envClass.description})`);
32+
33+
const spec: EnvironmentSpec = {
34+
desiredPhase: 'ENVIRONMENT_PHASE_RUNNING',
35+
machine: { class: envClass.id },
36+
};
37+
38+
if (contextUrl) {
39+
await verifyContextUrl(client, contextUrl, envClass.runnerId);
40+
spec.content = {
41+
initializer: {
42+
specs: [
43+
{
44+
contextUrl: {
45+
url: contextUrl,
46+
},
47+
},
48+
],
49+
},
50+
};
51+
}
52+
53+
console.log('Creating environment');
54+
const { environment } = await client.environments.create({ spec });
55+
disposables.add(() => client.environments.delete({ environmentId: environment.id }));
56+
57+
console.log('Waiting for environment to be ready');
58+
await waitForEnvironmentRunning(client, environment.id);
59+
60+
console.log('Running command');
61+
if (command) {
62+
for await (const line of runCommand(client, environment.id, command)) {
63+
console.log(line);
64+
}
65+
}
66+
});
67+
}
68+
69+
main().catch((error) => {
70+
console.error('Error:', error);
71+
process.exit(1);
72+
});

examples/run-service.ts

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
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+
19+
const envClass = await findMostUsedEnvironmentClass(client);
20+
if (!envClass) {
21+
console.error('Error: No environment class found. Please create one first.');
22+
process.exit(1);
23+
}
24+
console.log(`Found environment class: ${envClass.displayName} (${envClass.description})`);
25+
26+
const port = 8888;
27+
const spec: EnvironmentSpec = {
28+
desiredPhase: 'ENVIRONMENT_PHASE_RUNNING',
29+
machine: { class: envClass.id },
30+
ports: [
31+
{
32+
name: 'Lama Service',
33+
port,
34+
admission: 'ADMISSION_LEVEL_EVERYONE',
35+
},
36+
],
37+
};
38+
39+
if (contextUrl) {
40+
await verifyContextUrl(client, contextUrl, envClass.runnerId);
41+
spec.content = {
42+
initializer: {
43+
specs: [
44+
{
45+
contextUrl: {
46+
url: contextUrl,
47+
},
48+
},
49+
],
50+
},
51+
};
52+
}
53+
54+
console.log('Creating environment');
55+
const { environment } = await client.environments.create({ spec });
56+
disposables.add(() => client.environments.delete({ environmentId: environment.id }));
57+
58+
console.log('Waiting for environment to be ready');
59+
const env = new EnvironmentState(client, environment.id);
60+
disposables.add(() => env.close());
61+
await env.waitUntilRunning();
62+
63+
console.log('Starting Lama Service');
64+
const lines = runService(
65+
client,
66+
environment.id,
67+
{
68+
name: 'Lama Service',
69+
description: 'Lama Service',
70+
reference: 'lama-service',
71+
},
72+
{
73+
commands: {
74+
start: `curl lama.sh | LAMA_PORT=${port} sh`,
75+
ready: `curl -s http://localhost:${port}`,
76+
},
77+
},
78+
);
79+
80+
const portUrl = await env.waitForPortUrl(port);
81+
console.log(`Lama Service is running at ${portUrl}`);
82+
83+
for await (const line of lines) {
84+
console.log(line);
85+
}
86+
});
87+
}
88+
89+
main().catch((error) => {
90+
console.error('Error:', error);
91+
process.exit(1);
92+
});

0 commit comments

Comments
 (0)