Skip to content

Commit 623a1d2

Browse files
authored
Merge pull request #71 from patronus-ai/main
Add git server environment
2 parents 2e47c79 + 3f52fb6 commit 623a1d2

File tree

16 files changed

+1478
-3
lines changed

16 files changed

+1478
-3
lines changed

.env.example

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# Copy this file to .env and customize as needed
2+
3+
# Gitea Service Configuration
4+
GITEA_URL=http://host.docker.internal:3000
5+
GITEA_USERNAME=gitea
6+
GITEA_PASSWORD=gitea123

.github/workflows/docker-build.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,8 @@ jobs:
7777
dockerfile: src/envs/sumo_rl_env/server/Dockerfile
7878
- name: atari-env
7979
dockerfile: src/envs/atari_env/server/Dockerfile
80+
- name: git-env
81+
dockerfile: src/envs/git_env/server/Dockerfile
8082

8183
steps:
8284
- name: Checkout code

examples/local_git_env.py

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
#!/usr/bin/env python3
2+
"""
3+
Simple test showing how users will use GitEnv.from_docker_image().
4+
5+
This is the simplest possible usage.
6+
7+
Prerequisites:
8+
1. .env file configured (copy from .env.example)
9+
2. Shared Gitea running: ./scripts/setup_shared_gitea.sh
10+
3. OpenEnv repo migrated to Gitea (see README)
11+
"""
12+
13+
import os
14+
import sys
15+
from pathlib import Path
16+
17+
# Load environment variables from .env file
18+
from dotenv import load_dotenv
19+
load_dotenv()
20+
21+
# Add src to path
22+
sys.path.insert(0, str(Path(__file__).parent.parent / "src"))
23+
24+
from envs.git_env import GitAction, GitEnv
25+
26+
27+
def main():
28+
"""Test GitEnv.from_docker_image()."""
29+
print("=" * 60)
30+
print("GitEnv.from_docker_image() Test")
31+
print("=" * 60)
32+
print()
33+
34+
try:
35+
# Pass environment variables from .env to container
36+
env_vars = {
37+
"GITEA_URL": os.getenv("GITEA_URL"),
38+
"GITEA_USERNAME": os.getenv("GITEA_USERNAME"),
39+
"GITEA_PASSWORD": os.getenv("GITEA_PASSWORD"),
40+
}
41+
42+
# Verify env vars are loaded
43+
if not all(env_vars.values()):
44+
print("❌ Error: Required environment variables not found in .env")
45+
print(" Make sure .env file exists (copy from .env.example)")
46+
return False
47+
48+
print("Creating client from Docker image with .env credentials...")
49+
print(" Using GitEnv.from_docker_image() factory method")
50+
print()
51+
52+
# Create client using from_docker_image factory method
53+
client = GitEnv.from_docker_image("git-env:latest", env_vars=env_vars)
54+
55+
print("✓ Client created and container started!\n")
56+
57+
# Now use it like any other client
58+
print("Testing the environment:")
59+
print("-" * 60)
60+
61+
# Reset
62+
print("\n1. Reset:")
63+
result = client.reset()
64+
print(f" Message: {result.observation.message}")
65+
print(f" Success: {result.observation.success}")
66+
67+
# Get initial state
68+
state = client.state()
69+
print(f" State: episode_id={state.episode_id}, step_count={state.step_count}")
70+
print(f" Gitea ready: {state.gitea_ready}")
71+
72+
# List repositories
73+
print("\n2. List repositories:")
74+
result = client.step(GitAction(action_type="list_repos"))
75+
print(f" Success: {result.observation.success}")
76+
print(f" Found {len(result.observation.repos)} repositories")
77+
for repo in result.observation.repos:
78+
print(f" - {repo['name']}")
79+
80+
# Clone repository
81+
print("\n3. Clone repository:")
82+
result = client.step(GitAction(action_type="clone_repo", repo_name="OpenEnv"))
83+
print(f" Success: {result.observation.success}")
84+
print(f" Message: {result.observation.message}")
85+
print(f" Output: {result.observation.output}")
86+
87+
# Execute git commands
88+
print("\n4. Execute git commands:")
89+
90+
git_commands = [
91+
"status",
92+
"log --oneline -5",
93+
"branch -a",
94+
]
95+
96+
for cmd in git_commands:
97+
result = client.step(
98+
GitAction(action_type="execute_git_command", command=cmd, working_dir="OpenEnv")
99+
)
100+
print(f"\n git {cmd}:")
101+
print(f" Success: {result.observation.success}")
102+
if result.observation.output:
103+
# Show first few lines
104+
lines = result.observation.output.strip().split("\n")[:5]
105+
for line in lines:
106+
print(f" {line}")
107+
if len(result.observation.output.strip().split("\n")) > 5:
108+
print(" ...")
109+
110+
# Check final state
111+
print("\n5. Check final state:")
112+
state = client.state()
113+
print(f" episode_id: {state.episode_id}")
114+
print(f" step_count: {state.step_count}")
115+
print(f" gitea_ready: {state.gitea_ready}")
116+
117+
print("\n" + "-" * 60)
118+
print("\n✓ All operations successful!")
119+
print()
120+
121+
print("Cleaning up...")
122+
client.close()
123+
print("✓ Container stopped and removed")
124+
print()
125+
126+
print("=" * 60)
127+
print("Test completed successfully!")
128+
print("=" * 60)
129+
130+
return True
131+
132+
except Exception as e:
133+
print(f"\n❌ Test failed: {e}")
134+
import traceback
135+
136+
traceback.print_exc()
137+
return False
138+
139+
140+
if __name__ == "__main__":
141+
success = main()
142+
exit(0 if success else 1)

