Skip to content

Commit 299bff7

Browse files
committed
add execute command example
1 parent 4de6561 commit 299bff7

File tree

5 files changed

+187
-3
lines changed

5 files changed

+187
-3
lines changed

.devcontainer/Dockerfile

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
1-
ARG VARIANT="3.9"
2-
FROM mcr.microsoft.com/vscode/devcontainers/python:0-${VARIANT}
1+
FROM mcr.microsoft.com/devcontainers/python:3.12
32

43
USER vscode
54

65
RUN curl -sSf https://rye.astral.sh/get | RYE_VERSION="0.35.0" RYE_INSTALL_OPTION="--yes" bash
76
ENV PATH=/home/vscode/.rye/shims:$PATH
87

9-
RUN echo "[[ -d .venv ]] && source .venv/bin/activate" >> /home/vscode/.bashrc
8+
RUN echo "[[ -d .venv ]] && source .venv/bin/activate || export PATH=\$PATH" >> /home/vscode/.bashrc

examples/execute_command.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
#!/usr/bin/env python
2+
3+
import sys
4+
import asyncio
5+
from gitpod import Gitpod
6+
import gitpod.lib as util
7+
from gitpod.types.environment_create_params import SpecContentInitializerSpecContextURL
8+
9+
# Example: ./examples/execute_command.py https://github.com/gitpod-io/empty 'echo "Hello World!"'
10+
async def main() -> None:
11+
client = Gitpod()
12+
13+
if len(sys.argv) < 3:
14+
print("Usage: ./examples/execute_command.py <CONTEXT_URL> <COMMAND>")
15+
sys.exit(1)
16+
context_url = sys.argv[1]
17+
command = " ".join(sys.argv[2:])
18+
19+
env_class = util.find_most_used_environment_class(client)
20+
if not env_class:
21+
print("Error: No environment class found. Please create one first.")
22+
sys.exit(1)
23+
24+
environment_id = client.environments.create(
25+
spec={
26+
"desired_phase": "ENVIRONMENT_PHASE_RUNNING",
27+
"content": {
28+
"initializer": {"specs": [SpecContentInitializerSpecContextURL(context_url={"url": context_url})]}
29+
},
30+
"machine": {"class": env_class.id},
31+
}
32+
).environment.id
33+
34+
try:
35+
util.wait_for_environment_ready(client, environment_id)
36+
37+
output = await util.run_command(client, environment_id, command)
38+
print(output)
39+
finally:
40+
client.environments.delete(environment_id=environment_id)
41+
42+
if __name__ == "__main__":
43+
asyncio.run(main())

src/gitpod/lib/__init__.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
from .task import run_command
2+
from .environment import find_most_used_environment_class, wait_for_environment_ready
3+
4+
__all__ = [
5+
'find_most_used_environment_class',
6+
'wait_for_environment_ready',
7+
'run_command'
8+
]