scripts/setup_shared_gitea.sh

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
#!/bin/bash
2+
# Setup script for shared Gitea instance
3+
# This script starts Gitea, waits for it to be ready, and creates the admin user
4+
# Requires: .env file with GITEA_USERNAME and GITEA_PASSWORD
5+
6+
set -e
7+
8+
# Load credentials from .env file
9+
if [ -f .env ]; then
10+
export $(cat .env | grep -E '^(GITEA_USERNAME|GITEA_PASSWORD)=' | xargs)
11+
else
12+
echo "❌ Error: .env file not found"
13+
echo " Please copy .env.example to .env and configure credentials"
14+
exit 1
15+
fi
16+
17+
echo "====================================="
18+
echo "Setting up shared Gitea instance"
19+
echo "====================================="
20+
echo
21+
22+
# Start Gitea with docker-compose
23+
echo "1. Starting Gitea container..."
24+
docker-compose -f src/envs/git_env/docker-compose.gitea.yml up -d
25+
26+
# Wait for Gitea to be healthy
27+
echo "2. Waiting for Gitea to be ready..."
28+
timeout=60
29+
elapsed=0
30+
while [ $elapsed -lt $timeout ]; do
31+
if docker exec openenv-gitea curl -sf http://localhost:3000/ > /dev/null 2>&1; then
32+
echo " ✓ Gitea is ready!"
33+
break
34+
fi
35+
echo " Waiting... (${elapsed}s/${timeout}s)"
36+
sleep 2
37+
elapsed=$((elapsed + 2))
38+
done
39+
40+
if [ $elapsed -ge $timeout ]; then
41+
echo " ✗ Timeout waiting for Gitea"
42+
exit 1
43+
fi
44+
45+
# Initialize Gitea (POST to root URL)
46+
echo "3. Initializing Gitea configuration..."
47+
docker exec openenv-gitea curl -s -X POST \
48+
-H "Content-Type: application/x-www-form-urlencoded" \
49+
-d "db_type=sqlite3" \
50+
-d "db_path=%2Fdata%2Fgitea%2Fgitea.db" \
51+
-d "app_name=Gitea" \
52+
-d "repo_root_path=%2Fdata%2Fgit%2Frepositories" \
53+
-d "run_user=git" \
54+
-d "domain=gitea" \
55+
-d "http_port=3000" \
56+
-d "app_url=http%3A%2F%2Fgitea%3A3000%2F" \
57+
-d "log_root_path=%2Fdata%2Fgitea%2Flog" \
58+
-d "offline_mode=on" \
59+
http://localhost:3000/ > /dev/null || echo " (Config may already exist)"
60+
61+
# Create admin user
62+
echo "4. Creating admin user ($GITEA_USERNAME)..."
63+
docker exec openenv-gitea su git -c \
64+
"gitea admin user create --username $GITEA_USERNAME --password $GITEA_PASSWORD --email ${GITEA_USERNAME}@local.env --admin" \
65+
2>&1 | grep -q "already exists" && echo " ✓ User already exists" || echo " ✓ User created"
66+
67+
echo
68+
echo "====================================="
69+
echo "✓ Gitea setup complete!"
70+
echo "====================================="
71+
echo
72+
echo "Gitea is now available at:"
73+
echo " - Web UI: http://localhost:3000"
74+
echo " - From containers: http://gitea:3000"
75+
echo
76+
echo "Admin credentials are configured from .env file"
77+
echo
78+
echo "To stop Gitea:"
79+
echo " docker-compose -f src/envs/git_env/docker-compose.gitea.yml down"
80+
echo
81+
echo "To remove all data:"
82+
echo " docker-compose -f src/envs/git_env/docker-compose.gitea.yml down -v"
83+
echo

src/core/http_env_client.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ def from_docker_image(
4646
cls: Type[EnvClientT],
4747
image: str,
4848
provider: Optional["ContainerProvider"] = None,
49+
**kwargs: Any,
4950
) -> EnvClientT:
5051
"""
5152
Create an environment client by spinning up a Docker container locally.
@@ -61,6 +62,8 @@ def from_docker_image(
6162
Args:
6263
image: Docker image name to run (e.g., "echo-env:latest")
6364
provider: Container provider to use (defaults to LocalDockerProvider)
65+
**kwargs: Additional arguments to pass to provider.start_container()
66+
(e.g., env_vars, port)
6467
6568
Returns:
6669
An instance of the client class connected to the running container
@@ -72,6 +75,12 @@ def from_docker_image(
7275
>>> # Create environment from image
7376
>>> env = CodingEnv.from_docker_image("coding-env:latest")
7477
>>>
78+
>>> # Create environment with custom env vars
79+
>>> env = CodingEnv.from_docker_image(
80+
... "coding-env:latest",
81+
... env_vars={"MY_VAR": "value"}
82+
... )
83+
>>>
7584
>>> # Use the environment
7685
>>> result = env.reset()
7786
>>> print(result.observation)
@@ -87,8 +96,8 @@ def from_docker_image(
8796
if provider is None:
8897
provider = LocalDockerProvider()
8998

90-
# 1. Start container
91-
base_url = provider.start_container(image)
99+
# 1. Start container with optional kwargs (e.g., env_vars, port)
100+
base_url = provider.start_container(image, **kwargs)
92101

93102
# 2. Wait for server to be ready
94103
provider.wait_for_ready(base_url)

src/core/tools/__init__.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@
66

77
"""Core tools for code execution and other utilities."""
88

9+
from .git_server_client import GitServerClient, RepoInfo
910
from .local_python_executor import PyExecutor
1011

11-
__all__ = ["PyExecutor"]
12+
__all__ = [
13+
"PyExecutor",
14+
"GitServerClient",
15+
"RepoInfo",
16+
]

0 commit comments

Comments
 (0)