src/gitpod/lib/environment.py

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
from typing import Optional
2+
3+
from gitpod import Gitpod
4+
from gitpod.types.environments.class_list_response import ClassListResponse
5+
6+
def find_most_used_environment_class(client: Gitpod) -> Optional[ClassListResponse]:
7+
"""
8+
Find the most used environment class.
9+
"""
10+
user = client.users.get_authenticated_user(body={}).user
11+
12+
class_usage = {}
13+
page = client.environments.list(organization_id=user.organization_id)
14+
while page:
15+
for env in page.environments:
16+
env_class = env.spec.machine.class_
17+
if env_class not in class_usage:
18+
class_usage[env_class] = 0
19+
class_usage[env_class] += 1
20+
if page.pagination and page.pagination.next_token:
21+
page = client.environments.list(token=page.pagination.next_token, organization_id=user.organization_id)
22+
else:
23+
break
24+
25+
sorted_classes = sorted(class_usage.items(), key=lambda item: -item[1])
26+
environment_class_id = sorted_classes[0][0] if sorted_classes else None
27+
if not environment_class_id:
28+
return None
29+
30+
page = client.environments.classes.list(filter={"enabled": True})
31+
while page:
32+
for cls in page.environment_classes:
33+
if cls.id == environment_class_id:
34+
return cls
35+
if page.pagination and page.pagination.next_token:
36+
page = client.environments.classes.list(token=page.pagination.next_token)
37+
else:
38+
break
39+
return None
40+
41+
42+
def wait_for_environment_ready(client: Gitpod, environment_id: str) -> None:
43+
def is_ready() -> bool:
44+
environment = client.environments.retrieve(environment_id=environment_id).environment
45+
if environment.status.phase in [
46+
"ENVIRONMENT_PHASE_STOPPING",
47+
"ENVIRONMENT_PHASE_STOPPED",
48+
"ENVIRONMENT_PHASE_DELETING",
49+
"ENVIRONMENT_PHASE_DELETED",
50+
]:
51+
raise RuntimeError(f"Environment {environment_id} is in an unexpected phase: {environment.status.phase}")
52+
elif environment.status.failure_message and len(environment.status.failure_message) > 0:
53+
raise RuntimeError(f"Environment {environment_id} failed: {'; '.join(environment.status.failure_message)}")
54+
return environment.status.phase == "ENVIRONMENT_PHASE_RUNNING"
55+
56+
event_stream = client.events.watch(environment_id=environment_id, timeout=None)
57+
try:
58+
if is_ready():
59+
return
60+
61+
for event in event_stream:
62+
if event.resource_type == "RESOURCE_TYPE_ENVIRONMENT" and event.resource_id == environment_id:
63+
if is_ready():
64+
return
65+
finally:
66+
event_stream.http_response.close()
67+

src/gitpod/lib/task.py

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import httpx
2+
3+
from gitpod import Gitpod
4+
TASK_REFERENCE = "gitpod-python-sdk"
5+
6+
async def run_command(client: Gitpod, environment_id: str, command: str) -> str:
7+
tasks = client.environments.automations.tasks.list(
8+
filter={
9+
"references": [TASK_REFERENCE],
10+
"environment_ids": [environment_id]
11+
}
12+
).tasks
13+
14+
task_id = ""
15+
if not tasks:
16+
task_id = client.environments.automations.tasks.create(
17+
spec={
18+
"command": command,
19+
},
20+
environment_id=environment_id,
21+
metadata={
22+
"name": "Gitpod Python SDK Task",
23+
"description": "Gitpod Python SDK Task",
24+
"reference": TASK_REFERENCE,
25+
},
26+
).task.id
27+
else:
28+
task_id = tasks[0].id
29+
client.environments.automations.tasks.update(
30+
id=task_id,
31+
spec={
32+
"command": command,
33+
},
34+
)
35+
36+
task_execution_id = client.environments.automations.tasks.start(id=task_id).task_execution.id
37+
log_url = wait_for_log_url(client, environment_id, task_execution_id)
38+
logs_access_token = client.environments.create_logs_token(environment_id=environment_id).access_token
39+
async with httpx.AsyncClient() as http_client:
40+
async with http_client.stream("GET", log_url, headers={"Authorization": f"Bearer {logs_access_token}"}) as response:
41+
chunks: list[str] = []
42+
async for chunk in response.aiter_text():
43+
chunks.append(chunk)
44+
return "".join(chunks)
45+
46+
def wait_for_log_url(client: Gitpod, environment_id: str, task_execution_id: str) -> str:
47+
def get_log_url() -> str:
48+
execution = client.environments.automations.tasks.executions.retrieve(id=task_execution_id).task_execution
49+
return execution.status.log_url
50+
51+
log_url = get_log_url()
52+
if log_url:
53+
return log_url
54+
55+
event_stream = client.events.watch(environment_id=environment_id, timeout=None)
56+
try:
57+
log_url = get_log_url()
58+
if log_url:
59+
return log_url
60+
61+
for event in event_stream:
62+
if event.resource_type == "RESOURCE_TYPE_TASK_EXECUTION" and event.resource_id == task_execution_id:
63+
log_url = get_log_url()
64+
if log_url:
65+
break
66+
finally:
67+
event_stream.http_response.close()

0 commit comments

Comments
 (0)