<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
	<id>http://edurange.org/wiki/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=Jack</id>
	<title>EDURange - User contributions [en]</title>
	<link rel="self" type="application/atom+xml" href="http://edurange.org/wiki/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=Jack"/>
	<link rel="alternate" type="text/html" href="http://edurange.org/wiki/Special:Contributions/Jack"/>
	<updated>2026-04-04T00:30:30Z</updated>
	<subtitle>User contributions</subtitle>
	<generator>MediaWiki 1.43.0</generator>
	<entry>
		<id>http://edurange.org/wiki/index.php?title=Podman_API&amp;diff=883</id>
		<title>Podman API</title>
		<link rel="alternate" type="text/html" href="http://edurange.org/wiki/index.php?title=Podman_API&amp;diff=883"/>
		<updated>2025-07-19T18:14:37Z</updated>

		<summary type="html">&lt;p&gt;Jack: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[Category:Demonstrations]]&lt;br /&gt;
In reference to: &lt;br /&gt;
https://github.com/edurange/demo-podman-api-extra&lt;br /&gt;
&lt;br /&gt;
More design thoughts and justifications to be added later, these are just the protocol specs.&lt;br /&gt;
&lt;br /&gt;
This design is not meant to be followed, it&#039;s just my first sketch at listing everything we might need.&lt;br /&gt;
&lt;br /&gt;
Testing instructions can be found [https://github.com/edurange/demo-podman-api-extra/blob/main/TESTING.md here], or run the demo script.&lt;br /&gt;
&lt;br /&gt;
== Overview ==&lt;br /&gt;
&lt;br /&gt;
* Container lifecycle management (create, start, stop, remove)&lt;br /&gt;
* Container listing and monitoring&lt;br /&gt;
* Log retrieval&lt;br /&gt;
* User management within containers&lt;br /&gt;
* File operations&lt;br /&gt;
* Command execution&lt;br /&gt;
* Health checks and metrics&lt;br /&gt;
&lt;br /&gt;
=== Prerequisites ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;Python 3.7+&amp;lt;/p&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;Podman installed and configured&amp;lt;/p&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;Required Python packages:&amp;lt;/p&amp;gt;&lt;br /&gt;
&amp;lt;p&amp;gt;pip install flask flask-cors podman&amp;lt;/p&amp;gt;&amp;lt;/li&amp;gt;&amp;lt;/ul&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Running the API ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;python3 demo-podman-api.py&amp;lt;/pre&amp;gt;&lt;br /&gt;
The API will start on http://localhost:5000 by default.&lt;br /&gt;
&lt;br /&gt;
== Configuration ==&lt;br /&gt;
&lt;br /&gt;
Configure the API using environment variables:&lt;br /&gt;
&lt;br /&gt;
Example:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;export MAX_WORKERS=20&lt;br /&gt;
export DEBUG=true&lt;br /&gt;
export REQUEST_TIMEOUT=60&lt;br /&gt;
python3 demo-podman-api.py&amp;lt;/pre&amp;gt;&lt;br /&gt;
== API Endpoints ==&lt;br /&gt;
&lt;br /&gt;
=== Base URL ===&lt;br /&gt;
&lt;br /&gt;
All endpoints are prefixed with /api/v1&lt;br /&gt;
&lt;br /&gt;
=== Container Management ===&lt;br /&gt;
&lt;br /&gt;
==== Create Container ====&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;POST&#039;&#039;&#039; /containers&lt;br /&gt;
&lt;br /&gt;
Creates and starts a new container.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Request Body:&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;{&lt;br /&gt;
  &amp;amp;quot;image&amp;amp;quot;: &amp;amp;quot;alpine:latest&amp;amp;quot;,&lt;br /&gt;
  &amp;amp;quot;name&amp;amp;quot;: &amp;amp;quot;my-container&amp;amp;quot;,&lt;br /&gt;
  &amp;amp;quot;command&amp;amp;quot;: &amp;amp;quot;sleep 300&amp;amp;quot;,&lt;br /&gt;
  &amp;amp;quot;environment&amp;amp;quot;: {&amp;amp;quot;ENV_VAR&amp;amp;quot;: &amp;amp;quot;value&amp;amp;quot;},&lt;br /&gt;
  &amp;amp;quot;ports&amp;amp;quot;: {&amp;amp;quot;8080&amp;amp;quot;: &amp;amp;quot;80&amp;amp;quot;},&lt;br /&gt;
  &amp;amp;quot;volumes&amp;amp;quot;: {&amp;amp;quot;/host/path&amp;amp;quot;: &amp;amp;quot;/container/path&amp;amp;quot;},&lt;br /&gt;
  &amp;amp;quot;user&amp;amp;quot;: &amp;amp;quot;root&amp;amp;quot;&lt;br /&gt;
}&amp;lt;/pre&amp;gt;&lt;br /&gt;
&#039;&#039;&#039;Required Fields:&#039;&#039;&#039; image, name&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Response:&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;{&lt;br /&gt;
  &amp;amp;quot;success&amp;amp;quot;: true,&lt;br /&gt;
  &amp;amp;quot;container&amp;amp;quot;: {&lt;br /&gt;
    &amp;amp;quot;id&amp;amp;quot;: &amp;amp;quot;abc123def456&amp;amp;quot;,&lt;br /&gt;
    &amp;amp;quot;name&amp;amp;quot;: &amp;amp;quot;my-container&amp;amp;quot;,&lt;br /&gt;
    &amp;amp;quot;status&amp;amp;quot;: &amp;amp;quot;running&amp;amp;quot;&lt;br /&gt;
  }&lt;br /&gt;
}&amp;lt;/pre&amp;gt;&lt;br /&gt;
==== List Containers ====&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;GET&#039;&#039;&#039; /containers&lt;br /&gt;
&lt;br /&gt;
Lists all containers (running and stopped).&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Response:&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;{&lt;br /&gt;
  &amp;amp;quot;success&amp;amp;quot;: true,&lt;br /&gt;
  &amp;amp;quot;containers&amp;amp;quot;: [&lt;br /&gt;
    {&lt;br /&gt;
      &amp;amp;quot;id&amp;amp;quot;: &amp;amp;quot;abc123def456&amp;amp;quot;,&lt;br /&gt;
      &amp;amp;quot;name&amp;amp;quot;: &amp;amp;quot;my-container&amp;amp;quot;,&lt;br /&gt;
      &amp;amp;quot;status&amp;amp;quot;: &amp;amp;quot;running&amp;amp;quot;,&lt;br /&gt;
      &amp;amp;quot;image&amp;amp;quot;: &amp;amp;quot;alpine:latest&amp;amp;quot;&lt;br /&gt;
    }&lt;br /&gt;
  ]&lt;br /&gt;
}&amp;lt;/pre&amp;gt;&lt;br /&gt;
==== Start Container ====&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;POST&#039;&#039;&#039; /containers/{name}/start&lt;br /&gt;
&lt;br /&gt;
Starts a stopped container.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Response:&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;{&lt;br /&gt;
  &amp;amp;quot;success&amp;amp;quot;: true,&lt;br /&gt;
  &amp;amp;quot;container&amp;amp;quot;: {&lt;br /&gt;
    &amp;amp;quot;name&amp;amp;quot;: &amp;amp;quot;my-container&amp;amp;quot;,&lt;br /&gt;
    &amp;amp;quot;status&amp;amp;quot;: &amp;amp;quot;started&amp;amp;quot;&lt;br /&gt;
  }&lt;br /&gt;
}&amp;lt;/pre&amp;gt;&lt;br /&gt;
==== Stop Container ====&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;POST&#039;&#039;&#039; /containers/{name}/stop&lt;br /&gt;
&lt;br /&gt;
Stops a running container.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Response:&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;{&lt;br /&gt;
  &amp;amp;quot;success&amp;amp;quot;: true,&lt;br /&gt;
  &amp;amp;quot;container&amp;amp;quot;: {&lt;br /&gt;
    &amp;amp;quot;name&amp;amp;quot;: &amp;amp;quot;my-container&amp;amp;quot;,&lt;br /&gt;
    &amp;amp;quot;status&amp;amp;quot;: &amp;amp;quot;stopped&amp;amp;quot;&lt;br /&gt;
  }&lt;br /&gt;
}&amp;lt;/pre&amp;gt;&lt;br /&gt;
==== Remove Container ====&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;DELETE&#039;&#039;&#039; /containers/{name}&lt;br /&gt;
&lt;br /&gt;
Removes a container.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Query Parameters:&#039;&#039;&#039; - force (boolean): Force removal of running container&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Response:&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;{&lt;br /&gt;
  &amp;amp;quot;success&amp;amp;quot;: true,&lt;br /&gt;
  &amp;amp;quot;container&amp;amp;quot;: {&lt;br /&gt;
    &amp;amp;quot;name&amp;amp;quot;: &amp;amp;quot;my-container&amp;amp;quot;,&lt;br /&gt;
    &amp;amp;quot;removed&amp;amp;quot;: true&lt;br /&gt;
  }&lt;br /&gt;
}&amp;lt;/pre&amp;gt;&lt;br /&gt;
==== Get Container Logs ====&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;GET&#039;&#039;&#039; /containers/{name}/logs&lt;br /&gt;
&lt;br /&gt;
Retrieves container logs.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Query Parameters:&#039;&#039;&#039; - tail (integer): Number of lines to retrieve (default: 100)&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Response:&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;{&lt;br /&gt;
  &amp;amp;quot;success&amp;amp;quot;: true,&lt;br /&gt;
  &amp;amp;quot;container&amp;amp;quot;: {&lt;br /&gt;
    &amp;amp;quot;logs&amp;amp;quot;: &amp;amp;quot;Container log output here...&amp;amp;quot;&lt;br /&gt;
  }&lt;br /&gt;
}&amp;lt;/pre&amp;gt;&lt;br /&gt;
=== Container Operations ===&lt;br /&gt;
&lt;br /&gt;
==== Execute Command ====&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;POST&#039;&#039;&#039; /containers/{name}/exec&lt;br /&gt;
&lt;br /&gt;
Executes a command inside a container.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Request Body:&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;{&lt;br /&gt;
  &amp;amp;quot;command&amp;amp;quot;: &amp;amp;quot;ls -la /tmp&amp;amp;quot;,&lt;br /&gt;
  &amp;amp;quot;user&amp;amp;quot;: &amp;amp;quot;root&amp;amp;quot;&lt;br /&gt;
}&amp;lt;/pre&amp;gt;&lt;br /&gt;
&#039;&#039;&#039;Required Fields:&#039;&#039;&#039; command&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Response:&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;{&lt;br /&gt;
  &amp;amp;quot;success&amp;amp;quot;: true,&lt;br /&gt;
  &amp;amp;quot;execution&amp;amp;quot;: {&lt;br /&gt;
    &amp;amp;quot;exit_code&amp;amp;quot;: 0,&lt;br /&gt;
    &amp;amp;quot;output&amp;amp;quot;: &amp;amp;quot;total 4\ndrwxrwxrwt 2 root root 4096 Jan 1 00:00 .\n&amp;amp;quot;,&lt;br /&gt;
    &amp;amp;quot;success&amp;amp;quot;: true&lt;br /&gt;
  }&lt;br /&gt;
}&amp;lt;/pre&amp;gt;&lt;br /&gt;
==== Add User ====&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;POST&#039;&#039;&#039; /containers/{name}/users&lt;br /&gt;
&lt;br /&gt;
Creates a new user inside a container.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Request Body:&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;{&lt;br /&gt;
  &amp;amp;quot;username&amp;amp;quot;: &amp;amp;quot;newuser&amp;amp;quot;,&lt;br /&gt;
  &amp;amp;quot;password&amp;amp;quot;: &amp;amp;quot;password123&amp;amp;quot;,&lt;br /&gt;
  &amp;amp;quot;shell&amp;amp;quot;: &amp;amp;quot;/bin/bash&amp;amp;quot;&lt;br /&gt;
}&amp;lt;/pre&amp;gt;&lt;br /&gt;
&#039;&#039;&#039;Required Fields:&#039;&#039;&#039; username&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Response:&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;{&lt;br /&gt;
  &amp;amp;quot;success&amp;amp;quot;: true,&lt;br /&gt;
  &amp;amp;quot;user&amp;amp;quot;: {&lt;br /&gt;
    &amp;amp;quot;username&amp;amp;quot;: &amp;amp;quot;newuser&amp;amp;quot;,&lt;br /&gt;
    &amp;amp;quot;created&amp;amp;quot;: true&lt;br /&gt;
  }&lt;br /&gt;
}&amp;lt;/pre&amp;gt;&lt;br /&gt;
==== Add File ====&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;POST&#039;&#039;&#039; /containers/{name}/files&lt;br /&gt;
&lt;br /&gt;
Creates a file inside a container.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Request Body:&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;{&lt;br /&gt;
  &amp;amp;quot;dest_path&amp;amp;quot;: &amp;amp;quot;/tmp/myfile.txt&amp;amp;quot;,&lt;br /&gt;
  &amp;amp;quot;content&amp;amp;quot;: &amp;amp;quot;Hello, World!\nThis is file content.&amp;amp;quot;&lt;br /&gt;
}&amp;lt;/pre&amp;gt;&lt;br /&gt;
&#039;&#039;&#039;Required Fields:&#039;&#039;&#039; dest_path, content&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Response:&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;{&lt;br /&gt;
  &amp;amp;quot;success&amp;amp;quot;: true,&lt;br /&gt;
  &amp;amp;quot;file&amp;amp;quot;: {&lt;br /&gt;
    &amp;amp;quot;dest_path&amp;amp;quot;: &amp;amp;quot;/tmp/myfile.txt&amp;amp;quot;,&lt;br /&gt;
    &amp;amp;quot;size&amp;amp;quot;: 32&lt;br /&gt;
  }&lt;br /&gt;
}&amp;lt;/pre&amp;gt;&lt;br /&gt;
=== System Endpoints ===&lt;br /&gt;
&lt;br /&gt;
==== Health Check ====&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;GET&#039;&#039;&#039; /health&lt;br /&gt;
&lt;br /&gt;
Checks API and Podman connectivity.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Response:&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;{&lt;br /&gt;
  &amp;amp;quot;status&amp;amp;quot;: &amp;amp;quot;healthy&amp;amp;quot;,&lt;br /&gt;
  &amp;amp;quot;podman&amp;amp;quot;: &amp;amp;quot;connected&amp;amp;quot;,&lt;br /&gt;
  &amp;amp;quot;timestamp&amp;amp;quot;: &amp;amp;quot;2025-01-19T15:47:19.123456&amp;amp;quot;&lt;br /&gt;
}&amp;lt;/pre&amp;gt;&lt;br /&gt;
==== Metrics ====&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;GET&#039;&#039;&#039; /metrics&lt;br /&gt;
&lt;br /&gt;
Returns API performance metrics.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Response:&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;{&lt;br /&gt;
  &amp;amp;quot;uptime_seconds&amp;amp;quot;: 3600,&lt;br /&gt;
  &amp;amp;quot;uptime_human&amp;amp;quot;: &amp;amp;quot;1:00:00&amp;amp;quot;,&lt;br /&gt;
  &amp;amp;quot;active_threads&amp;amp;quot;: 2,&lt;br /&gt;
  &amp;amp;quot;max_workers&amp;amp;quot;: 10,&lt;br /&gt;
  &amp;amp;quot;timestamp&amp;amp;quot;: &amp;amp;quot;2025-01-19T15:47:19.123456&amp;amp;quot;&lt;br /&gt;
}&amp;lt;/pre&amp;gt;&lt;br /&gt;
== Error Handling ==&lt;br /&gt;
&lt;br /&gt;
=== Error Response Format ===&lt;br /&gt;
&lt;br /&gt;
All errors follow a consistent format:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;{&lt;br /&gt;
  &amp;amp;quot;success&amp;amp;quot;: false,&lt;br /&gt;
  &amp;amp;quot;error&amp;amp;quot;: {&lt;br /&gt;
    &amp;amp;quot;message&amp;amp;quot;: &amp;amp;quot;Error description&amp;amp;quot;,&lt;br /&gt;
    &amp;amp;quot;code&amp;amp;quot;: 404,&lt;br /&gt;
    &amp;amp;quot;type&amp;amp;quot;: &amp;amp;quot;PodmanAPIError&amp;amp;quot;&lt;br /&gt;
  },&lt;br /&gt;
  &amp;amp;quot;timestamp&amp;amp;quot;: &amp;amp;quot;2025-01-19T15:47:19.123456&amp;amp;quot;&lt;br /&gt;
}&amp;lt;/pre&amp;gt;&lt;br /&gt;
=== HTTP Status Codes ===&lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
! Code&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 200&lt;br /&gt;
| Success&lt;br /&gt;
|-&lt;br /&gt;
| 400&lt;br /&gt;
| Bad Request (missing required fields)&lt;br /&gt;
|-&lt;br /&gt;
| 404&lt;br /&gt;
| Resource not found (container, image, etc.)&lt;br /&gt;
|-&lt;br /&gt;
| 408&lt;br /&gt;
| Request timeout&lt;br /&gt;
|-&lt;br /&gt;
| 500&lt;br /&gt;
| Internal server error&lt;br /&gt;
|-&lt;br /&gt;
| 503&lt;br /&gt;
| Service unavailable (Podman connection issues)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Common Error Types ===&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;PodmanAPIError&#039;&#039;&#039;: Podman-related errors&lt;br /&gt;
* &#039;&#039;&#039;InternalError&#039;&#039;&#039;: Server-side errors&lt;br /&gt;
* &#039;&#039;&#039;ValidationError&#039;&#039;&#039;: Request validation failures&lt;br /&gt;
&lt;br /&gt;
== Examples ==&lt;br /&gt;
&lt;br /&gt;
=== Complete Container Workflow ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;# 1. Check API health&lt;br /&gt;
curl -X GET http://localhost:5000/api/v1/health | jq&lt;br /&gt;
&lt;br /&gt;
# 2. Create and start container&lt;br /&gt;
curl -X POST http://localhost:5000/api/v1/containers \&lt;br /&gt;
  -H &amp;amp;quot;Content-Type: application/json&amp;amp;quot; \&lt;br /&gt;
  -d &#039;{&lt;br /&gt;
    &amp;amp;quot;image&amp;amp;quot;: &amp;amp;quot;alpine:latest&amp;amp;quot;,&lt;br /&gt;
    &amp;amp;quot;name&amp;amp;quot;: &amp;amp;quot;demo-container&amp;amp;quot;,&lt;br /&gt;
    &amp;amp;quot;command&amp;amp;quot;: &amp;amp;quot;sleep 300&amp;amp;quot;&lt;br /&gt;
  }&#039; | jq&lt;br /&gt;
&lt;br /&gt;
# 3. Add a user&lt;br /&gt;
curl -X POST http://localhost:5000/api/v1/containers/demo-container/users \&lt;br /&gt;
  -H &amp;amp;quot;Content-Type: application/json&amp;amp;quot; \&lt;br /&gt;
  -d &#039;{&lt;br /&gt;
    &amp;amp;quot;username&amp;amp;quot;: &amp;amp;quot;testuser&amp;amp;quot;,&lt;br /&gt;
    &amp;amp;quot;password&amp;amp;quot;: &amp;amp;quot;testpass&amp;amp;quot;,&lt;br /&gt;
    &amp;amp;quot;shell&amp;amp;quot;: &amp;amp;quot;/bin/sh&amp;amp;quot;&lt;br /&gt;
  }&#039; | jq&lt;br /&gt;
&lt;br /&gt;
# 4. Create a file&lt;br /&gt;
curl -X POST http://localhost:5000/api/v1/containers/demo-container/files \&lt;br /&gt;
  -H &amp;amp;quot;Content-Type: application/json&amp;amp;quot; \&lt;br /&gt;
  -d &#039;{&lt;br /&gt;
    &amp;amp;quot;dest_path&amp;amp;quot;: &amp;amp;quot;/tmp/demo.txt&amp;amp;quot;,&lt;br /&gt;
    &amp;amp;quot;content&amp;amp;quot;: &amp;amp;quot;Hello from the API!&amp;amp;quot;&lt;br /&gt;
  }&#039; | jq&lt;br /&gt;
&lt;br /&gt;
# 5. Execute commands&lt;br /&gt;
curl -X POST http://localhost:5000/api/v1/containers/demo-container/exec \&lt;br /&gt;
  -H &amp;amp;quot;Content-Type: application/json&amp;amp;quot; \&lt;br /&gt;
  -d &#039;{&amp;amp;quot;command&amp;amp;quot;: &amp;amp;quot;cat /tmp/demo.txt&amp;amp;quot;}&#039; | jq&lt;br /&gt;
&lt;br /&gt;
# 6. Check logs&lt;br /&gt;
curl -X GET &amp;amp;quot;http://localhost:5000/api/v1/containers/demo-container/logs?tail=20&amp;amp;quot; | jq&lt;br /&gt;
&lt;br /&gt;
# 7. Stop and remove container&lt;br /&gt;
curl -X POST http://localhost:5000/api/v1/containers/demo-container/stop | jq&lt;br /&gt;
curl -X DELETE &amp;amp;quot;http://localhost:5000/api/v1/containers/demo-container?force=true&amp;amp;quot; | jq&amp;lt;/pre&amp;gt;&lt;br /&gt;
=== Python Client Example ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;import requests&lt;br /&gt;
import json&lt;br /&gt;
&lt;br /&gt;
class PodmanAPIClient:&lt;br /&gt;
    def __init__(self, base_url=&amp;amp;quot;http://localhost:5000/api/v1&amp;amp;quot;):&lt;br /&gt;
        self.base_url = base_url&lt;br /&gt;
    &lt;br /&gt;
    def create_container(self, image, name, **kwargs):&lt;br /&gt;
        data = {&amp;amp;quot;image&amp;amp;quot;: image, &amp;amp;quot;name&amp;amp;quot;: name, **kwargs}&lt;br /&gt;
        response = requests.post(f&amp;amp;quot;{self.base_url}/containers&amp;amp;quot;, json=data)&lt;br /&gt;
        return response.json()&lt;br /&gt;
    &lt;br /&gt;
    def execute_command(self, container_name, command, user=None):&lt;br /&gt;
        data = {&amp;amp;quot;command&amp;amp;quot;: command}&lt;br /&gt;
        if user:&lt;br /&gt;
            data[&amp;amp;quot;user&amp;amp;quot;] = user&lt;br /&gt;
        response = requests.post(&lt;br /&gt;
            f&amp;amp;quot;{self.base_url}/containers/{container_name}/exec&amp;amp;quot;, &lt;br /&gt;
            json=data&lt;br /&gt;
        )&lt;br /&gt;
        return response.json()&lt;br /&gt;
&lt;br /&gt;
# Usage&lt;br /&gt;
client = PodmanAPIClient()&lt;br /&gt;
result = client.create_container(&amp;amp;quot;alpine:latest&amp;amp;quot;, &amp;amp;quot;test-container&amp;amp;quot;)&lt;br /&gt;
print(json.dumps(result, indent=2))&amp;lt;/pre&amp;gt;&lt;br /&gt;
== Security Considerations ==&lt;br /&gt;
&lt;br /&gt;
* The API runs without authentication by default&lt;br /&gt;
* Container commands are executed with full privileges&lt;br /&gt;
* File operations can write anywhere in the container filesystem&lt;br /&gt;
* Consider implementing authentication and authorization for production use&lt;br /&gt;
* Validate and sanitize all user inputs&lt;br /&gt;
* Consider running the API behind a reverse proxy with rate limiting&lt;br /&gt;
&lt;br /&gt;
== Troubleshooting ==&lt;br /&gt;
&lt;br /&gt;
=== Common Issues ===&lt;br /&gt;
&lt;br /&gt;
# &#039;&#039;&#039;“Container not found” errors&#039;&#039;&#039;: &lt;br /&gt;
Ensure the container name is correct and the container exists&lt;br /&gt;
&lt;br /&gt;
# &#039;&#039;&#039;“Image not found” errors&#039;&#039;&#039;: &lt;br /&gt;
Pull the image with &amp;lt;code&amp;gt;podman pull &amp;amp;lt;image&amp;amp;gt;&amp;lt;/code&amp;gt; first&lt;br /&gt;
&lt;br /&gt;
# &#039;&#039;&#039;Permission errors&#039;&#039;&#039;: &lt;br /&gt;
Ensure Podman is properly configured for your user&lt;br /&gt;
&lt;br /&gt;
# &#039;&#039;&#039;Connection errors&#039;&#039;&#039;: &lt;br /&gt;
Check that Podman service is running&lt;br /&gt;
&lt;br /&gt;
=== Debug Mode ===&lt;br /&gt;
Enable debug mode for detailed logging:&lt;br /&gt;
&amp;lt;pre&amp;gt;export DEBUG=true&lt;br /&gt;
python3 demo-podman-api.py&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Logs ===&lt;br /&gt;
The API logs all operations. Check the console output for detailed error information.&lt;/div&gt;</summary>
		<author><name>Jack</name></author>
	</entry>
	<entry>
		<id>http://edurange.org/wiki/index.php?title=Podman_API&amp;diff=882</id>
		<title>Podman API</title>
		<link rel="alternate" type="text/html" href="http://edurange.org/wiki/index.php?title=Podman_API&amp;diff=882"/>
		<updated>2025-07-19T15:12:30Z</updated>

		<summary type="html">&lt;p&gt;Jack: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[Category:Demonstrations]]&lt;br /&gt;
In reference to: &lt;br /&gt;
https://github.com/edurange/demo-podman-api-extra&lt;br /&gt;
&lt;br /&gt;
More design thoughts and justifications to be added later, these are just the protocol specs.&lt;br /&gt;
&lt;br /&gt;
This design is not meant to be followed, it&#039;s just my first sketch at listing everything we might need.&lt;br /&gt;
&lt;br /&gt;
Testing instructions can be found [https://github.com/edurange/demo-podman-api-extra/blob/main/TESTING.md here], or run the demo script.&lt;br /&gt;
&lt;br /&gt;
== Table of Contents ==&lt;br /&gt;
&lt;br /&gt;
* Overview&lt;br /&gt;
* Configuration&lt;br /&gt;
* API Endpoints&lt;br /&gt;
* Error Handling&lt;br /&gt;
* Examples&lt;br /&gt;
* Testing&lt;br /&gt;
&lt;br /&gt;
== Overview ==&lt;br /&gt;
&lt;br /&gt;
* Container lifecycle management (create, start, stop, remove)&lt;br /&gt;
* Container listing and monitoring&lt;br /&gt;
* Log retrieval&lt;br /&gt;
* User management within containers&lt;br /&gt;
* File operations&lt;br /&gt;
* Command execution&lt;br /&gt;
* Health checks and metrics&lt;br /&gt;
&lt;br /&gt;
=== Prerequisites ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;Python 3.7+&amp;lt;/p&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;Podman installed and configured&amp;lt;/p&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;Required Python packages:&amp;lt;/p&amp;gt;&lt;br /&gt;
&amp;lt;p&amp;gt;pip install flask flask-cors podman&amp;lt;/p&amp;gt;&amp;lt;/li&amp;gt;&amp;lt;/ul&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Running the API ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;python3 demo-podman-api.py&amp;lt;/pre&amp;gt;&lt;br /&gt;
The API will start on http://localhost:5000 by default.&lt;br /&gt;
&lt;br /&gt;
== Configuration ==&lt;br /&gt;
&lt;br /&gt;
Configure the API using environment variables:&lt;br /&gt;
&lt;br /&gt;
Example:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;export MAX_WORKERS=20&lt;br /&gt;
export DEBUG=true&lt;br /&gt;
export REQUEST_TIMEOUT=60&lt;br /&gt;
python3 demo-podman-api.py&amp;lt;/pre&amp;gt;&lt;br /&gt;
== API Endpoints ==&lt;br /&gt;
&lt;br /&gt;
=== Base URL ===&lt;br /&gt;
&lt;br /&gt;
All endpoints are prefixed with /api/v1&lt;br /&gt;
&lt;br /&gt;
=== Container Management ===&lt;br /&gt;
&lt;br /&gt;
==== Create Container ====&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;POST&#039;&#039;&#039; /containers&lt;br /&gt;
&lt;br /&gt;
Creates and starts a new container.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Request Body:&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;{&lt;br /&gt;
  &amp;amp;quot;image&amp;amp;quot;: &amp;amp;quot;alpine:latest&amp;amp;quot;,&lt;br /&gt;
  &amp;amp;quot;name&amp;amp;quot;: &amp;amp;quot;my-container&amp;amp;quot;,&lt;br /&gt;
  &amp;amp;quot;command&amp;amp;quot;: &amp;amp;quot;sleep 300&amp;amp;quot;,&lt;br /&gt;
  &amp;amp;quot;environment&amp;amp;quot;: {&amp;amp;quot;ENV_VAR&amp;amp;quot;: &amp;amp;quot;value&amp;amp;quot;},&lt;br /&gt;
  &amp;amp;quot;ports&amp;amp;quot;: {&amp;amp;quot;8080&amp;amp;quot;: &amp;amp;quot;80&amp;amp;quot;},&lt;br /&gt;
  &amp;amp;quot;volumes&amp;amp;quot;: {&amp;amp;quot;/host/path&amp;amp;quot;: &amp;amp;quot;/container/path&amp;amp;quot;},&lt;br /&gt;
  &amp;amp;quot;user&amp;amp;quot;: &amp;amp;quot;root&amp;amp;quot;&lt;br /&gt;
}&amp;lt;/pre&amp;gt;&lt;br /&gt;
&#039;&#039;&#039;Required Fields:&#039;&#039;&#039; image, name&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Response:&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;{&lt;br /&gt;
  &amp;amp;quot;success&amp;amp;quot;: true,&lt;br /&gt;
  &amp;amp;quot;container&amp;amp;quot;: {&lt;br /&gt;
    &amp;amp;quot;id&amp;amp;quot;: &amp;amp;quot;abc123def456&amp;amp;quot;,&lt;br /&gt;
    &amp;amp;quot;name&amp;amp;quot;: &amp;amp;quot;my-container&amp;amp;quot;,&lt;br /&gt;
    &amp;amp;quot;status&amp;amp;quot;: &amp;amp;quot;running&amp;amp;quot;&lt;br /&gt;
  }&lt;br /&gt;
}&amp;lt;/pre&amp;gt;&lt;br /&gt;
==== List Containers ====&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;GET&#039;&#039;&#039; /containers&lt;br /&gt;
&lt;br /&gt;
Lists all containers (running and stopped).&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Response:&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;{&lt;br /&gt;
  &amp;amp;quot;success&amp;amp;quot;: true,&lt;br /&gt;
  &amp;amp;quot;containers&amp;amp;quot;: [&lt;br /&gt;
    {&lt;br /&gt;
      &amp;amp;quot;id&amp;amp;quot;: &amp;amp;quot;abc123def456&amp;amp;quot;,&lt;br /&gt;
      &amp;amp;quot;name&amp;amp;quot;: &amp;amp;quot;my-container&amp;amp;quot;,&lt;br /&gt;
      &amp;amp;quot;status&amp;amp;quot;: &amp;amp;quot;running&amp;amp;quot;,&lt;br /&gt;
      &amp;amp;quot;image&amp;amp;quot;: &amp;amp;quot;alpine:latest&amp;amp;quot;&lt;br /&gt;
    }&lt;br /&gt;
  ]&lt;br /&gt;
}&amp;lt;/pre&amp;gt;&lt;br /&gt;
==== Start Container ====&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;POST&#039;&#039;&#039; /containers/{name}/start&lt;br /&gt;
&lt;br /&gt;
Starts a stopped container.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Response:&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;{&lt;br /&gt;
  &amp;amp;quot;success&amp;amp;quot;: true,&lt;br /&gt;
  &amp;amp;quot;container&amp;amp;quot;: {&lt;br /&gt;
    &amp;amp;quot;name&amp;amp;quot;: &amp;amp;quot;my-container&amp;amp;quot;,&lt;br /&gt;
    &amp;amp;quot;status&amp;amp;quot;: &amp;amp;quot;started&amp;amp;quot;&lt;br /&gt;
  }&lt;br /&gt;
}&amp;lt;/pre&amp;gt;&lt;br /&gt;
==== Stop Container ====&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;POST&#039;&#039;&#039; /containers/{name}/stop&lt;br /&gt;
&lt;br /&gt;
Stops a running container.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Response:&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;{&lt;br /&gt;
  &amp;amp;quot;success&amp;amp;quot;: true,&lt;br /&gt;
  &amp;amp;quot;container&amp;amp;quot;: {&lt;br /&gt;
    &amp;amp;quot;name&amp;amp;quot;: &amp;amp;quot;my-container&amp;amp;quot;,&lt;br /&gt;
    &amp;amp;quot;status&amp;amp;quot;: &amp;amp;quot;stopped&amp;amp;quot;&lt;br /&gt;
  }&lt;br /&gt;
}&amp;lt;/pre&amp;gt;&lt;br /&gt;
==== Remove Container ====&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;DELETE&#039;&#039;&#039; /containers/{name}&lt;br /&gt;
&lt;br /&gt;
Removes a container.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Query Parameters:&#039;&#039;&#039; - force (boolean): Force removal of running container&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Response:&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;{&lt;br /&gt;
  &amp;amp;quot;success&amp;amp;quot;: true,&lt;br /&gt;
  &amp;amp;quot;container&amp;amp;quot;: {&lt;br /&gt;
    &amp;amp;quot;name&amp;amp;quot;: &amp;amp;quot;my-container&amp;amp;quot;,&lt;br /&gt;
    &amp;amp;quot;removed&amp;amp;quot;: true&lt;br /&gt;
  }&lt;br /&gt;
}&amp;lt;/pre&amp;gt;&lt;br /&gt;
==== Get Container Logs ====&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;GET&#039;&#039;&#039; /containers/{name}/logs&lt;br /&gt;
&lt;br /&gt;
Retrieves container logs.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Query Parameters:&#039;&#039;&#039; - tail (integer): Number of lines to retrieve (default: 100)&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Response:&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;{&lt;br /&gt;
  &amp;amp;quot;success&amp;amp;quot;: true,&lt;br /&gt;
  &amp;amp;quot;container&amp;amp;quot;: {&lt;br /&gt;
    &amp;amp;quot;logs&amp;amp;quot;: &amp;amp;quot;Container log output here...&amp;amp;quot;&lt;br /&gt;
  }&lt;br /&gt;
}&amp;lt;/pre&amp;gt;&lt;br /&gt;
=== Container Operations ===&lt;br /&gt;
&lt;br /&gt;
==== Execute Command ====&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;POST&#039;&#039;&#039; /containers/{name}/exec&lt;br /&gt;
&lt;br /&gt;
Executes a command inside a container.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Request Body:&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;{&lt;br /&gt;
  &amp;amp;quot;command&amp;amp;quot;: &amp;amp;quot;ls -la /tmp&amp;amp;quot;,&lt;br /&gt;
  &amp;amp;quot;user&amp;amp;quot;: &amp;amp;quot;root&amp;amp;quot;&lt;br /&gt;
}&amp;lt;/pre&amp;gt;&lt;br /&gt;
&#039;&#039;&#039;Required Fields:&#039;&#039;&#039; command&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Response:&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;{&lt;br /&gt;
  &amp;amp;quot;success&amp;amp;quot;: true,&lt;br /&gt;
  &amp;amp;quot;execution&amp;amp;quot;: {&lt;br /&gt;
    &amp;amp;quot;exit_code&amp;amp;quot;: 0,&lt;br /&gt;
    &amp;amp;quot;output&amp;amp;quot;: &amp;amp;quot;total 4\ndrwxrwxrwt 2 root root 4096 Jan 1 00:00 .\n&amp;amp;quot;,&lt;br /&gt;
    &amp;amp;quot;success&amp;amp;quot;: true&lt;br /&gt;
  }&lt;br /&gt;
}&amp;lt;/pre&amp;gt;&lt;br /&gt;
==== Add User ====&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;POST&#039;&#039;&#039; /containers/{name}/users&lt;br /&gt;
&lt;br /&gt;
Creates a new user inside a container.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Request Body:&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;{&lt;br /&gt;
  &amp;amp;quot;username&amp;amp;quot;: &amp;amp;quot;newuser&amp;amp;quot;,&lt;br /&gt;
  &amp;amp;quot;password&amp;amp;quot;: &amp;amp;quot;password123&amp;amp;quot;,&lt;br /&gt;
  &amp;amp;quot;shell&amp;amp;quot;: &amp;amp;quot;/bin/bash&amp;amp;quot;&lt;br /&gt;
}&amp;lt;/pre&amp;gt;&lt;br /&gt;
&#039;&#039;&#039;Required Fields:&#039;&#039;&#039; username&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Response:&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;{&lt;br /&gt;
  &amp;amp;quot;success&amp;amp;quot;: true,&lt;br /&gt;
  &amp;amp;quot;user&amp;amp;quot;: {&lt;br /&gt;
    &amp;amp;quot;username&amp;amp;quot;: &amp;amp;quot;newuser&amp;amp;quot;,&lt;br /&gt;
    &amp;amp;quot;created&amp;amp;quot;: true&lt;br /&gt;
  }&lt;br /&gt;
}&amp;lt;/pre&amp;gt;&lt;br /&gt;
==== Add File ====&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;POST&#039;&#039;&#039; /containers/{name}/files&lt;br /&gt;
&lt;br /&gt;
Creates a file inside a container.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Request Body:&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;{&lt;br /&gt;
  &amp;amp;quot;dest_path&amp;amp;quot;: &amp;amp;quot;/tmp/myfile.txt&amp;amp;quot;,&lt;br /&gt;
  &amp;amp;quot;content&amp;amp;quot;: &amp;amp;quot;Hello, World!\nThis is file content.&amp;amp;quot;&lt;br /&gt;
}&amp;lt;/pre&amp;gt;&lt;br /&gt;
&#039;&#039;&#039;Required Fields:&#039;&#039;&#039; dest_path, content&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Response:&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;{&lt;br /&gt;
  &amp;amp;quot;success&amp;amp;quot;: true,&lt;br /&gt;
  &amp;amp;quot;file&amp;amp;quot;: {&lt;br /&gt;
    &amp;amp;quot;dest_path&amp;amp;quot;: &amp;amp;quot;/tmp/myfile.txt&amp;amp;quot;,&lt;br /&gt;
    &amp;amp;quot;size&amp;amp;quot;: 32&lt;br /&gt;
  }&lt;br /&gt;
}&amp;lt;/pre&amp;gt;&lt;br /&gt;
=== System Endpoints ===&lt;br /&gt;
&lt;br /&gt;
==== Health Check ====&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;GET&#039;&#039;&#039; /health&lt;br /&gt;
&lt;br /&gt;
Checks API and Podman connectivity.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Response:&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;{&lt;br /&gt;
  &amp;amp;quot;status&amp;amp;quot;: &amp;amp;quot;healthy&amp;amp;quot;,&lt;br /&gt;
  &amp;amp;quot;podman&amp;amp;quot;: &amp;amp;quot;connected&amp;amp;quot;,&lt;br /&gt;
  &amp;amp;quot;timestamp&amp;amp;quot;: &amp;amp;quot;2025-01-19T15:47:19.123456&amp;amp;quot;&lt;br /&gt;
}&amp;lt;/pre&amp;gt;&lt;br /&gt;
==== Metrics ====&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;GET&#039;&#039;&#039; /metrics&lt;br /&gt;
&lt;br /&gt;
Returns API performance metrics.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Response:&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;{&lt;br /&gt;
  &amp;amp;quot;uptime_seconds&amp;amp;quot;: 3600,&lt;br /&gt;
  &amp;amp;quot;uptime_human&amp;amp;quot;: &amp;amp;quot;1:00:00&amp;amp;quot;,&lt;br /&gt;
  &amp;amp;quot;active_threads&amp;amp;quot;: 2,&lt;br /&gt;
  &amp;amp;quot;max_workers&amp;amp;quot;: 10,&lt;br /&gt;
  &amp;amp;quot;timestamp&amp;amp;quot;: &amp;amp;quot;2025-01-19T15:47:19.123456&amp;amp;quot;&lt;br /&gt;
}&amp;lt;/pre&amp;gt;&lt;br /&gt;
== Error Handling ==&lt;br /&gt;
&lt;br /&gt;
=== Error Response Format ===&lt;br /&gt;
&lt;br /&gt;
All errors follow a consistent format:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;{&lt;br /&gt;
  &amp;amp;quot;success&amp;amp;quot;: false,&lt;br /&gt;
  &amp;amp;quot;error&amp;amp;quot;: {&lt;br /&gt;
    &amp;amp;quot;message&amp;amp;quot;: &amp;amp;quot;Error description&amp;amp;quot;,&lt;br /&gt;
    &amp;amp;quot;code&amp;amp;quot;: 404,&lt;br /&gt;
    &amp;amp;quot;type&amp;amp;quot;: &amp;amp;quot;PodmanAPIError&amp;amp;quot;&lt;br /&gt;
  },&lt;br /&gt;
  &amp;amp;quot;timestamp&amp;amp;quot;: &amp;amp;quot;2025-01-19T15:47:19.123456&amp;amp;quot;&lt;br /&gt;
}&amp;lt;/pre&amp;gt;&lt;br /&gt;
=== HTTP Status Codes ===&lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
! Code&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 200&lt;br /&gt;
| Success&lt;br /&gt;
|-&lt;br /&gt;
| 400&lt;br /&gt;
| Bad Request (missing required fields)&lt;br /&gt;
|-&lt;br /&gt;
| 404&lt;br /&gt;
| Resource not found (container, image, etc.)&lt;br /&gt;
|-&lt;br /&gt;
| 408&lt;br /&gt;
| Request timeout&lt;br /&gt;
|-&lt;br /&gt;
| 500&lt;br /&gt;
| Internal server error&lt;br /&gt;
|-&lt;br /&gt;
| 503&lt;br /&gt;
| Service unavailable (Podman connection issues)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Common Error Types ===&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;PodmanAPIError&#039;&#039;&#039;: Podman-related errors&lt;br /&gt;
* &#039;&#039;&#039;InternalError&#039;&#039;&#039;: Server-side errors&lt;br /&gt;
* &#039;&#039;&#039;ValidationError&#039;&#039;&#039;: Request validation failures&lt;br /&gt;
&lt;br /&gt;
== Examples ==&lt;br /&gt;
&lt;br /&gt;
=== Complete Container Workflow ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;# 1. Check API health&lt;br /&gt;
curl -X GET http://localhost:5000/api/v1/health | jq&lt;br /&gt;
&lt;br /&gt;
# 2. Create and start container&lt;br /&gt;
curl -X POST http://localhost:5000/api/v1/containers \&lt;br /&gt;
  -H &amp;amp;quot;Content-Type: application/json&amp;amp;quot; \&lt;br /&gt;
  -d &#039;{&lt;br /&gt;
    &amp;amp;quot;image&amp;amp;quot;: &amp;amp;quot;alpine:latest&amp;amp;quot;,&lt;br /&gt;
    &amp;amp;quot;name&amp;amp;quot;: &amp;amp;quot;demo-container&amp;amp;quot;,&lt;br /&gt;
    &amp;amp;quot;command&amp;amp;quot;: &amp;amp;quot;sleep 300&amp;amp;quot;&lt;br /&gt;
  }&#039; | jq&lt;br /&gt;
&lt;br /&gt;
# 3. Add a user&lt;br /&gt;
curl -X POST http://localhost:5000/api/v1/containers/demo-container/users \&lt;br /&gt;
  -H &amp;amp;quot;Content-Type: application/json&amp;amp;quot; \&lt;br /&gt;
  -d &#039;{&lt;br /&gt;
    &amp;amp;quot;username&amp;amp;quot;: &amp;amp;quot;testuser&amp;amp;quot;,&lt;br /&gt;
    &amp;amp;quot;password&amp;amp;quot;: &amp;amp;quot;testpass&amp;amp;quot;,&lt;br /&gt;
    &amp;amp;quot;shell&amp;amp;quot;: &amp;amp;quot;/bin/sh&amp;amp;quot;&lt;br /&gt;
  }&#039; | jq&lt;br /&gt;
&lt;br /&gt;
# 4. Create a file&lt;br /&gt;
curl -X POST http://localhost:5000/api/v1/containers/demo-container/files \&lt;br /&gt;
  -H &amp;amp;quot;Content-Type: application/json&amp;amp;quot; \&lt;br /&gt;
  -d &#039;{&lt;br /&gt;
    &amp;amp;quot;dest_path&amp;amp;quot;: &amp;amp;quot;/tmp/demo.txt&amp;amp;quot;,&lt;br /&gt;
    &amp;amp;quot;content&amp;amp;quot;: &amp;amp;quot;Hello from the API!&amp;amp;quot;&lt;br /&gt;
  }&#039; | jq&lt;br /&gt;
&lt;br /&gt;
# 5. Execute commands&lt;br /&gt;
curl -X POST http://localhost:5000/api/v1/containers/demo-container/exec \&lt;br /&gt;
  -H &amp;amp;quot;Content-Type: application/json&amp;amp;quot; \&lt;br /&gt;
  -d &#039;{&amp;amp;quot;command&amp;amp;quot;: &amp;amp;quot;cat /tmp/demo.txt&amp;amp;quot;}&#039; | jq&lt;br /&gt;
&lt;br /&gt;
# 6. Check logs&lt;br /&gt;
curl -X GET &amp;amp;quot;http://localhost:5000/api/v1/containers/demo-container/logs?tail=20&amp;amp;quot; | jq&lt;br /&gt;
&lt;br /&gt;
# 7. Stop and remove container&lt;br /&gt;
curl -X POST http://localhost:5000/api/v1/containers/demo-container/stop | jq&lt;br /&gt;
curl -X DELETE &amp;amp;quot;http://localhost:5000/api/v1/containers/demo-container?force=true&amp;amp;quot; | jq&amp;lt;/pre&amp;gt;&lt;br /&gt;
=== Python Client Example ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;import requests&lt;br /&gt;
import json&lt;br /&gt;
&lt;br /&gt;
class PodmanAPIClient:&lt;br /&gt;
    def __init__(self, base_url=&amp;amp;quot;http://localhost:5000/api/v1&amp;amp;quot;):&lt;br /&gt;
        self.base_url = base_url&lt;br /&gt;
    &lt;br /&gt;
    def create_container(self, image, name, **kwargs):&lt;br /&gt;
        data = {&amp;amp;quot;image&amp;amp;quot;: image, &amp;amp;quot;name&amp;amp;quot;: name, **kwargs}&lt;br /&gt;
        response = requests.post(f&amp;amp;quot;{self.base_url}/containers&amp;amp;quot;, json=data)&lt;br /&gt;
        return response.json()&lt;br /&gt;
    &lt;br /&gt;
    def execute_command(self, container_name, command, user=None):&lt;br /&gt;
        data = {&amp;amp;quot;command&amp;amp;quot;: command}&lt;br /&gt;
        if user:&lt;br /&gt;
            data[&amp;amp;quot;user&amp;amp;quot;] = user&lt;br /&gt;
        response = requests.post(&lt;br /&gt;
            f&amp;amp;quot;{self.base_url}/containers/{container_name}/exec&amp;amp;quot;, &lt;br /&gt;
            json=data&lt;br /&gt;
        )&lt;br /&gt;
        return response.json()&lt;br /&gt;
&lt;br /&gt;
# Usage&lt;br /&gt;
client = PodmanAPIClient()&lt;br /&gt;
result = client.create_container(&amp;amp;quot;alpine:latest&amp;amp;quot;, &amp;amp;quot;test-container&amp;amp;quot;)&lt;br /&gt;
print(json.dumps(result, indent=2))&amp;lt;/pre&amp;gt;&lt;br /&gt;
== Security Considerations ==&lt;br /&gt;
&lt;br /&gt;
* The API runs without authentication by default&lt;br /&gt;
* Container commands are executed with full privileges&lt;br /&gt;
* File operations can write anywhere in the container filesystem&lt;br /&gt;
* Consider implementing authentication and authorization for production use&lt;br /&gt;
* Validate and sanitize all user inputs&lt;br /&gt;
* Consider running the API behind a reverse proxy with rate limiting&lt;br /&gt;
&lt;br /&gt;
== Troubleshooting ==&lt;br /&gt;
&lt;br /&gt;
=== Common Issues ===&lt;br /&gt;
&lt;br /&gt;
# &#039;&#039;&#039;“Container not found” errors&#039;&#039;&#039;: &lt;br /&gt;
Ensure the container name is correct and the container exists&lt;br /&gt;
&lt;br /&gt;
# &#039;&#039;&#039;“Image not found” errors&#039;&#039;&#039;: &lt;br /&gt;
Pull the image with &amp;lt;code&amp;gt;podman pull &amp;amp;lt;image&amp;amp;gt;&amp;lt;/code&amp;gt; first&lt;br /&gt;
&lt;br /&gt;
# &#039;&#039;&#039;Permission errors&#039;&#039;&#039;: &lt;br /&gt;
Ensure Podman is properly configured for your user&lt;br /&gt;
&lt;br /&gt;
# &#039;&#039;&#039;Connection errors&#039;&#039;&#039;: &lt;br /&gt;
Check that Podman service is running&lt;br /&gt;
&lt;br /&gt;
=== Debug Mode ===&lt;br /&gt;
Enable debug mode for detailed logging:&lt;br /&gt;
&amp;lt;pre&amp;gt;export DEBUG=true&lt;br /&gt;
python3 demo-podman-api.py&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Logs ===&lt;br /&gt;
The API logs all operations. Check the console output for detailed error information.&lt;/div&gt;</summary>
		<author><name>Jack</name></author>
	</entry>
	<entry>
		<id>http://edurange.org/wiki/index.php?title=Podman_API&amp;diff=881</id>
		<title>Podman API</title>
		<link rel="alternate" type="text/html" href="http://edurange.org/wiki/index.php?title=Podman_API&amp;diff=881"/>
		<updated>2025-07-19T14:12:25Z</updated>

		<summary type="html">&lt;p&gt;Jack: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[Category:Demonstrations]]&lt;br /&gt;
In reference to: &lt;br /&gt;
https://github.com/edurange/demo-podman-api-extra&lt;br /&gt;
&lt;br /&gt;
More design thoughts and justifications to be added later, these are just the protocol specs.&lt;br /&gt;
&lt;br /&gt;
This design is not meant to be followed, it&#039;s just my first sketch at listing everything we might need.&lt;br /&gt;
&lt;br /&gt;
Testing instructions can be found [https://github.com/edurange/demo-podman-api-extra/blob/main/TESTING.md here], or run the demo script.&lt;br /&gt;
== Configuration ==&lt;br /&gt;
&lt;br /&gt;
===== Config =====&lt;br /&gt;
Configuration class that loads settings from environment variables. &lt;br /&gt;
&lt;br /&gt;
* - MAX_WORKERS: Thread pool size (default: 10) &lt;br /&gt;
* - DEBUG: Enable debug mode (default: False) &lt;br /&gt;
* - REQUEST_TIMEOUT: Operation timeout in seconds (default: 30)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span id=&amp;quot;custom-exceptions&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&lt;br /&gt;
== Custom Exceptions ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;PodmanAPIError(message, status_code=500)&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Custom exception for API errors with HTTP status codes.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span id=&amp;quot;decorators&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&lt;br /&gt;
== Decorators ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;@handle_errors&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
* Decorator that catches exceptions and converts them to PodmanAPIError: &lt;br /&gt;
* - Catches asyncio.TimeoutError → 408 Timeout &lt;br /&gt;
* - Catches “not found” errors → 404 Not Found &lt;br /&gt;
* - Catches other exceptions → 500 Internal Server Error&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;@validate_json(required_fields=None)&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Decorator that validates JSON request data: &lt;br /&gt;
&lt;br /&gt;
* - Ensures request contains valid JSON &lt;br /&gt;
* - Checks for required fields &lt;br /&gt;
* - Returns 400 Bad Request if validation fails&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span id=&amp;quot;core-api-class&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&lt;br /&gt;
=== Core API Class ===&lt;br /&gt;
&lt;br /&gt;
===== PodmanAPI.__init__() =====&lt;br /&gt;
Initializes the API with a thread pool executor.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;PodmanAPI._get_client()&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Returns a new Podman client instance.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;PodmanAPI._run_async(func, timeout=30)&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Executes a function asynchronously in the thread pool with timeout.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;PodmanAPI._handle_exec_result(result)&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Helper that normalizes exec_run results to (exit_code, output_string).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span id=&amp;quot;container-management&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&lt;br /&gt;
== Container Management ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;create_container(config)&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Creates and starts a new container. &lt;br /&gt;
&lt;br /&gt;
* - Input: {image, name, command?, environment?, ports?, volumes?, user?} &lt;br /&gt;
* - Returns: {id, name, status}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;start_container(name)&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Starts an existing container. &lt;br /&gt;
&lt;br /&gt;
* - Input: Container name &lt;br /&gt;
* - Returns: {name, status}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;stop_container(name)&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Stops a running container. &lt;br /&gt;
&lt;br /&gt;
* - Input: Container name &lt;br /&gt;
* - Returns: {name, status}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;remove_container(name, force=False)&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Removes a container. &lt;br /&gt;
&lt;br /&gt;
* - Input: Container name, force flag &lt;br /&gt;
* - Returns: {name, removed}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;list_containers()&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Lists all containers (running and stopped). &lt;br /&gt;
&lt;br /&gt;
* - Returns: Array of {id, name, status, image}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;get_container_logs(name, tail=100)&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Retrieves container logs. &lt;br /&gt;
&lt;br /&gt;
* - Input: Container name, number of lines &lt;br /&gt;
* - Returns: {logs}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Host Operations&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;add_user(container_name, user_config)&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Creates a user inside a container. &lt;br /&gt;
&lt;br /&gt;
* - Input: Container name, {username, password?, shell?} &lt;br /&gt;
* - Returns: {username, created}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;add_file(container_name, file_config)&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Creates a file inside a container. &lt;br /&gt;
&lt;br /&gt;
* - Input: Container name, {dest_path, content} &lt;br /&gt;
* - Returns: {dest_path, size}&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;execute_command(container_name, command, user=None)&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Executes a command inside a container. &lt;br /&gt;
&lt;br /&gt;
* - Input: Container name, command string, optional user &lt;br /&gt;
* - Returns: {exit_code, output, success}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;shutdown()&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Gracefully shuts down the thread pool executor.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span id=&amp;quot;error-handlers&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&lt;br /&gt;
== Error Handlers ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;handle_podman_error(error)&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Flask error handler for PodmanAPIError exceptions. &lt;br /&gt;
&lt;br /&gt;
* - Returns structured JSON error response with timestamp&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;handle_internal_error(error)&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Flask error handler for 500 Internal Server Error. &lt;br /&gt;
&lt;br /&gt;
* - Logs error and returns generic error response&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span id=&amp;quot;api-routes&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&lt;br /&gt;
== API Routes ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;POST /api/v1/containers&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Creates a new container. Requires image and name fields.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;POST /api/v1/containers/&amp;lt;name&amp;gt;/start&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Starts the specified container.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;POST /api/v1/containers/&amp;lt;name&amp;gt;/stop&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Stops the specified container.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;DELETE /api/v1/containers/&amp;lt;name&amp;gt;?force=bool&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Removes the specified container. Optional force parameter.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;GET /api/v1/containers&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Lists all containers.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;GET /api/v1/containers/&amp;lt;name&amp;gt;/logs?tail=int&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Gets container logs. Optional tail parameter (default: 100).&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;POST /api/v1/containers/&amp;lt;name&amp;gt;/users&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Adds a user to the container. Requires username field.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;POST /api/v1/containers/&amp;lt;name&amp;gt;/files&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Adds a file to the container. Requires dest_path and content fields.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;POST /api/v1/containers/&amp;lt;name&amp;gt;/exec&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Executes a command in the container. Requires command field.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;GET /api/v1/health&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Health check endpoint. Returns Podman connection status.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;GET /api/v1/metrics&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Returns API metrics including uptime and thread pool status.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span id=&amp;quot;signal-handlers&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&lt;br /&gt;
== Signal Handlers ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;shutdown_handler(sig, frame)&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Handles SIGINT and SIGTERM signals for graceful shutdown. &lt;br /&gt;
&lt;br /&gt;
* - Logs signal details including source file and line number &lt;br /&gt;
* - Shuts down thread pool and exits cleanly&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span id=&amp;quot;response-format&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&lt;br /&gt;
== Response Format ==&lt;br /&gt;
&lt;br /&gt;
Successful requests will look like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;{&lt;br /&gt;
  &amp;amp;quot;success&amp;amp;quot;: true,&lt;br /&gt;
  &amp;amp;quot;container|user|file|execution&amp;amp;quot;: { ... },&lt;br /&gt;
  &amp;amp;quot;timestamp&amp;amp;quot;: &amp;amp;quot;ISO-8601-timestamp&amp;amp;quot;&lt;br /&gt;
}&amp;lt;/pre&amp;gt;&lt;br /&gt;
Errors will look like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;{&lt;br /&gt;
  &amp;amp;quot;success&amp;amp;quot;: false,&lt;br /&gt;
  &amp;amp;quot;error&amp;amp;quot;: {&lt;br /&gt;
    &amp;amp;quot;message&amp;amp;quot;: &amp;amp;quot;Error description&amp;amp;quot;,&lt;br /&gt;
    &amp;amp;quot;code&amp;amp;quot;: 400,&lt;br /&gt;
    &amp;amp;quot;type&amp;amp;quot;: &amp;amp;quot;ErrorType&amp;amp;quot;&lt;br /&gt;
  },&lt;br /&gt;
  &amp;amp;quot;timestamp&amp;amp;quot;: &amp;amp;quot;ISO-8601-timestamp&amp;amp;quot;&lt;br /&gt;
}&amp;lt;/pre&amp;gt;&lt;/div&gt;</summary>
		<author><name>Jack</name></author>
	</entry>
	<entry>
		<id>http://edurange.org/wiki/index.php?title=Podman_API&amp;diff=880</id>
		<title>Podman API</title>
		<link rel="alternate" type="text/html" href="http://edurange.org/wiki/index.php?title=Podman_API&amp;diff=880"/>
		<updated>2025-07-19T13:50:18Z</updated>

		<summary type="html">&lt;p&gt;Jack: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[Category:Demonstrations]]&lt;br /&gt;
In reference to: &lt;br /&gt;
https://github.com/edurange/demo-podman-api-extra&lt;br /&gt;
&lt;br /&gt;
More design thoughts and justifications to be added later, these are just the protocol specs.&lt;br /&gt;
&lt;br /&gt;
This design is not meant to be followed, it&#039;s just my first sketch at listing everything we might need.&lt;br /&gt;
&lt;br /&gt;
[TODO: Add the curl scripts I used for testing]&lt;br /&gt;
== Configuration ==&lt;br /&gt;
&lt;br /&gt;
===== Config =====&lt;br /&gt;
Configuration class that loads settings from environment variables. &lt;br /&gt;
&lt;br /&gt;
* - MAX_WORKERS: Thread pool size (default: 10) &lt;br /&gt;
* - DEBUG: Enable debug mode (default: False) &lt;br /&gt;
* - REQUEST_TIMEOUT: Operation timeout in seconds (default: 30)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span id=&amp;quot;custom-exceptions&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&lt;br /&gt;
== Custom Exceptions ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;PodmanAPIError(message, status_code=500)&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Custom exception for API errors with HTTP status codes.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span id=&amp;quot;decorators&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&lt;br /&gt;
== Decorators ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;@handle_errors&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
* Decorator that catches exceptions and converts them to PodmanAPIError: &lt;br /&gt;
* - Catches asyncio.TimeoutError → 408 Timeout &lt;br /&gt;
* - Catches “not found” errors → 404 Not Found &lt;br /&gt;
* - Catches other exceptions → 500 Internal Server Error&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;@validate_json(required_fields=None)&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Decorator that validates JSON request data: &lt;br /&gt;
&lt;br /&gt;
* - Ensures request contains valid JSON &lt;br /&gt;
* - Checks for required fields &lt;br /&gt;
* - Returns 400 Bad Request if validation fails&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span id=&amp;quot;core-api-class&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&lt;br /&gt;
=== Core API Class ===&lt;br /&gt;
&lt;br /&gt;
===== PodmanAPI.__init__() =====&lt;br /&gt;
Initializes the API with a thread pool executor.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;PodmanAPI._get_client()&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Returns a new Podman client instance.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;PodmanAPI._run_async(func, timeout=30)&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Executes a function asynchronously in the thread pool with timeout.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;PodmanAPI._handle_exec_result(result)&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Helper that normalizes exec_run results to (exit_code, output_string).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span id=&amp;quot;container-management&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&lt;br /&gt;
== Container Management ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;create_container(config)&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Creates and starts a new container. &lt;br /&gt;
&lt;br /&gt;
* - Input: {image, name, command?, environment?, ports?, volumes?, user?} &lt;br /&gt;
* - Returns: {id, name, status}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;start_container(name)&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Starts an existing container. &lt;br /&gt;
&lt;br /&gt;
* - Input: Container name &lt;br /&gt;
* - Returns: {name, status}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;stop_container(name)&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Stops a running container. &lt;br /&gt;
&lt;br /&gt;
* - Input: Container name &lt;br /&gt;
* - Returns: {name, status}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;remove_container(name, force=False)&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Removes a container. &lt;br /&gt;
&lt;br /&gt;
* - Input: Container name, force flag &lt;br /&gt;
* - Returns: {name, removed}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;list_containers()&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Lists all containers (running and stopped). &lt;br /&gt;
&lt;br /&gt;
* - Returns: Array of {id, name, status, image}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;get_container_logs(name, tail=100)&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Retrieves container logs. &lt;br /&gt;
&lt;br /&gt;
* - Input: Container name, number of lines &lt;br /&gt;
* - Returns: {logs}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Host Operations&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;add_user(container_name, user_config)&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Creates a user inside a container. &lt;br /&gt;
&lt;br /&gt;
* - Input: Container name, {username, password?, shell?} &lt;br /&gt;
* - Returns: {username, created}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;add_file(container_name, file_config)&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Creates a file inside a container. &lt;br /&gt;
&lt;br /&gt;
* - Input: Container name, {dest_path, content} &lt;br /&gt;
* - Returns: {dest_path, size}&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;execute_command(container_name, command, user=None)&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Executes a command inside a container. &lt;br /&gt;
&lt;br /&gt;
* - Input: Container name, command string, optional user &lt;br /&gt;
* - Returns: {exit_code, output, success}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;shutdown()&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Gracefully shuts down the thread pool executor.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span id=&amp;quot;error-handlers&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&lt;br /&gt;
== Error Handlers ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;handle_podman_error(error)&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Flask error handler for PodmanAPIError exceptions. &lt;br /&gt;
&lt;br /&gt;
* - Returns structured JSON error response with timestamp&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;handle_internal_error(error)&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Flask error handler for 500 Internal Server Error. &lt;br /&gt;
&lt;br /&gt;
* - Logs error and returns generic error response&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span id=&amp;quot;api-routes&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&lt;br /&gt;
== API Routes ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;POST /api/v1/containers&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Creates a new container. Requires image and name fields.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;POST /api/v1/containers/&amp;lt;name&amp;gt;/start&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Starts the specified container.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;POST /api/v1/containers/&amp;lt;name&amp;gt;/stop&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Stops the specified container.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;DELETE /api/v1/containers/&amp;lt;name&amp;gt;?force=bool&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Removes the specified container. Optional force parameter.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;GET /api/v1/containers&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Lists all containers.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;GET /api/v1/containers/&amp;lt;name&amp;gt;/logs?tail=int&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Gets container logs. Optional tail parameter (default: 100).&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;POST /api/v1/containers/&amp;lt;name&amp;gt;/users&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Adds a user to the container. Requires username field.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;POST /api/v1/containers/&amp;lt;name&amp;gt;/files&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Adds a file to the container. Requires dest_path and content fields.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;POST /api/v1/containers/&amp;lt;name&amp;gt;/exec&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Executes a command in the container. Requires command field.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;GET /api/v1/health&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Health check endpoint. Returns Podman connection status.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;GET /api/v1/metrics&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Returns API metrics including uptime and thread pool status.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span id=&amp;quot;signal-handlers&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&lt;br /&gt;
== Signal Handlers ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;shutdown_handler(sig, frame)&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Handles SIGINT and SIGTERM signals for graceful shutdown. &lt;br /&gt;
&lt;br /&gt;
* - Logs signal details including source file and line number &lt;br /&gt;
* - Shuts down thread pool and exits cleanly&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span id=&amp;quot;response-format&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&lt;br /&gt;
== Response Format ==&lt;br /&gt;
&lt;br /&gt;
Successful requests will look like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;{&lt;br /&gt;
  &amp;amp;quot;success&amp;amp;quot;: true,&lt;br /&gt;
  &amp;amp;quot;container|user|file|execution&amp;amp;quot;: { ... },&lt;br /&gt;
  &amp;amp;quot;timestamp&amp;amp;quot;: &amp;amp;quot;ISO-8601-timestamp&amp;amp;quot;&lt;br /&gt;
}&amp;lt;/pre&amp;gt;&lt;br /&gt;
Errors will look like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;{&lt;br /&gt;
  &amp;amp;quot;success&amp;amp;quot;: false,&lt;br /&gt;
  &amp;amp;quot;error&amp;amp;quot;: {&lt;br /&gt;
    &amp;amp;quot;message&amp;amp;quot;: &amp;amp;quot;Error description&amp;amp;quot;,&lt;br /&gt;
    &amp;amp;quot;code&amp;amp;quot;: 400,&lt;br /&gt;
    &amp;amp;quot;type&amp;amp;quot;: &amp;amp;quot;ErrorType&amp;amp;quot;&lt;br /&gt;
  },&lt;br /&gt;
  &amp;amp;quot;timestamp&amp;amp;quot;: &amp;amp;quot;ISO-8601-timestamp&amp;amp;quot;&lt;br /&gt;
}&amp;lt;/pre&amp;gt;&lt;/div&gt;</summary>
		<author><name>Jack</name></author>
	</entry>
	<entry>
		<id>http://edurange.org/wiki/index.php?title=Podman_API&amp;diff=879</id>
		<title>Podman API</title>
		<link rel="alternate" type="text/html" href="http://edurange.org/wiki/index.php?title=Podman_API&amp;diff=879"/>
		<updated>2025-07-19T13:47:59Z</updated>

		<summary type="html">&lt;p&gt;Jack: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[Category:Demonstrations]]&lt;br /&gt;
In reference to: &lt;br /&gt;
https://github.com/edurange/demo-podman-api-extra&lt;br /&gt;
&lt;br /&gt;
More design thoughts and justifications to be added later, these are just the protocol specs.&lt;br /&gt;
&lt;br /&gt;
This design is not meant to be followed, it&#039;s just my first sketch at listing everything we might need.&lt;br /&gt;
&lt;br /&gt;
[TODO: Add the curl scripts I used for testing]&lt;br /&gt;
== Configuration ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span id=&amp;quot;config&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&lt;br /&gt;
=== Config ===&lt;br /&gt;
&lt;br /&gt;
Configuration class that loads settings from environment variables. &lt;br /&gt;
&lt;br /&gt;
* - MAX_WORKERS: Thread pool size (default: 10) &lt;br /&gt;
* - DEBUG: Enable debug mode (default: False) &lt;br /&gt;
* - REQUEST_TIMEOUT: Operation timeout in seconds (default: 30)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span id=&amp;quot;custom-exceptions&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&lt;br /&gt;
== Custom Exceptions ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;PodmanAPIError(message, status_code=500)&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Custom exception for API errors with HTTP status codes.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span id=&amp;quot;decorators&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&lt;br /&gt;
== Decorators ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;@handle_errors&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
* Decorator that catches exceptions and converts them to PodmanAPIError: &lt;br /&gt;
* - Catches asyncio.TimeoutError → 408 Timeout &lt;br /&gt;
* - Catches “not found” errors → 404 Not Found &lt;br /&gt;
* - Catches other exceptions → 500 Internal Server Error&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;@validate_json(required_fields=None)&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Decorator that validates JSON request data: &lt;br /&gt;
&lt;br /&gt;
* - Ensures request contains valid JSON &lt;br /&gt;
* - Checks for required fields &lt;br /&gt;
* - Returns 400 Bad Request if validation fails&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span id=&amp;quot;core-api-class&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&lt;br /&gt;
=== Core API Class ===&lt;br /&gt;
&lt;br /&gt;
===== PodmanAPI.__init__() =====&lt;br /&gt;
Initializes the API with a thread pool executor.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;PodmanAPI._get_client()&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Returns a new Podman client instance.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;PodmanAPI._run_async(func, timeout=30)&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Executes a function asynchronously in the thread pool with timeout.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;PodmanAPI._handle_exec_result(result)&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Helper that normalizes exec_run results to (exit_code, output_string).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span id=&amp;quot;container-management&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&lt;br /&gt;
== Container Management ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;create_container(config)&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Creates and starts a new container. &lt;br /&gt;
&lt;br /&gt;
* - Input: {image, name, command?, environment?, ports?, volumes?, user?} &lt;br /&gt;
* - Returns: {id, name, status}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;start_container(name)&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Starts an existing container. &lt;br /&gt;
&lt;br /&gt;
* - Input: Container name &lt;br /&gt;
* - Returns: {name, status}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;stop_container(name)&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Stops a running container. &lt;br /&gt;
&lt;br /&gt;
* - Input: Container name &lt;br /&gt;
* - Returns: {name, status}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;remove_container(name, force=False)&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Removes a container. &lt;br /&gt;
&lt;br /&gt;
* - Input: Container name, force flag &lt;br /&gt;
* - Returns: {name, removed}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;list_containers()&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Lists all containers (running and stopped). &lt;br /&gt;
&lt;br /&gt;
* - Returns: Array of {id, name, status, image}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;get_container_logs(name, tail=100)&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Retrieves container logs. &lt;br /&gt;
&lt;br /&gt;
* - Input: Container name, number of lines &lt;br /&gt;
* - Returns: {logs}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Host Operations&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;add_user(container_name, user_config)&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Creates a user inside a container. &lt;br /&gt;
&lt;br /&gt;
* - Input: Container name, {username, password?, shell?} &lt;br /&gt;
* - Returns: {username, created}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;add_file(container_name, file_config)&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Creates a file inside a container. &lt;br /&gt;
&lt;br /&gt;
* - Input: Container name, {dest_path, content} &lt;br /&gt;
* - Returns: {dest_path, size}&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;execute_command(container_name, command, user=None)&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Executes a command inside a container. &lt;br /&gt;
&lt;br /&gt;
* - Input: Container name, command string, optional user &lt;br /&gt;
* - Returns: {exit_code, output, success}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;shutdown()&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Gracefully shuts down the thread pool executor.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span id=&amp;quot;error-handlers&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&lt;br /&gt;
== Error Handlers ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;handle_podman_error(error)&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Flask error handler for PodmanAPIError exceptions. &lt;br /&gt;
&lt;br /&gt;
* - Returns structured JSON error response with timestamp&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;handle_internal_error(error)&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Flask error handler for 500 Internal Server Error. &lt;br /&gt;
&lt;br /&gt;
* - Logs error and returns generic error response&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span id=&amp;quot;api-routes&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&lt;br /&gt;
== API Routes ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;POST /api/v1/containers&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Creates a new container. Requires image and name fields.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;POST /api/v1/containers/&amp;lt;name&amp;gt;/start&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Starts the specified container.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;POST /api/v1/containers/&amp;lt;name&amp;gt;/stop&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Stops the specified container.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;DELETE /api/v1/containers/&amp;lt;name&amp;gt;?force=bool&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Removes the specified container. Optional force parameter.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;GET /api/v1/containers&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Lists all containers.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;GET /api/v1/containers/&amp;lt;name&amp;gt;/logs?tail=int&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Gets container logs. Optional tail parameter (default: 100).&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;POST /api/v1/containers/&amp;lt;name&amp;gt;/users&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Adds a user to the container. Requires username field.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;POST /api/v1/containers/&amp;lt;name&amp;gt;/files&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Adds a file to the container. Requires dest_path and content fields.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;POST /api/v1/containers/&amp;lt;name&amp;gt;/exec&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Executes a command in the container. Requires command field.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;GET /api/v1/health&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Health check endpoint. Returns Podman connection status.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;GET /api/v1/metrics&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Returns API metrics including uptime and thread pool status.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span id=&amp;quot;signal-handlers&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&lt;br /&gt;
== Signal Handlers ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;shutdown_handler(sig, frame)&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Handles SIGINT and SIGTERM signals for graceful shutdown. &lt;br /&gt;
&lt;br /&gt;
* - Logs signal details including source file and line number &lt;br /&gt;
* - Shuts down thread pool and exits cleanly&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span id=&amp;quot;response-format&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&lt;br /&gt;
== Response Format ==&lt;br /&gt;
&lt;br /&gt;
Successful requests will look like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;{&lt;br /&gt;
  &amp;amp;quot;success&amp;amp;quot;: true,&lt;br /&gt;
  &amp;amp;quot;container|user|file|execution&amp;amp;quot;: { ... },&lt;br /&gt;
  &amp;amp;quot;timestamp&amp;amp;quot;: &amp;amp;quot;ISO-8601-timestamp&amp;amp;quot;&lt;br /&gt;
}&amp;lt;/pre&amp;gt;&lt;br /&gt;
Errors will look like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;{&lt;br /&gt;
  &amp;amp;quot;success&amp;amp;quot;: false,&lt;br /&gt;
  &amp;amp;quot;error&amp;amp;quot;: {&lt;br /&gt;
    &amp;amp;quot;message&amp;amp;quot;: &amp;amp;quot;Error description&amp;amp;quot;,&lt;br /&gt;
    &amp;amp;quot;code&amp;amp;quot;: 400,&lt;br /&gt;
    &amp;amp;quot;type&amp;amp;quot;: &amp;amp;quot;ErrorType&amp;amp;quot;&lt;br /&gt;
  },&lt;br /&gt;
  &amp;amp;quot;timestamp&amp;amp;quot;: &amp;amp;quot;ISO-8601-timestamp&amp;amp;quot;&lt;br /&gt;
}&amp;lt;/pre&amp;gt;&lt;/div&gt;</summary>
		<author><name>Jack</name></author>
	</entry>
	<entry>
		<id>http://edurange.org/wiki/index.php?title=Podman_API&amp;diff=878</id>
		<title>Podman API</title>
		<link rel="alternate" type="text/html" href="http://edurange.org/wiki/index.php?title=Podman_API&amp;diff=878"/>
		<updated>2025-07-19T13:44:18Z</updated>

		<summary type="html">&lt;p&gt;Jack: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[Category:Demonstrations]]&lt;br /&gt;
In reference to: &lt;br /&gt;
https://github.com/edurange/demo-podman-api-extra&lt;br /&gt;
&lt;br /&gt;
More design thoughts and justifications to be added later, these are just the protocol specs.&lt;br /&gt;
&lt;br /&gt;
This design is not meant to be followed, it&#039;s just my first sketch at listing everything we might need.&lt;br /&gt;
&lt;br /&gt;
[TODO: Add the curl scripts I used for testing]&lt;br /&gt;
== Configuration ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span id=&amp;quot;config&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&lt;br /&gt;
=== Config ===&lt;br /&gt;
&lt;br /&gt;
Configuration class that loads settings from environment variables. &lt;br /&gt;
&lt;br /&gt;
* - MAX_WORKERS: Thread pool size (default: 10) &lt;br /&gt;
* - DEBUG: Enable debug mode (default: False) &lt;br /&gt;
* - REQUEST_TIMEOUT: Operation timeout in seconds (default: 30)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span id=&amp;quot;custom-exceptions&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&lt;br /&gt;
== Custom Exceptions ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;PodmanAPIError(message, status_code=500)&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Custom exception for API errors with HTTP status codes.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span id=&amp;quot;decorators&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&lt;br /&gt;
== Decorators ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;@handle_errors&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
* Decorator that catches exceptions and converts them to PodmanAPIError: &lt;br /&gt;
* - Catches asyncio.TimeoutError → 408 Timeout &lt;br /&gt;
* - Catches “not found” errors → 404 Not Found &lt;br /&gt;
* - Catches other exceptions → 500 Internal Server Error&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;@validate_json(required_fields=None)&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Decorator that validates JSON request data: &lt;br /&gt;
&lt;br /&gt;
* - Ensures request contains valid JSON &lt;br /&gt;
* - Checks for required fields &lt;br /&gt;
* - Returns 400 Bad Request if validation fails&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span id=&amp;quot;core-api-class&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&lt;br /&gt;
=== Core API Class ===&lt;br /&gt;
&lt;br /&gt;
===== PodmanAPI.__init__() =====&lt;br /&gt;
Initializes the API with a thread pool executor.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;PodmanAPI._get_client()&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Returns a new Podman client instance.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;PodmanAPI._run_async(func, timeout=30)&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Executes a function asynchronously in the thread pool with timeout.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;PodmanAPI._handle_exec_result(result)&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Helper that normalizes exec_run results to (exit_code, output_string).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span id=&amp;quot;container-management&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&lt;br /&gt;
== Container Management ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;create_container(config)&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Creates and starts a new container. &lt;br /&gt;
&lt;br /&gt;
* - Input: {image, name, command?, environment?, ports?, volumes?, user?} &lt;br /&gt;
* - Returns: {id, name, status}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;start_container(name)&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Starts an existing container. &lt;br /&gt;
&lt;br /&gt;
* - Input: Container name &lt;br /&gt;
* - Returns: {name, status}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;stop_container(name)&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Stops a running container. &lt;br /&gt;
&lt;br /&gt;
* - Input: Container name &lt;br /&gt;
* - Returns: {name, status}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;remove_container(name, force=False)&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Removes a container. &lt;br /&gt;
&lt;br /&gt;
* - Input: Container name, force flag &lt;br /&gt;
* - Returns: {name, removed}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;list_containers()&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Lists all containers (running and stopped). &lt;br /&gt;
&lt;br /&gt;
* - Returns: Array of {id, name, status, image}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;get_container_logs(name, tail=100)&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Retrieves container logs. &lt;br /&gt;
&lt;br /&gt;
* - Input: Container name, number of lines &lt;br /&gt;
* - Returns: {logs}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Host Operations&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;add_user(container_name, user_config)&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Creates a user inside a container. &lt;br /&gt;
&lt;br /&gt;
* - Input: Container name, {username, password?, shell?} &lt;br /&gt;
* - Returns: {username, created}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;add_file(container_name, file_config)&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Creates a file inside a container. &lt;br /&gt;
&lt;br /&gt;
* - Input: Container name, {dest_path, content} &lt;br /&gt;
* - Returns: {dest_path, size}&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;execute_command(container_name, command, user=None)&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Executes a command inside a container. &lt;br /&gt;
&lt;br /&gt;
* - Input: Container name, command string, optional user &lt;br /&gt;
* - Returns: {exit_code, output, success}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;shutdown()&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Gracefully shuts down the thread pool executor.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span id=&amp;quot;error-handlers&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&lt;br /&gt;
== Error Handlers ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;handle_podman_error(error)&#039;&#039;&#039; Flask error handler for PodmanAPIError exceptions. - Returns structured JSON error response with timestamp&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;handle_internal_error(error)&#039;&#039;&#039; Flask error handler for 500 Internal Server Error. - Logs error and returns generic error response&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span id=&amp;quot;api-routes&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&lt;br /&gt;
== API Routes ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;POST /api/v1/containers&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Creates a new container. Requires image and name fields.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;POST /api/v1/containers/&amp;lt;name&amp;gt;/start&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Starts the specified container.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;POST /api/v1/containers/&amp;lt;name&amp;gt;/stop&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Stops the specified container.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;DELETE /api/v1/containers/&amp;lt;name&amp;gt;?force=bool&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Removes the specified container. Optional force parameter.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;GET /api/v1/containers&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Lists all containers.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;GET /api/v1/containers/&amp;lt;name&amp;gt;/logs?tail=int&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Gets container logs. Optional tail parameter (default: 100).&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;POST /api/v1/containers/&amp;lt;name&amp;gt;/users&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Adds a user to the container. Requires username field.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;POST /api/v1/containers/&amp;lt;name&amp;gt;/files&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Adds a file to the container. Requires dest_path and content fields.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;POST /api/v1/containers/&amp;lt;name&amp;gt;/exec&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Executes a command in the container. Requires command field.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;GET /api/v1/health&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Health check endpoint. Returns Podman connection status.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;GET /api/v1/metrics&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Returns API metrics including uptime and thread pool status.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span id=&amp;quot;signal-handlers&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&lt;br /&gt;
== Signal Handlers ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;shutdown_handler(sig, frame)&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Handles SIGINT and SIGTERM signals for graceful shutdown. &lt;br /&gt;
&lt;br /&gt;
* - Logs signal details including source file and line number &lt;br /&gt;
* - Shuts down thread pool and exits cleanly&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span id=&amp;quot;response-format&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&lt;br /&gt;
== Response Format ==&lt;br /&gt;
&lt;br /&gt;
Successful requests will look like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;{&lt;br /&gt;
  &amp;amp;quot;success&amp;amp;quot;: true,&lt;br /&gt;
  &amp;amp;quot;container|user|file|execution&amp;amp;quot;: { ... },&lt;br /&gt;
  &amp;amp;quot;timestamp&amp;amp;quot;: &amp;amp;quot;ISO-8601-timestamp&amp;amp;quot;&lt;br /&gt;
}&amp;lt;/pre&amp;gt;&lt;br /&gt;
Errors will look like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;{&lt;br /&gt;
  &amp;amp;quot;success&amp;amp;quot;: false,&lt;br /&gt;
  &amp;amp;quot;error&amp;amp;quot;: {&lt;br /&gt;
    &amp;amp;quot;message&amp;amp;quot;: &amp;amp;quot;Error description&amp;amp;quot;,&lt;br /&gt;
    &amp;amp;quot;code&amp;amp;quot;: 400,&lt;br /&gt;
    &amp;amp;quot;type&amp;amp;quot;: &amp;amp;quot;ErrorType&amp;amp;quot;&lt;br /&gt;
  },&lt;br /&gt;
  &amp;amp;quot;timestamp&amp;amp;quot;: &amp;amp;quot;ISO-8601-timestamp&amp;amp;quot;&lt;br /&gt;
}&amp;lt;/pre&amp;gt;&lt;/div&gt;</summary>
		<author><name>Jack</name></author>
	</entry>
	<entry>
		<id>http://edurange.org/wiki/index.php?title=Podman_API&amp;diff=877</id>
		<title>Podman API</title>
		<link rel="alternate" type="text/html" href="http://edurange.org/wiki/index.php?title=Podman_API&amp;diff=877"/>
		<updated>2025-07-19T13:43:52Z</updated>

		<summary type="html">&lt;p&gt;Jack: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
[Category:Demonstrations]]&lt;br /&gt;
In reference to: &lt;br /&gt;
https://github.com/edurange/demo-podman-api-extra&lt;br /&gt;
&lt;br /&gt;
More design thoughts and justifications to be added later, these are just the protocol specs.&lt;br /&gt;
&lt;br /&gt;
This design is not meant to be followed, it&#039;s just my first sketch at listing everything we might need.&lt;br /&gt;
&lt;br /&gt;
[TODO: Add the curl scripts I used for testing]&lt;br /&gt;
== Configuration ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span id=&amp;quot;config&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&lt;br /&gt;
=== Config ===&lt;br /&gt;
&lt;br /&gt;
Configuration class that loads settings from environment variables. &lt;br /&gt;
&lt;br /&gt;
* - MAX_WORKERS: Thread pool size (default: 10) &lt;br /&gt;
* - DEBUG: Enable debug mode (default: False) &lt;br /&gt;
* - REQUEST_TIMEOUT: Operation timeout in seconds (default: 30)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span id=&amp;quot;custom-exceptions&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&lt;br /&gt;
== Custom Exceptions ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;PodmanAPIError(message, status_code=500)&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Custom exception for API errors with HTTP status codes.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span id=&amp;quot;decorators&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&lt;br /&gt;
== Decorators ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;@handle_errors&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
* Decorator that catches exceptions and converts them to PodmanAPIError: &lt;br /&gt;
* - Catches asyncio.TimeoutError → 408 Timeout &lt;br /&gt;
* - Catches “not found” errors → 404 Not Found &lt;br /&gt;
* - Catches other exceptions → 500 Internal Server Error&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;@validate_json(required_fields=None)&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Decorator that validates JSON request data: &lt;br /&gt;
&lt;br /&gt;
* - Ensures request contains valid JSON &lt;br /&gt;
* - Checks for required fields &lt;br /&gt;
* - Returns 400 Bad Request if validation fails&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span id=&amp;quot;core-api-class&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&lt;br /&gt;
=== Core API Class ===&lt;br /&gt;
&lt;br /&gt;
===== PodmanAPI.__init__() =====&lt;br /&gt;
Initializes the API with a thread pool executor.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;PodmanAPI._get_client()&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Returns a new Podman client instance.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;PodmanAPI._run_async(func, timeout=30)&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Executes a function asynchronously in the thread pool with timeout.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;PodmanAPI._handle_exec_result(result)&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Helper that normalizes exec_run results to (exit_code, output_string).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span id=&amp;quot;container-management&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&lt;br /&gt;
== Container Management ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;create_container(config)&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Creates and starts a new container. &lt;br /&gt;
&lt;br /&gt;
* - Input: {image, name, command?, environment?, ports?, volumes?, user?} &lt;br /&gt;
* - Returns: {id, name, status}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;start_container(name)&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Starts an existing container. &lt;br /&gt;
&lt;br /&gt;
* - Input: Container name &lt;br /&gt;
* - Returns: {name, status}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;stop_container(name)&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Stops a running container. &lt;br /&gt;
&lt;br /&gt;
* - Input: Container name &lt;br /&gt;
* - Returns: {name, status}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;remove_container(name, force=False)&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Removes a container. &lt;br /&gt;
&lt;br /&gt;
* - Input: Container name, force flag &lt;br /&gt;
* - Returns: {name, removed}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;list_containers()&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Lists all containers (running and stopped). &lt;br /&gt;
&lt;br /&gt;
* - Returns: Array of {id, name, status, image}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;get_container_logs(name, tail=100)&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Retrieves container logs. &lt;br /&gt;
&lt;br /&gt;
* - Input: Container name, number of lines &lt;br /&gt;
* - Returns: {logs}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Host Operations&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;add_user(container_name, user_config)&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Creates a user inside a container. &lt;br /&gt;
&lt;br /&gt;
* - Input: Container name, {username, password?, shell?} &lt;br /&gt;
* - Returns: {username, created}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;add_file(container_name, file_config)&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Creates a file inside a container. &lt;br /&gt;
&lt;br /&gt;
* - Input: Container name, {dest_path, content} &lt;br /&gt;
* - Returns: {dest_path, size}&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;execute_command(container_name, command, user=None)&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Executes a command inside a container. &lt;br /&gt;
&lt;br /&gt;
* - Input: Container name, command string, optional user &lt;br /&gt;
* - Returns: {exit_code, output, success}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;shutdown()&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Gracefully shuts down the thread pool executor.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span id=&amp;quot;error-handlers&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&lt;br /&gt;
== Error Handlers ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;handle_podman_error(error)&#039;&#039;&#039; Flask error handler for PodmanAPIError exceptions. - Returns structured JSON error response with timestamp&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;handle_internal_error(error)&#039;&#039;&#039; Flask error handler for 500 Internal Server Error. - Logs error and returns generic error response&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span id=&amp;quot;api-routes&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&lt;br /&gt;
== API Routes ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;POST /api/v1/containers&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Creates a new container. Requires image and name fields.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;POST /api/v1/containers/&amp;lt;name&amp;gt;/start&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Starts the specified container.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;POST /api/v1/containers/&amp;lt;name&amp;gt;/stop&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Stops the specified container.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;DELETE /api/v1/containers/&amp;lt;name&amp;gt;?force=bool&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Removes the specified container. Optional force parameter.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;GET /api/v1/containers&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Lists all containers.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;GET /api/v1/containers/&amp;lt;name&amp;gt;/logs?tail=int&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Gets container logs. Optional tail parameter (default: 100).&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;POST /api/v1/containers/&amp;lt;name&amp;gt;/users&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Adds a user to the container. Requires username field.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;POST /api/v1/containers/&amp;lt;name&amp;gt;/files&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Adds a file to the container. Requires dest_path and content fields.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;POST /api/v1/containers/&amp;lt;name&amp;gt;/exec&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Executes a command in the container. Requires command field.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;GET /api/v1/health&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Health check endpoint. Returns Podman connection status.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;GET /api/v1/metrics&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Returns API metrics including uptime and thread pool status.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span id=&amp;quot;signal-handlers&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&lt;br /&gt;
== Signal Handlers ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;shutdown_handler(sig, frame)&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Handles SIGINT and SIGTERM signals for graceful shutdown. &lt;br /&gt;
&lt;br /&gt;
* - Logs signal details including source file and line number &lt;br /&gt;
* - Shuts down thread pool and exits cleanly&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span id=&amp;quot;response-format&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&lt;br /&gt;
== Response Format ==&lt;br /&gt;
&lt;br /&gt;
Successful requests will look like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;{&lt;br /&gt;
  &amp;amp;quot;success&amp;amp;quot;: true,&lt;br /&gt;
  &amp;amp;quot;container|user|file|execution&amp;amp;quot;: { ... },&lt;br /&gt;
  &amp;amp;quot;timestamp&amp;amp;quot;: &amp;amp;quot;ISO-8601-timestamp&amp;amp;quot;&lt;br /&gt;
}&amp;lt;/pre&amp;gt;&lt;br /&gt;
Errors will look like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;{&lt;br /&gt;
  &amp;amp;quot;success&amp;amp;quot;: false,&lt;br /&gt;
  &amp;amp;quot;error&amp;amp;quot;: {&lt;br /&gt;
    &amp;amp;quot;message&amp;amp;quot;: &amp;amp;quot;Error description&amp;amp;quot;,&lt;br /&gt;
    &amp;amp;quot;code&amp;amp;quot;: 400,&lt;br /&gt;
    &amp;amp;quot;type&amp;amp;quot;: &amp;amp;quot;ErrorType&amp;amp;quot;&lt;br /&gt;
  },&lt;br /&gt;
  &amp;amp;quot;timestamp&amp;amp;quot;: &amp;amp;quot;ISO-8601-timestamp&amp;amp;quot;&lt;br /&gt;
}&amp;lt;/pre&amp;gt;&lt;/div&gt;</summary>
		<author><name>Jack</name></author>
	</entry>
	<entry>
		<id>http://edurange.org/wiki/index.php?title=Podman_API&amp;diff=876</id>
		<title>Podman API</title>
		<link rel="alternate" type="text/html" href="http://edurange.org/wiki/index.php?title=Podman_API&amp;diff=876"/>
		<updated>2025-07-19T13:42:46Z</updated>

		<summary type="html">&lt;p&gt;Jack: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
&amp;lt;nowiki&amp;gt;[[Category:Demonstrations]]&amp;lt;/nowiki&amp;gt;  &lt;br /&gt;
&lt;br /&gt;
In reference to: &lt;br /&gt;
&lt;br /&gt;
https://github.com/edurange/demo-podman-api-extra&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
More design thoughts and justifications to be added later, these are just the protocol specs.&lt;br /&gt;
&lt;br /&gt;
This design is not meant to be followed, it&#039;s just my first sketch at listing everything we might need.&lt;br /&gt;
&lt;br /&gt;
[TODO: Add the curl scripts I used for testing]&lt;br /&gt;
== Configuration ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span id=&amp;quot;config&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&lt;br /&gt;
=== Config ===&lt;br /&gt;
&lt;br /&gt;
Configuration class that loads settings from environment variables. &lt;br /&gt;
&lt;br /&gt;
* - MAX_WORKERS: Thread pool size (default: 10) &lt;br /&gt;
* - DEBUG: Enable debug mode (default: False) &lt;br /&gt;
* - REQUEST_TIMEOUT: Operation timeout in seconds (default: 30)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span id=&amp;quot;custom-exceptions&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&lt;br /&gt;
== Custom Exceptions ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;PodmanAPIError(message, status_code=500)&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Custom exception for API errors with HTTP status codes.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span id=&amp;quot;decorators&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&lt;br /&gt;
== Decorators ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;@handle_errors&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
* Decorator that catches exceptions and converts them to PodmanAPIError: &lt;br /&gt;
* - Catches asyncio.TimeoutError → 408 Timeout &lt;br /&gt;
* - Catches “not found” errors → 404 Not Found &lt;br /&gt;
* - Catches other exceptions → 500 Internal Server Error&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;@validate_json(required_fields=None)&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Decorator that validates JSON request data: &lt;br /&gt;
&lt;br /&gt;
* - Ensures request contains valid JSON &lt;br /&gt;
* - Checks for required fields &lt;br /&gt;
* - Returns 400 Bad Request if validation fails&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span id=&amp;quot;core-api-class&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&lt;br /&gt;
=== Core API Class ===&lt;br /&gt;
&lt;br /&gt;
===== PodmanAPI.__init__() =====&lt;br /&gt;
Initializes the API with a thread pool executor.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;PodmanAPI._get_client()&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Returns a new Podman client instance.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;PodmanAPI._run_async(func, timeout=30)&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Executes a function asynchronously in the thread pool with timeout.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;PodmanAPI._handle_exec_result(result)&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Helper that normalizes exec_run results to (exit_code, output_string).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span id=&amp;quot;container-management&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&lt;br /&gt;
== Container Management ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;create_container(config)&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Creates and starts a new container. &lt;br /&gt;
&lt;br /&gt;
* - Input: {image, name, command?, environment?, ports?, volumes?, user?} &lt;br /&gt;
* - Returns: {id, name, status}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;start_container(name)&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Starts an existing container. &lt;br /&gt;
&lt;br /&gt;
* - Input: Container name &lt;br /&gt;
* - Returns: {name, status}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;stop_container(name)&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Stops a running container. &lt;br /&gt;
&lt;br /&gt;
* - Input: Container name &lt;br /&gt;
* - Returns: {name, status}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;remove_container(name, force=False)&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Removes a container. &lt;br /&gt;
&lt;br /&gt;
* - Input: Container name, force flag &lt;br /&gt;
* - Returns: {name, removed}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;list_containers()&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Lists all containers (running and stopped). &lt;br /&gt;
&lt;br /&gt;
* - Returns: Array of {id, name, status, image}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;get_container_logs(name, tail=100)&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Retrieves container logs. &lt;br /&gt;
&lt;br /&gt;
* - Input: Container name, number of lines &lt;br /&gt;
* - Returns: {logs}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Host Operations&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;add_user(container_name, user_config)&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Creates a user inside a container. &lt;br /&gt;
&lt;br /&gt;
* - Input: Container name, {username, password?, shell?} &lt;br /&gt;
* - Returns: {username, created}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;add_file(container_name, file_config)&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Creates a file inside a container. &lt;br /&gt;
&lt;br /&gt;
* - Input: Container name, {dest_path, content} &lt;br /&gt;
* - Returns: {dest_path, size}&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;execute_command(container_name, command, user=None)&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Executes a command inside a container. &lt;br /&gt;
&lt;br /&gt;
* - Input: Container name, command string, optional user &lt;br /&gt;
* - Returns: {exit_code, output, success}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;shutdown()&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Gracefully shuts down the thread pool executor.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span id=&amp;quot;error-handlers&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&lt;br /&gt;
== Error Handlers ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;handle_podman_error(error)&#039;&#039;&#039; Flask error handler for PodmanAPIError exceptions. - Returns structured JSON error response with timestamp&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;handle_internal_error(error)&#039;&#039;&#039; Flask error handler for 500 Internal Server Error. - Logs error and returns generic error response&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span id=&amp;quot;api-routes&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&lt;br /&gt;
== API Routes ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;POST /api/v1/containers&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Creates a new container. Requires image and name fields.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;POST /api/v1/containers/&amp;lt;name&amp;gt;/start&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Starts the specified container.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;POST /api/v1/containers/&amp;lt;name&amp;gt;/stop&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Stops the specified container.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;DELETE /api/v1/containers/&amp;lt;name&amp;gt;?force=bool&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Removes the specified container. Optional force parameter.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;GET /api/v1/containers&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Lists all containers.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;GET /api/v1/containers/&amp;lt;name&amp;gt;/logs?tail=int&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Gets container logs. Optional tail parameter (default: 100).&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;POST /api/v1/containers/&amp;lt;name&amp;gt;/users&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Adds a user to the container. Requires username field.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;POST /api/v1/containers/&amp;lt;name&amp;gt;/files&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Adds a file to the container. Requires dest_path and content fields.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;POST /api/v1/containers/&amp;lt;name&amp;gt;/exec&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Executes a command in the container. Requires command field.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;GET /api/v1/health&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Health check endpoint. Returns Podman connection status.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;GET /api/v1/metrics&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Returns API metrics including uptime and thread pool status.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span id=&amp;quot;signal-handlers&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&lt;br /&gt;
== Signal Handlers ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;shutdown_handler(sig, frame)&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Handles SIGINT and SIGTERM signals for graceful shutdown. &lt;br /&gt;
&lt;br /&gt;
* - Logs signal details including source file and line number &lt;br /&gt;
* - Shuts down thread pool and exits cleanly&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span id=&amp;quot;response-format&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&lt;br /&gt;
== Response Format ==&lt;br /&gt;
&lt;br /&gt;
Successful requests will look like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;{&lt;br /&gt;
  &amp;amp;quot;success&amp;amp;quot;: true,&lt;br /&gt;
  &amp;amp;quot;container|user|file|execution&amp;amp;quot;: { ... },&lt;br /&gt;
  &amp;amp;quot;timestamp&amp;amp;quot;: &amp;amp;quot;ISO-8601-timestamp&amp;amp;quot;&lt;br /&gt;
}&amp;lt;/pre&amp;gt;&lt;br /&gt;
Errors will look like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;{&lt;br /&gt;
  &amp;amp;quot;success&amp;amp;quot;: false,&lt;br /&gt;
  &amp;amp;quot;error&amp;amp;quot;: {&lt;br /&gt;
    &amp;amp;quot;message&amp;amp;quot;: &amp;amp;quot;Error description&amp;amp;quot;,&lt;br /&gt;
    &amp;amp;quot;code&amp;amp;quot;: 400,&lt;br /&gt;
    &amp;amp;quot;type&amp;amp;quot;: &amp;amp;quot;ErrorType&amp;amp;quot;&lt;br /&gt;
  },&lt;br /&gt;
  &amp;amp;quot;timestamp&amp;amp;quot;: &amp;amp;quot;ISO-8601-timestamp&amp;amp;quot;&lt;br /&gt;
}&amp;lt;/pre&amp;gt;&lt;/div&gt;</summary>
		<author><name>Jack</name></author>
	</entry>
	<entry>
		<id>http://edurange.org/wiki/index.php?title=Podman_API&amp;diff=875</id>
		<title>Podman API</title>
		<link rel="alternate" type="text/html" href="http://edurange.org/wiki/index.php?title=Podman_API&amp;diff=875"/>
		<updated>2025-07-19T13:38:27Z</updated>

		<summary type="html">&lt;p&gt;Jack: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
In reference to: &lt;br /&gt;
&lt;br /&gt;
https://github.com/edurange/demo-podman-api-extra&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
More design thoughts and justifications to be added later, these are just the protocol specs.&lt;br /&gt;
&lt;br /&gt;
This design is not meant to be followed, it&#039;s just my first sketch at listing everything we might need.&lt;br /&gt;
&lt;br /&gt;
[TODO: Add the curl scripts I used for testing]&lt;br /&gt;
== Configuration ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span id=&amp;quot;config&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&lt;br /&gt;
=== Config ===&lt;br /&gt;
&lt;br /&gt;
Configuration class that loads settings from environment variables. &lt;br /&gt;
&lt;br /&gt;
* - MAX_WORKERS: Thread pool size (default: 10) &lt;br /&gt;
* - DEBUG: Enable debug mode (default: False) &lt;br /&gt;
* - REQUEST_TIMEOUT: Operation timeout in seconds (default: 30)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span id=&amp;quot;custom-exceptions&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&lt;br /&gt;
== Custom Exceptions ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;PodmanAPIError(message, status_code=500)&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Custom exception for API errors with HTTP status codes.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span id=&amp;quot;decorators&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&lt;br /&gt;
== Decorators ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;@handle_errors&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
* Decorator that catches exceptions and converts them to PodmanAPIError: &lt;br /&gt;
* - Catches asyncio.TimeoutError → 408 Timeout &lt;br /&gt;
* - Catches “not found” errors → 404 Not Found &lt;br /&gt;
* - Catches other exceptions → 500 Internal Server Error&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;@validate_json(required_fields=None)&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Decorator that validates JSON request data: &lt;br /&gt;
&lt;br /&gt;
* - Ensures request contains valid JSON &lt;br /&gt;
* - Checks for required fields &lt;br /&gt;
* - Returns 400 Bad Request if validation fails&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span id=&amp;quot;core-api-class&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&lt;br /&gt;
=== Core API Class ===&lt;br /&gt;
&lt;br /&gt;
===== PodmanAPI.__init__() =====&lt;br /&gt;
Initializes the API with a thread pool executor.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;PodmanAPI._get_client()&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Returns a new Podman client instance.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;PodmanAPI._run_async(func, timeout=30)&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Executes a function asynchronously in the thread pool with timeout.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;PodmanAPI._handle_exec_result(result)&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Helper that normalizes exec_run results to (exit_code, output_string).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span id=&amp;quot;container-management&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&lt;br /&gt;
== Container Management ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;create_container(config)&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Creates and starts a new container. &lt;br /&gt;
&lt;br /&gt;
* - Input: {image, name, command?, environment?, ports?, volumes?, user?} &lt;br /&gt;
* - Returns: {id, name, status}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;start_container(name)&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Starts an existing container. &lt;br /&gt;
&lt;br /&gt;
* - Input: Container name &lt;br /&gt;
* - Returns: {name, status}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;stop_container(name)&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Stops a running container. &lt;br /&gt;
&lt;br /&gt;
* - Input: Container name &lt;br /&gt;
* - Returns: {name, status}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;remove_container(name, force=False)&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Removes a container. &lt;br /&gt;
&lt;br /&gt;
* - Input: Container name, force flag &lt;br /&gt;
* - Returns: {name, removed}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;list_containers()&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Lists all containers (running and stopped). &lt;br /&gt;
&lt;br /&gt;
* - Returns: Array of {id, name, status, image}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;get_container_logs(name, tail=100)&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Retrieves container logs. &lt;br /&gt;
&lt;br /&gt;
* - Input: Container name, number of lines &lt;br /&gt;
* - Returns: {logs}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Host Operations&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;add_user(container_name, user_config)&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Creates a user inside a container. &lt;br /&gt;
&lt;br /&gt;
* - Input: Container name, {username, password?, shell?} &lt;br /&gt;
* - Returns: {username, created}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;add_file(container_name, file_config)&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Creates a file inside a container. &lt;br /&gt;
&lt;br /&gt;
* - Input: Container name, {dest_path, content} &lt;br /&gt;
* - Returns: {dest_path, size}&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;execute_command(container_name, command, user=None)&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Executes a command inside a container. &lt;br /&gt;
&lt;br /&gt;
* - Input: Container name, command string, optional user &lt;br /&gt;
* - Returns: {exit_code, output, success}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;shutdown()&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Gracefully shuts down the thread pool executor.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span id=&amp;quot;error-handlers&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&lt;br /&gt;
== Error Handlers ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;handle_podman_error(error)&#039;&#039;&#039; Flask error handler for PodmanAPIError exceptions. - Returns structured JSON error response with timestamp&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;handle_internal_error(error)&#039;&#039;&#039; Flask error handler for 500 Internal Server Error. - Logs error and returns generic error response&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span id=&amp;quot;api-routes&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&lt;br /&gt;
== API Routes ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;POST /api/v1/containers&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Creates a new container. Requires image and name fields.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;POST /api/v1/containers/&amp;lt;name&amp;gt;/start&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Starts the specified container.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;POST /api/v1/containers/&amp;lt;name&amp;gt;/stop&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Stops the specified container.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;DELETE /api/v1/containers/&amp;lt;name&amp;gt;?force=bool&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Removes the specified container. Optional force parameter.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;GET /api/v1/containers&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Lists all containers.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;GET /api/v1/containers/&amp;lt;name&amp;gt;/logs?tail=int&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Gets container logs. Optional tail parameter (default: 100).&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;POST /api/v1/containers/&amp;lt;name&amp;gt;/users&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Adds a user to the container. Requires username field.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;POST /api/v1/containers/&amp;lt;name&amp;gt;/files&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Adds a file to the container. Requires dest_path and content fields.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;POST /api/v1/containers/&amp;lt;name&amp;gt;/exec&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Executes a command in the container. Requires command field.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;GET /api/v1/health&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Health check endpoint. Returns Podman connection status.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;GET /api/v1/metrics&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Returns API metrics including uptime and thread pool status.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span id=&amp;quot;signal-handlers&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&lt;br /&gt;
== Signal Handlers ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;shutdown_handler(sig, frame)&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Handles SIGINT and SIGTERM signals for graceful shutdown. &lt;br /&gt;
&lt;br /&gt;
* - Logs signal details including source file and line number &lt;br /&gt;
* - Shuts down thread pool and exits cleanly&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span id=&amp;quot;response-format&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&lt;br /&gt;
== Response Format ==&lt;br /&gt;
&lt;br /&gt;
Successful requests will look like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;{&lt;br /&gt;
  &amp;amp;quot;success&amp;amp;quot;: true,&lt;br /&gt;
  &amp;amp;quot;container|user|file|execution&amp;amp;quot;: { ... },&lt;br /&gt;
  &amp;amp;quot;timestamp&amp;amp;quot;: &amp;amp;quot;ISO-8601-timestamp&amp;amp;quot;&lt;br /&gt;
}&amp;lt;/pre&amp;gt;&lt;br /&gt;
Errors will look like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;{&lt;br /&gt;
  &amp;amp;quot;success&amp;amp;quot;: false,&lt;br /&gt;
  &amp;amp;quot;error&amp;amp;quot;: {&lt;br /&gt;
    &amp;amp;quot;message&amp;amp;quot;: &amp;amp;quot;Error description&amp;amp;quot;,&lt;br /&gt;
    &amp;amp;quot;code&amp;amp;quot;: 400,&lt;br /&gt;
    &amp;amp;quot;type&amp;amp;quot;: &amp;amp;quot;ErrorType&amp;amp;quot;&lt;br /&gt;
  },&lt;br /&gt;
  &amp;amp;quot;timestamp&amp;amp;quot;: &amp;amp;quot;ISO-8601-timestamp&amp;amp;quot;&lt;br /&gt;
}&amp;lt;/pre&amp;gt;&lt;/div&gt;</summary>
		<author><name>Jack</name></author>
	</entry>
	<entry>
		<id>http://edurange.org/wiki/index.php?title=Podman_API&amp;diff=874</id>
		<title>Podman API</title>
		<link rel="alternate" type="text/html" href="http://edurange.org/wiki/index.php?title=Podman_API&amp;diff=874"/>
		<updated>2025-07-19T13:37:08Z</updated>

		<summary type="html">&lt;p&gt;Jack: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
&lt;br /&gt;
More design thoughts and justifications to be added later, these are just the protocol specs.&lt;br /&gt;
&lt;br /&gt;
This design is not meant to be followed, it&#039;s just my first sketch at listing everything we might need.&lt;br /&gt;
&lt;br /&gt;
[TODO: Add the curl scripts I used for testing]&lt;br /&gt;
== Configuration ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span id=&amp;quot;config&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&lt;br /&gt;
=== Config ===&lt;br /&gt;
&lt;br /&gt;
Configuration class that loads settings from environment variables. &lt;br /&gt;
&lt;br /&gt;
* - MAX_WORKERS: Thread pool size (default: 10) &lt;br /&gt;
* - DEBUG: Enable debug mode (default: False) &lt;br /&gt;
* - REQUEST_TIMEOUT: Operation timeout in seconds (default: 30)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span id=&amp;quot;custom-exceptions&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&lt;br /&gt;
== Custom Exceptions ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;PodmanAPIError(message, status_code=500)&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Custom exception for API errors with HTTP status codes.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span id=&amp;quot;decorators&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&lt;br /&gt;
== Decorators ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;@handle_errors&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
* Decorator that catches exceptions and converts them to PodmanAPIError: &lt;br /&gt;
* - Catches asyncio.TimeoutError → 408 Timeout &lt;br /&gt;
* - Catches “not found” errors → 404 Not Found &lt;br /&gt;
* - Catches other exceptions → 500 Internal Server Error&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;@validate_json(required_fields=None)&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Decorator that validates JSON request data: &lt;br /&gt;
&lt;br /&gt;
* - Ensures request contains valid JSON &lt;br /&gt;
* - Checks for required fields &lt;br /&gt;
* - Returns 400 Bad Request if validation fails&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span id=&amp;quot;core-api-class&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&lt;br /&gt;
=== Core API Class ===&lt;br /&gt;
&lt;br /&gt;
===== PodmanAPI.__init__() =====&lt;br /&gt;
Initializes the API with a thread pool executor.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;PodmanAPI._get_client()&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Returns a new Podman client instance.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;PodmanAPI._run_async(func, timeout=30)&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Executes a function asynchronously in the thread pool with timeout.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;PodmanAPI._handle_exec_result(result)&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Helper that normalizes exec_run results to (exit_code, output_string).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span id=&amp;quot;container-management&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&lt;br /&gt;
== Container Management ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;create_container(config)&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Creates and starts a new container. &lt;br /&gt;
&lt;br /&gt;
* - Input: {image, name, command?, environment?, ports?, volumes?, user?} &lt;br /&gt;
* - Returns: {id, name, status}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;start_container(name)&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Starts an existing container. &lt;br /&gt;
&lt;br /&gt;
* - Input: Container name &lt;br /&gt;
* - Returns: {name, status}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;stop_container(name)&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Stops a running container. &lt;br /&gt;
&lt;br /&gt;
* - Input: Container name &lt;br /&gt;
* - Returns: {name, status}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;remove_container(name, force=False)&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Removes a container. &lt;br /&gt;
&lt;br /&gt;
* - Input: Container name, force flag &lt;br /&gt;
* - Returns: {name, removed}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;list_containers()&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Lists all containers (running and stopped). &lt;br /&gt;
&lt;br /&gt;
* - Returns: Array of {id, name, status, image}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;get_container_logs(name, tail=100)&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Retrieves container logs. &lt;br /&gt;
&lt;br /&gt;
* - Input: Container name, number of lines &lt;br /&gt;
* - Returns: {logs}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Host Operations&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;add_user(container_name, user_config)&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Creates a user inside a container. &lt;br /&gt;
&lt;br /&gt;
* - Input: Container name, {username, password?, shell?} &lt;br /&gt;
* - Returns: {username, created}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;add_file(container_name, file_config)&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Creates a file inside a container. &lt;br /&gt;
&lt;br /&gt;
* - Input: Container name, {dest_path, content} &lt;br /&gt;
* - Returns: {dest_path, size}&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;execute_command(container_name, command, user=None)&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Executes a command inside a container. &lt;br /&gt;
&lt;br /&gt;
* - Input: Container name, command string, optional user &lt;br /&gt;
* - Returns: {exit_code, output, success}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;shutdown()&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Gracefully shuts down the thread pool executor.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span id=&amp;quot;error-handlers&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&lt;br /&gt;
== Error Handlers ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;handle_podman_error(error)&#039;&#039;&#039; Flask error handler for PodmanAPIError exceptions. - Returns structured JSON error response with timestamp&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;handle_internal_error(error)&#039;&#039;&#039; Flask error handler for 500 Internal Server Error. - Logs error and returns generic error response&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span id=&amp;quot;api-routes&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&lt;br /&gt;
== API Routes ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;POST /api/v1/containers&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Creates a new container. Requires image and name fields.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;POST /api/v1/containers/&amp;lt;name&amp;gt;/start&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Starts the specified container.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;POST /api/v1/containers/&amp;lt;name&amp;gt;/stop&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Stops the specified container.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;DELETE /api/v1/containers/&amp;lt;name&amp;gt;?force=bool&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Removes the specified container. Optional force parameter.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;GET /api/v1/containers&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Lists all containers.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;GET /api/v1/containers/&amp;lt;name&amp;gt;/logs?tail=int&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Gets container logs. Optional tail parameter (default: 100).&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;POST /api/v1/containers/&amp;lt;name&amp;gt;/users&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Adds a user to the container. Requires username field.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;POST /api/v1/containers/&amp;lt;name&amp;gt;/files&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Adds a file to the container. Requires dest_path and content fields.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;POST /api/v1/containers/&amp;lt;name&amp;gt;/exec&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Executes a command in the container. Requires command field.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;GET /api/v1/health&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Health check endpoint. Returns Podman connection status.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;GET /api/v1/metrics&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Returns API metrics including uptime and thread pool status.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span id=&amp;quot;signal-handlers&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&lt;br /&gt;
== Signal Handlers ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;shutdown_handler(sig, frame)&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Handles SIGINT and SIGTERM signals for graceful shutdown. &lt;br /&gt;
&lt;br /&gt;
* - Logs signal details including source file and line number &lt;br /&gt;
* - Shuts down thread pool and exits cleanly&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span id=&amp;quot;response-format&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&lt;br /&gt;
== Response Format ==&lt;br /&gt;
&lt;br /&gt;
Successful requests will look like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;{&lt;br /&gt;
  &amp;amp;quot;success&amp;amp;quot;: true,&lt;br /&gt;
  &amp;amp;quot;container|user|file|execution&amp;amp;quot;: { ... },&lt;br /&gt;
  &amp;amp;quot;timestamp&amp;amp;quot;: &amp;amp;quot;ISO-8601-timestamp&amp;amp;quot;&lt;br /&gt;
}&amp;lt;/pre&amp;gt;&lt;br /&gt;
Errors will look like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;{&lt;br /&gt;
  &amp;amp;quot;success&amp;amp;quot;: false,&lt;br /&gt;
  &amp;amp;quot;error&amp;amp;quot;: {&lt;br /&gt;
    &amp;amp;quot;message&amp;amp;quot;: &amp;amp;quot;Error description&amp;amp;quot;,&lt;br /&gt;
    &amp;amp;quot;code&amp;amp;quot;: 400,&lt;br /&gt;
    &amp;amp;quot;type&amp;amp;quot;: &amp;amp;quot;ErrorType&amp;amp;quot;&lt;br /&gt;
  },&lt;br /&gt;
  &amp;amp;quot;timestamp&amp;amp;quot;: &amp;amp;quot;ISO-8601-timestamp&amp;amp;quot;&lt;br /&gt;
}&amp;lt;/pre&amp;gt;&lt;/div&gt;</summary>
		<author><name>Jack</name></author>
	</entry>
	<entry>
		<id>http://edurange.org/wiki/index.php?title=Podman_API&amp;diff=873</id>
		<title>Podman API</title>
		<link rel="alternate" type="text/html" href="http://edurange.org/wiki/index.php?title=Podman_API&amp;diff=873"/>
		<updated>2025-07-19T13:31:25Z</updated>

		<summary type="html">&lt;p&gt;Jack: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
&lt;br /&gt;
More design thoughts and justifications to be added later, these are just the protocol specs.&lt;br /&gt;
&lt;br /&gt;
This design is not meant to be followed, it&#039;s just my first sketch at listing everything we might need.&lt;br /&gt;
&lt;br /&gt;
[TODO: Add the curl scripts I used for testing]&lt;br /&gt;
== Configuration ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span id=&amp;quot;config&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&lt;br /&gt;
=== Config ===&lt;br /&gt;
&lt;br /&gt;
Configuration class that loads settings from environment variables. - MAX_WORKERS: Thread pool size (default: 10) - DEBUG: Enable debug mode (default: False) - REQUEST_TIMEOUT: Operation timeout in seconds (default: 30)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span id=&amp;quot;custom-exceptions&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&lt;br /&gt;
== Custom Exceptions ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;PodmanAPIError(message, status_code=500)&#039;&#039;&#039; Custom exception for API errors with HTTP status codes.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span id=&amp;quot;decorators&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&lt;br /&gt;
== Decorators ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;@handle_errors&#039;&#039;&#039; Decorator that catches exceptions and converts them to PodmanAPIError: - Catches asyncio.TimeoutError → 408 Timeout - Catches “not found” errors → 404 Not Found - Catches other exceptions → 500 Internal Server Error&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;@validate_json(required_fields=None)&#039;&#039;&#039; Decorator that validates JSON request data: - Ensures request contains valid JSON - Checks for required fields - Returns 400 Bad Request if validation fails&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span id=&amp;quot;core-api-class&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&lt;br /&gt;
=== Core API Class ===&lt;br /&gt;
&lt;br /&gt;
**PodmanAPI.__init__()** Initializes the API with a thread pool executor.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;PodmanAPI._get_client()&#039;&#039;&#039; Returns a new Podman client instance.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;PodmanAPI._run_async(func, timeout=30)&#039;&#039;&#039; Executes a function asynchronously in the thread pool with timeout.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;PodmanAPI._handle_exec_result(result)&#039;&#039;&#039; Helper that normalizes exec_run results to (exit_code, output_string).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span id=&amp;quot;container-management&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&lt;br /&gt;
== Container Management ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;create_container(config)&#039;&#039;&#039; Creates and starts a new container. - Input: {image, name, command?, environment?, ports?, volumes?, user?} - Returns: {id, name, status}&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;start_container(name)&#039;&#039;&#039; Starts an existing container. - Input: Container name - Returns: {name, status}&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;stop_container(name)&#039;&#039;&#039; Stops a running container. - Input: Container name - Returns: {name, status}&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;remove_container(name, force=False)&#039;&#039;&#039; Removes a container. - Input: Container name, force flag - Returns: {name, removed}&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;list_containers()&#039;&#039;&#039; Lists all containers (running and stopped). - Returns: Array of {id, name, status, image}&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;get_container_logs(name, tail=100)&#039;&#039;&#039; Retrieves container logs. - Input: Container name, number of lines - Returns: {logs}&lt;br /&gt;
&lt;br /&gt;
Host Operations&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;add_user(container_name, user_config)&#039;&#039;&#039; Creates a user inside a container. - Input: Container name, {username, password?, shell?} - Returns: {username, created}&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;add_file(container_name, file_config)&#039;&#039;&#039; Creates a file inside a container. - Input: Container name, {dest_path, content} - Returns: {dest_path, size}&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;execute_command(container_name, command, user=None)&#039;&#039;&#039; Executes a command inside a container. - Input: Container name, command string, optional user - Returns: {exit_code, output, success}&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;shutdown()&#039;&#039;&#039; Gracefully shuts down the thread pool executor.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span id=&amp;quot;error-handlers&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&lt;br /&gt;
== Error Handlers ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;handle_podman_error(error)&#039;&#039;&#039; Flask error handler for PodmanAPIError exceptions. - Returns structured JSON error response with timestamp&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;handle_internal_error(error)&#039;&#039;&#039; Flask error handler for 500 Internal Server Error. - Logs error and returns generic error response&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span id=&amp;quot;api-routes&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&lt;br /&gt;
== API Routes ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;POST /api/v1/containers&#039;&#039;&#039; Creates a new container. Requires image and name fields.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;POST /api/v1/containers/&amp;lt;name&amp;gt;/start&#039;&#039;&#039; Starts the specified container.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;POST /api/v1/containers/&amp;lt;name&amp;gt;/stop&#039;&#039;&#039; Stops the specified container.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;DELETE /api/v1/containers/&amp;lt;name&amp;gt;?force=bool&#039;&#039;&#039; Removes the specified container. Optional force parameter.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;GET /api/v1/containers&#039;&#039;&#039; Lists all containers.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;GET /api/v1/containers/&amp;lt;name&amp;gt;/logs?tail=int&#039;&#039;&#039; Gets container logs. Optional tail parameter (default: 100).&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;POST /api/v1/containers/&amp;lt;name&amp;gt;/users&#039;&#039;&#039; Adds a user to the container. Requires username field.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;POST /api/v1/containers/&amp;lt;name&amp;gt;/files&#039;&#039;&#039; Adds a file to the container. Requires dest_path and content fields.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;POST /api/v1/containers/&amp;lt;name&amp;gt;/exec&#039;&#039;&#039; Executes a command in the container. Requires command field.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;GET /api/v1/health&#039;&#039;&#039; Health check endpoint. Returns Podman connection status.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;GET /api/v1/metrics&#039;&#039;&#039; Returns API metrics including uptime and thread pool status.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span id=&amp;quot;signal-handlers&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&lt;br /&gt;
== Signal Handlers ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;shutdown_handler(sig, frame)&#039;&#039;&#039; Handles SIGINT and SIGTERM signals for graceful shutdown. - Logs signal details including source file and line number - Shuts down thread pool and exits cleanly&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span id=&amp;quot;response-format&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&lt;br /&gt;
== Response Format ==&lt;br /&gt;
&lt;br /&gt;
Successful requests will look like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;{&lt;br /&gt;
  &amp;amp;quot;success&amp;amp;quot;: true,&lt;br /&gt;
  &amp;amp;quot;container|user|file|execution&amp;amp;quot;: { ... },&lt;br /&gt;
  &amp;amp;quot;timestamp&amp;amp;quot;: &amp;amp;quot;ISO-8601-timestamp&amp;amp;quot;&lt;br /&gt;
}&amp;lt;/pre&amp;gt;&lt;br /&gt;
Errors will look like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;{&lt;br /&gt;
  &amp;amp;quot;success&amp;amp;quot;: false,&lt;br /&gt;
  &amp;amp;quot;error&amp;amp;quot;: {&lt;br /&gt;
    &amp;amp;quot;message&amp;amp;quot;: &amp;amp;quot;Error description&amp;amp;quot;,&lt;br /&gt;
    &amp;amp;quot;code&amp;amp;quot;: 400,&lt;br /&gt;
    &amp;amp;quot;type&amp;amp;quot;: &amp;amp;quot;ErrorType&amp;amp;quot;&lt;br /&gt;
  },&lt;br /&gt;
  &amp;amp;quot;timestamp&amp;amp;quot;: &amp;amp;quot;ISO-8601-timestamp&amp;amp;quot;&lt;br /&gt;
}&amp;lt;/pre&amp;gt;&lt;/div&gt;</summary>
		<author><name>Jack</name></author>
	</entry>
	<entry>
		<id>http://edurange.org/wiki/index.php?title=Podman_API&amp;diff=872</id>
		<title>Podman API</title>
		<link rel="alternate" type="text/html" href="http://edurange.org/wiki/index.php?title=Podman_API&amp;diff=872"/>
		<updated>2025-07-19T13:28:57Z</updated>

		<summary type="html">&lt;p&gt;Jack: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&amp;lt;span id=&amp;quot;configuration&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&lt;br /&gt;
== Configuration ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span id=&amp;quot;config&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&lt;br /&gt;
=== Config ===&lt;br /&gt;
&lt;br /&gt;
Configuration class that loads settings from environment variables. - MAX_WORKERS: Thread pool size (default: 10) - DEBUG: Enable debug mode (default: False) - REQUEST_TIMEOUT: Operation timeout in seconds (default: 30)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span id=&amp;quot;custom-exceptions&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&lt;br /&gt;
== Custom Exceptions ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;PodmanAPIError(message, status_code=500)&#039;&#039;&#039; Custom exception for API errors with HTTP status codes.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span id=&amp;quot;decorators&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&lt;br /&gt;
== Decorators ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;@handle_errors&#039;&#039;&#039; Decorator that catches exceptions and converts them to PodmanAPIError: - Catches asyncio.TimeoutError → 408 Timeout - Catches “not found” errors → 404 Not Found - Catches other exceptions → 500 Internal Server Error&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;@validate_json(required_fields=None)&#039;&#039;&#039; Decorator that validates JSON request data: - Ensures request contains valid JSON - Checks for required fields - Returns 400 Bad Request if validation fails&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span id=&amp;quot;core-api-class&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&lt;br /&gt;
=== Core API Class ===&lt;br /&gt;
&lt;br /&gt;
**PodmanAPI.__init__()** Initializes the API with a thread pool executor.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;PodmanAPI._get_client()&#039;&#039;&#039; Returns a new Podman client instance.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;PodmanAPI._run_async(func, timeout=30)&#039;&#039;&#039; Executes a function asynchronously in the thread pool with timeout.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;PodmanAPI._handle_exec_result(result)&#039;&#039;&#039; Helper that normalizes exec_run results to (exit_code, output_string).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span id=&amp;quot;container-management&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&lt;br /&gt;
== Container Management ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;create_container(config)&#039;&#039;&#039; Creates and starts a new container. - Input: {image, name, command?, environment?, ports?, volumes?, user?} - Returns: {id, name, status}&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;start_container(name)&#039;&#039;&#039; Starts an existing container. - Input: Container name - Returns: {name, status}&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;stop_container(name)&#039;&#039;&#039; Stops a running container. - Input: Container name - Returns: {name, status}&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;remove_container(name, force=False)&#039;&#039;&#039; Removes a container. - Input: Container name, force flag - Returns: {name, removed}&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;list_containers()&#039;&#039;&#039; Lists all containers (running and stopped). - Returns: Array of {id, name, status, image}&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;get_container_logs(name, tail=100)&#039;&#039;&#039; Retrieves container logs. - Input: Container name, number of lines - Returns: {logs}&lt;br /&gt;
&lt;br /&gt;
Host Operations&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;add_user(container_name, user_config)&#039;&#039;&#039; Creates a user inside a container. - Input: Container name, {username, password?, shell?} - Returns: {username, created}&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;add_file(container_name, file_config)&#039;&#039;&#039; Creates a file inside a container. - Input: Container name, {dest_path, content} - Returns: {dest_path, size}&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;execute_command(container_name, command, user=None)&#039;&#039;&#039; Executes a command inside a container. - Input: Container name, command string, optional user - Returns: {exit_code, output, success}&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;shutdown()&#039;&#039;&#039; Gracefully shuts down the thread pool executor.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span id=&amp;quot;error-handlers&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&lt;br /&gt;
== Error Handlers ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;handle_podman_error(error)&#039;&#039;&#039; Flask error handler for PodmanAPIError exceptions. - Returns structured JSON error response with timestamp&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;handle_internal_error(error)&#039;&#039;&#039; Flask error handler for 500 Internal Server Error. - Logs error and returns generic error response&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span id=&amp;quot;api-routes&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&lt;br /&gt;
== API Routes ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;POST /api/v1/containers&#039;&#039;&#039; Creates a new container. Requires image and name fields.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;POST /api/v1/containers/&amp;lt;name&amp;gt;/start&#039;&#039;&#039; Starts the specified container.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;POST /api/v1/containers/&amp;lt;name&amp;gt;/stop&#039;&#039;&#039; Stops the specified container.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;DELETE /api/v1/containers/&amp;lt;name&amp;gt;?force=bool&#039;&#039;&#039; Removes the specified container. Optional force parameter.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;GET /api/v1/containers&#039;&#039;&#039; Lists all containers.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;GET /api/v1/containers/&amp;lt;name&amp;gt;/logs?tail=int&#039;&#039;&#039; Gets container logs. Optional tail parameter (default: 100).&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;POST /api/v1/containers/&amp;lt;name&amp;gt;/users&#039;&#039;&#039; Adds a user to the container. Requires username field.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;POST /api/v1/containers/&amp;lt;name&amp;gt;/files&#039;&#039;&#039; Adds a file to the container. Requires dest_path and content fields.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;POST /api/v1/containers/&amp;lt;name&amp;gt;/exec&#039;&#039;&#039; Executes a command in the container. Requires command field.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;GET /api/v1/health&#039;&#039;&#039; Health check endpoint. Returns Podman connection status.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;GET /api/v1/metrics&#039;&#039;&#039; Returns API metrics including uptime and thread pool status.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span id=&amp;quot;signal-handlers&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&lt;br /&gt;
== Signal Handlers ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;shutdown_handler(sig, frame)&#039;&#039;&#039; Handles SIGINT and SIGTERM signals for graceful shutdown. - Logs signal details including source file and line number - Shuts down thread pool and exits cleanly&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span id=&amp;quot;response-format&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&lt;br /&gt;
== Response Format ==&lt;br /&gt;
&lt;br /&gt;
Successful requests will look like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;{&lt;br /&gt;
  &amp;amp;quot;success&amp;amp;quot;: true,&lt;br /&gt;
  &amp;amp;quot;container|user|file|execution&amp;amp;quot;: { ... },&lt;br /&gt;
  &amp;amp;quot;timestamp&amp;amp;quot;: &amp;amp;quot;ISO-8601-timestamp&amp;amp;quot;&lt;br /&gt;
}&amp;lt;/pre&amp;gt;&lt;br /&gt;
Errors will look like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;{&lt;br /&gt;
  &amp;amp;quot;success&amp;amp;quot;: false,&lt;br /&gt;
  &amp;amp;quot;error&amp;amp;quot;: {&lt;br /&gt;
    &amp;amp;quot;message&amp;amp;quot;: &amp;amp;quot;Error description&amp;amp;quot;,&lt;br /&gt;
    &amp;amp;quot;code&amp;amp;quot;: 400,&lt;br /&gt;
    &amp;amp;quot;type&amp;amp;quot;: &amp;amp;quot;ErrorType&amp;amp;quot;&lt;br /&gt;
  },&lt;br /&gt;
  &amp;amp;quot;timestamp&amp;amp;quot;: &amp;amp;quot;ISO-8601-timestamp&amp;amp;quot;&lt;br /&gt;
}&amp;lt;/pre&amp;gt;&lt;/div&gt;</summary>
		<author><name>Jack</name></author>
	</entry>
	<entry>
		<id>http://edurange.org/wiki/index.php?title=Podman_API&amp;diff=871</id>
		<title>Podman API</title>
		<link rel="alternate" type="text/html" href="http://edurange.org/wiki/index.php?title=Podman_API&amp;diff=871"/>
		<updated>2025-07-19T13:26:07Z</updated>

		<summary type="html">&lt;p&gt;Jack: Created page with &amp;quot;&amp;lt;nowiki&amp;gt;##&amp;lt;/nowiki&amp;gt; Configuration  &amp;lt;nowiki&amp;gt;###&amp;lt;/nowiki&amp;gt; Config  Configuration class that loads settings from environment variables.  - MAX_WORKERS: Thread pool size (default: 10)  - DEBUG: Enable debug mode (default: False)  - REQUEST_TIMEOUT: Operation timeout in seconds (default: 30)  &amp;lt;nowiki&amp;gt;##&amp;lt;/nowiki&amp;gt; Custom Exceptions  &amp;lt;nowiki&amp;gt;**&amp;lt;/nowiki&amp;gt;PodmanAPIError(message, status_code=500)**  Custom exception for API errors with HTTP status codes.  &amp;lt;nowiki&amp;gt;##&amp;lt;/nowiki&amp;gt; Decorato...&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&amp;lt;nowiki&amp;gt;##&amp;lt;/nowiki&amp;gt; Configuration&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;###&amp;lt;/nowiki&amp;gt; Config&lt;br /&gt;
&lt;br /&gt;
Configuration class that loads settings from environment variables.&lt;br /&gt;
&lt;br /&gt;
- MAX_WORKERS: Thread pool size (default: 10)&lt;br /&gt;
&lt;br /&gt;
- DEBUG: Enable debug mode (default: False)&lt;br /&gt;
&lt;br /&gt;
- REQUEST_TIMEOUT: Operation timeout in seconds (default: 30)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;##&amp;lt;/nowiki&amp;gt; Custom Exceptions&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;**&amp;lt;/nowiki&amp;gt;PodmanAPIError(message, status_code=500)**&lt;br /&gt;
&lt;br /&gt;
Custom exception for API errors with HTTP status codes.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;##&amp;lt;/nowiki&amp;gt; Decorators&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;**&amp;lt;/nowiki&amp;gt;@handle_errors**&lt;br /&gt;
&lt;br /&gt;
Decorator that catches exceptions and converts them to PodmanAPIError:&lt;br /&gt;
&lt;br /&gt;
- Catches asyncio.TimeoutError → 408 Timeout&lt;br /&gt;
&lt;br /&gt;
- Catches &amp;quot;not found&amp;quot; errors → 404 Not Found&lt;br /&gt;
&lt;br /&gt;
- Catches other exceptions → 500 Internal Server Error&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;**&amp;lt;/nowiki&amp;gt;@validate_json(required_fields=None)**&lt;br /&gt;
&lt;br /&gt;
Decorator that validates JSON request data:&lt;br /&gt;
&lt;br /&gt;
- Ensures request contains valid JSON&lt;br /&gt;
&lt;br /&gt;
- Checks for required fields&lt;br /&gt;
&lt;br /&gt;
- Returns 400 Bad Request if validation fails&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;###&amp;lt;/nowiki&amp;gt; Core API Class&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;**&amp;lt;/nowiki&amp;gt;PodmanAPI.__init__()**&lt;br /&gt;
&lt;br /&gt;
Initializes the API with a thread pool executor.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;**&amp;lt;/nowiki&amp;gt;PodmanAPI._get_client()**&lt;br /&gt;
&lt;br /&gt;
Returns a new Podman client instance.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;**&amp;lt;/nowiki&amp;gt;PodmanAPI._run_async(func, timeout=30)**&lt;br /&gt;
&lt;br /&gt;
Executes a function asynchronously in the thread pool with timeout.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;**&amp;lt;/nowiki&amp;gt;PodmanAPI._handle_exec_result(result)**&lt;br /&gt;
&lt;br /&gt;
Helper that normalizes exec_run results to (exit_code, output_string).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;##&amp;lt;/nowiki&amp;gt; Container Management&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;**&amp;lt;/nowiki&amp;gt;create_container(config)**&lt;br /&gt;
&lt;br /&gt;
Creates and starts a new container.&lt;br /&gt;
&lt;br /&gt;
- Input: {image, name, command?, environment?, ports?, volumes?, user?}&lt;br /&gt;
&lt;br /&gt;
- Returns: {id, name, status}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;**&amp;lt;/nowiki&amp;gt;start_container(name)**&lt;br /&gt;
&lt;br /&gt;
Starts an existing container.&lt;br /&gt;
&lt;br /&gt;
- Input: Container name&lt;br /&gt;
&lt;br /&gt;
- Returns: {name, status}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;**&amp;lt;/nowiki&amp;gt;stop_container(name)**&lt;br /&gt;
&lt;br /&gt;
Stops a running container.&lt;br /&gt;
&lt;br /&gt;
- Input: Container name&lt;br /&gt;
&lt;br /&gt;
- Returns: {name, status}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;**&amp;lt;/nowiki&amp;gt;remove_container(name, force=False)**&lt;br /&gt;
&lt;br /&gt;
Removes a container.&lt;br /&gt;
&lt;br /&gt;
- Input: Container name, force flag&lt;br /&gt;
&lt;br /&gt;
- Returns: {name, removed}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;**&amp;lt;/nowiki&amp;gt;list_containers()**&lt;br /&gt;
&lt;br /&gt;
Lists all containers (running and stopped).&lt;br /&gt;
&lt;br /&gt;
- Returns: Array of {id, name, status, image}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;**&amp;lt;/nowiki&amp;gt;get_container_logs(name, tail=100)**&lt;br /&gt;
&lt;br /&gt;
Retrieves container logs.&lt;br /&gt;
&lt;br /&gt;
- Input: Container name, number of lines&lt;br /&gt;
&lt;br /&gt;
- Returns: {logs}&lt;br /&gt;
&lt;br /&gt;
Host Operations&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;**&amp;lt;/nowiki&amp;gt;add_user(container_name, user_config)**&lt;br /&gt;
&lt;br /&gt;
Creates a user inside a container.&lt;br /&gt;
&lt;br /&gt;
- Input: Container name, {username, password?, shell?}&lt;br /&gt;
&lt;br /&gt;
- Returns: {username, created}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;**&amp;lt;/nowiki&amp;gt;add_file(container_name, file_config)**&lt;br /&gt;
&lt;br /&gt;
Creates a file inside a container.&lt;br /&gt;
&lt;br /&gt;
- Input: Container name, {dest_path, content}&lt;br /&gt;
&lt;br /&gt;
- Returns: {dest_path, size}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;**&amp;lt;/nowiki&amp;gt;execute_command(container_name, command, user=None)**&lt;br /&gt;
&lt;br /&gt;
Executes a command inside a container.&lt;br /&gt;
&lt;br /&gt;
- Input: Container name, command string, optional user&lt;br /&gt;
&lt;br /&gt;
- Returns: {exit_code, output, success}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;**&amp;lt;/nowiki&amp;gt;shutdown()**&lt;br /&gt;
&lt;br /&gt;
Gracefully shuts down the thread pool executor.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;##&amp;lt;/nowiki&amp;gt; Error Handlers&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;**&amp;lt;/nowiki&amp;gt;handle_podman_error(error)**&lt;br /&gt;
&lt;br /&gt;
Flask error handler for PodmanAPIError exceptions.&lt;br /&gt;
&lt;br /&gt;
- Returns structured JSON error response with timestamp&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;**&amp;lt;/nowiki&amp;gt;handle_internal_error(error)**&lt;br /&gt;
&lt;br /&gt;
Flask error handler for 500 Internal Server Error.&lt;br /&gt;
&lt;br /&gt;
- Logs error and returns generic error response&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;##&amp;lt;/nowiki&amp;gt; API Routes&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;**&amp;lt;/nowiki&amp;gt;POST /api/v1/containers**&lt;br /&gt;
&lt;br /&gt;
Creates a new container. Requires image and name fields.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;**&amp;lt;/nowiki&amp;gt;POST /api/v1/containers/&amp;lt;name&amp;gt;/start**&lt;br /&gt;
&lt;br /&gt;
Starts the specified container.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;**&amp;lt;/nowiki&amp;gt;POST /api/v1/containers/&amp;lt;name&amp;gt;/stop**&lt;br /&gt;
&lt;br /&gt;
Stops the specified container.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;**&amp;lt;/nowiki&amp;gt;DELETE /api/v1/containers/&amp;lt;name&amp;gt;?force=bool**&lt;br /&gt;
&lt;br /&gt;
Removes the specified container. Optional force parameter.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;**&amp;lt;/nowiki&amp;gt;GET /api/v1/containers**&lt;br /&gt;
&lt;br /&gt;
Lists all containers.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;**&amp;lt;/nowiki&amp;gt;GET /api/v1/containers/&amp;lt;name&amp;gt;/logs?tail=int**&lt;br /&gt;
&lt;br /&gt;
Gets container logs. Optional tail parameter (default: 100).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;**&amp;lt;/nowiki&amp;gt;POST /api/v1/containers/&amp;lt;name&amp;gt;/users**&lt;br /&gt;
&lt;br /&gt;
Adds a user to the container. Requires username field.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;**&amp;lt;/nowiki&amp;gt;POST /api/v1/containers/&amp;lt;name&amp;gt;/files**&lt;br /&gt;
&lt;br /&gt;
Adds a file to the container. Requires dest_path and content fields.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;**&amp;lt;/nowiki&amp;gt;POST /api/v1/containers/&amp;lt;name&amp;gt;/exec**&lt;br /&gt;
&lt;br /&gt;
Executes a command in the container. Requires command field.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;**&amp;lt;/nowiki&amp;gt;GET /api/v1/health**&lt;br /&gt;
&lt;br /&gt;
Health check endpoint. Returns Podman connection status.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;**&amp;lt;/nowiki&amp;gt;GET /api/v1/metrics**&lt;br /&gt;
&lt;br /&gt;
Returns API metrics including uptime and thread pool status.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;##&amp;lt;/nowiki&amp;gt; Signal Handlers&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;**&amp;lt;/nowiki&amp;gt;shutdown_handler(sig, frame)**&lt;br /&gt;
&lt;br /&gt;
Handles SIGINT and SIGTERM signals for graceful shutdown.&lt;br /&gt;
&lt;br /&gt;
- Logs signal details including source file and line number&lt;br /&gt;
&lt;br /&gt;
- Shuts down thread pool and exits cleanly&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;##&amp;lt;/nowiki&amp;gt; Response Format&lt;br /&gt;
&lt;br /&gt;
Successful requests will look like this:&lt;br /&gt;
&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
{&lt;br /&gt;
&lt;br /&gt;
  &amp;quot;success&amp;quot;: true,&lt;br /&gt;
&lt;br /&gt;
  &amp;quot;container|user|file|execution&amp;quot;: { ... },&lt;br /&gt;
&lt;br /&gt;
  &amp;quot;timestamp&amp;quot;: &amp;quot;ISO-8601-timestamp&amp;quot;&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
Errors will look like this:&lt;br /&gt;
&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
{&lt;br /&gt;
&lt;br /&gt;
  &amp;quot;success&amp;quot;: false,&lt;br /&gt;
&lt;br /&gt;
  &amp;quot;error&amp;quot;: {&lt;br /&gt;
&lt;br /&gt;
    &amp;quot;message&amp;quot;: &amp;quot;Error description&amp;quot;,&lt;br /&gt;
&lt;br /&gt;
    &amp;quot;code&amp;quot;: 400,&lt;br /&gt;
&lt;br /&gt;
    &amp;quot;type&amp;quot;: &amp;quot;ErrorType&amp;quot;&lt;br /&gt;
&lt;br /&gt;
  },&lt;br /&gt;
&lt;br /&gt;
  &amp;quot;timestamp&amp;quot;: &amp;quot;ISO-8601-timestamp&amp;quot;&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
```&lt;/div&gt;</summary>
		<author><name>Jack</name></author>
	</entry>
	<entry>
		<id>http://edurange.org/wiki/index.php?title=Category:Demonstrations&amp;diff=870</id>
		<title>Category:Demonstrations</title>
		<link rel="alternate" type="text/html" href="http://edurange.org/wiki/index.php?title=Category:Demonstrations&amp;diff=870"/>
		<updated>2025-07-19T13:25:33Z</updated>

		<summary type="html">&lt;p&gt;Jack: add my podman API demo&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Let&#039;s collect self-contained examples and demos here. I&#039;m imagining this to be a space for demos that don&#039;t link to the main software stack, and focus on implementing a single feature or concept at a time.&lt;br /&gt;
&lt;br /&gt;
Preferably, they should use no external dependencies, or have only a single external dependency. They should be easy to install and run without extra knowledge - the main usage method should be readily apparent and the code should follow documentation conventions.&lt;br /&gt;
&lt;br /&gt;
Most importantly, these should be short and easy to read, because their purpose is to teach the group. It would be great if we could demonstrate, discuss and come to understand these projects within one or two group meetings.&lt;br /&gt;
&lt;br /&gt;
== Demo Projects ==&lt;br /&gt;
These demonstrate or introduce new concepts, conventions, libraries, patterns, etc.&lt;br /&gt;
&lt;br /&gt;
* [[Namedpipe Log Writer]] - https://github.com/edurange/demo-namedpipe-log-writer&lt;br /&gt;
* [[Keystroke Observers]] - https://github.com/edurange/demo-keystroke-observers&lt;br /&gt;
* [[TTY Logging with BPF]] - https://github.com/edurange/demo-bpf-tty-logger/&lt;br /&gt;
* [[Podman API]]- [https://github.com/edurange/demo-podman-api-extra/tree/main https://github.com/edurange/demo-podman-api-extra]&lt;/div&gt;</summary>
		<author><name>Jack</name></author>
	</entry>
	<entry>
		<id>http://edurange.org/wiki/index.php?title=Main_Page&amp;diff=95</id>
		<title>Main Page</title>
		<link rel="alternate" type="text/html" href="http://edurange.org/wiki/index.php?title=Main_Page&amp;diff=95"/>
		<updated>2025-06-20T20:19:17Z</updated>

		<summary type="html">&lt;p&gt;Jack: /* Developer Links */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Welcome to the EDURange Wiki. This is less of a formal documentation as it is a living document.&lt;br /&gt;
&lt;br /&gt;
I think that if it&#039;s a choice between losing track of people&#039;s contributions and having a messy wiki, I&#039;d much prefer a messy wiki.  It&#039;s my hope that we can gather all of our notes, thoughts, and loosely related ephemera here, rather than having it scattered across correspondence and various sharing platforms.&lt;br /&gt;
&lt;br /&gt;
We don&#039;t have many conventions yet. Please follow the Golden Rule and treat others as you would be treated. Put your name on your changes. Consider posting to the discussion tab or appending to a page rather than overwriting it. Try not to overwrite the personal work of others.&lt;br /&gt;
&lt;br /&gt;
If it&#039;s installable/runnable/code of any sort, put it on GitHub as well - preferably under the edurange organization so that our future contributors will have seamless access to it too.&lt;br /&gt;
&lt;br /&gt;
Thanks for your time.&lt;br /&gt;
&lt;br /&gt;
~Joe G&lt;br /&gt;
&lt;br /&gt;
== Developer Links ==&lt;br /&gt;
[[Style Guidelines and Developer Tools]]&lt;br /&gt;
&lt;br /&gt;
[[Reference Materials]]&lt;br /&gt;
&lt;br /&gt;
[[Design Stories (OLD)]]&lt;br /&gt;
&lt;br /&gt;
[[:Category:Demonstrations|Demonstrations]]&lt;br /&gt;
&lt;br /&gt;
[[Interface Design]]&lt;br /&gt;
&lt;br /&gt;
[[Scenario Creation Guide]]&lt;br /&gt;
&lt;br /&gt;
[[Terraform Design]]&lt;br /&gt;
&lt;br /&gt;
[[Hosting Requirements]]&lt;br /&gt;
&lt;br /&gt;
[[Domain Modeling]]&lt;br /&gt;
&lt;br /&gt;
== Orphaned Default Media Wiki Content ==&lt;br /&gt;
&amp;lt;strong&amp;gt;Welcome to the EDURange Wiki&amp;lt;/strong&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Consult the [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents User&#039;s Guide] for information on using the wiki software.&lt;br /&gt;
&lt;br /&gt;
== Getting started with Media Wiki ==&lt;br /&gt;
* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Configuration settings list]&lt;br /&gt;
* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ MediaWiki FAQ]&lt;br /&gt;
* [https://lists.wikimedia.org/postorius/lists/mediawiki-announce.lists.wikimedia.org/ MediaWiki release mailing list]&lt;br /&gt;
* [https://www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Localise MediaWiki for your language]&lt;br /&gt;
* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Combating_spam Learn how to combat spam on your wiki]&lt;/div&gt;</summary>
		<author><name>Jack</name></author>
	</entry>
	<entry>
		<id>http://edurange.org/wiki/index.php?title=Design_Stories_(OLD)&amp;diff=94</id>
		<title>Design Stories (OLD)</title>
		<link rel="alternate" type="text/html" href="http://edurange.org/wiki/index.php?title=Design_Stories_(OLD)&amp;diff=94"/>
		<updated>2025-06-20T20:17:06Z</updated>

		<summary type="html">&lt;p&gt;Jack: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;The EDURange Design&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The guiding principles:&lt;br /&gt;
    1. EDURange is flexible for creating new exercises, so that it supports polymorphism.&lt;br /&gt;
    2. Solving the exercises should require analytical skills and abilities.&lt;br /&gt;
    3. EDURange allows the instructor to see what individual students are doing, so that they can provide real-time feedback.&lt;br /&gt;
    4. Easy to use&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;For the instructor&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* Seeing what individual students are doing&lt;br /&gt;
* The instructor should be able to clone an exercise and add students or groups and start the exercise easily&lt;br /&gt;
* The instructor should see the status of a scenario and re-start if necessary&lt;br /&gt;
* The instructor should be able to see the sequence of commands (including their time) that a student typed, what the context was (host and directory) and what the resulting shell output was. This needs to be presented in such a way that the instructor can quickly determine if the student is making progress.&lt;br /&gt;
* The instructor should be able to see the answers that students gave to the questions.&lt;br /&gt;
* The instructor should be able to be able to offer a hint, after seeing that a student has a question or wants a hint and interact with the student via chat.&lt;br /&gt;
* The instructor should see a ranked list of students who need help. ML should rank the  students.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;For the student&#039;&#039;&#039;  &lt;br /&gt;
&lt;br /&gt;
* The student should be able to register for an exercise that the instructor created.&lt;br /&gt;
* The student should be able to run/use the exercise (once started) from any computer with a browser.&lt;br /&gt;
* The Guide should be clear, in particular, the questions should be auto-graded unless they are essay questions.&lt;br /&gt;
* The students should see which questions they have answered correctly&lt;br /&gt;
* The tasks should follow a logical sequence&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;For the researcher/developer&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* The scripts that analyze the data should be easy to run.&lt;br /&gt;
* The final data should be collected in a single location at least for each single instance of a scenario.&lt;br /&gt;
* It should be possible to pool data from multiple runs of the same scenario. Any data that is exported should be anonymized It should be possible to verify functionality, e.g. that a scenario will start without error, that a question will be graded correctly, that a system will usable by n users simultaneously, etc.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;ML&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* The ML system should be able to use multiple models for predicting student challenges and should be milestone-specific&lt;br /&gt;
* It should be possible to run a trained model and make predictions during a scenario  Author The author should be able to make changes to the Guide.&lt;br /&gt;
* The author should be able to specify host and network information&lt;br /&gt;
* The author should be able to specify learning objectives for each task in a scenario&lt;br /&gt;
* The author should be able to specify milestones, i.e. indicators that a learning objective has been met.&lt;/div&gt;</summary>
		<author><name>Jack</name></author>
	</entry>
	<entry>
		<id>http://edurange.org/wiki/index.php?title=Design_Stories_(OLD)&amp;diff=93</id>
		<title>Design Stories (OLD)</title>
		<link rel="alternate" type="text/html" href="http://edurange.org/wiki/index.php?title=Design_Stories_(OLD)&amp;diff=93"/>
		<updated>2025-06-20T20:15:04Z</updated>

		<summary type="html">&lt;p&gt;Jack: Created page with &amp;quot;# The EduRange design  ## The guiding principles:     1. EDURange is flexible for creating new exercises, so that it supports polymorphism.     2. Solving the exercises should require analytical skills and abilities.     3. EDURange allows the instructor to see what individual students are doing, so that they can provide real-time feedback.     4. Easy to use  &amp;#039;&amp;#039;&amp;#039;For the instructor&amp;#039;&amp;#039;&amp;#039;  Seeing what individual students are doing  The instructor should be able to clone an e...&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;# The EduRange design&lt;br /&gt;
&lt;br /&gt;
## The guiding principles:&lt;br /&gt;
    1. EDURange is flexible for creating new exercises, so that it supports polymorphism.&lt;br /&gt;
    2. Solving the exercises should require analytical skills and abilities.&lt;br /&gt;
    3. EDURange allows the instructor to see what individual students are doing, so that they can provide real-time feedback.&lt;br /&gt;
    4. Easy to use&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;For the instructor&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Seeing what individual students are doing&lt;br /&gt;
&lt;br /&gt;
The instructor should be able to clone an exercise and add students or groups and start the exercise easily&lt;br /&gt;
 &lt;br /&gt;
The instructor should see the status of a scenario and re-start if necessary&lt;br /&gt;
&lt;br /&gt;
The instructor should be able to see the sequence of commands (including their time) that a student typed, what the context was (host and directory) and what the resulting shell output was. This needs to be presented in such a way that the instructor can quickly determine if the student is making progress. &lt;br /&gt;
&lt;br /&gt;
The instructor should be able to see the answers that students gave to the questions.&lt;br /&gt;
&lt;br /&gt;
The instructor should be able to be able to offer a hint, after seeing that a student has a question or wants a hint and interact with the student via chat.&lt;br /&gt;
&lt;br /&gt;
The instructor should see a ranked list of students who need help. ML should rank the  students.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;For the student&#039;&#039;&#039; &lt;br /&gt;
The student should be able to register for an exercise that the instructor created. &lt;br /&gt;
&lt;br /&gt;
The student should be able to run/use the exercise (once started) from any computer with a browser.&lt;br /&gt;
&lt;br /&gt;
The Guide should be clear, in particular, the questions should be auto-graded unless they are essay questions.&lt;br /&gt;
&lt;br /&gt;
The students should see which questions they have answered correctly&lt;br /&gt;
&lt;br /&gt;
The tasks should follow a logical sequence&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&lt;br /&gt;
For the researcher/developer&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The scripts that analyze the data should be easy to run.&lt;br /&gt;
&lt;br /&gt;
The final data should be collected in a single location at least for each single instance of a scenario. &lt;br /&gt;
&lt;br /&gt;
It should be possible to pool data from multiple runs of the same scenario.&lt;br /&gt;
Any data that is exported should be anonymized&lt;br /&gt;
It should be possible to verify functionality, e.g. that a scenario will start without error, that a question will be graded correctly, that a system will usable by n users simultaneously, etc.&lt;br /&gt;
&#039;&#039;&#039;&lt;br /&gt;
ML&#039;&#039;&#039;&lt;br /&gt;
The ML system should be able to use multiple models for predicting student challenges and should be milestone-specific&lt;br /&gt;
&lt;br /&gt;
It should be possible to run a trained model and make predictions during a scenario&lt;br /&gt;
&#039;&#039;&#039;&lt;br /&gt;
Author&#039;&#039;&#039;&lt;br /&gt;
The author should be able to make changes to the Guide.&lt;br /&gt;
 &lt;br /&gt;
The author should be able to specify host and network information&lt;br /&gt;
&lt;br /&gt;
The author should be able to specify learning objectives for each task in a scenario&lt;br /&gt;
&lt;br /&gt;
The author should be able to specify milestones, i.e. indicators that a learning objective has been met.&lt;/div&gt;</summary>
		<author><name>Jack</name></author>
	</entry>
	<entry>
		<id>http://edurange.org/wiki/index.php?title=Main_Page&amp;diff=78</id>
		<title>Main Page</title>
		<link rel="alternate" type="text/html" href="http://edurange.org/wiki/index.php?title=Main_Page&amp;diff=78"/>
		<updated>2025-06-07T16:56:27Z</updated>

		<summary type="html">&lt;p&gt;Jack: Undo revision 76 by Jack (talk)&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Welcome to the EDURange Wiki. This is less of a formal documentation as it is a living document.&lt;br /&gt;
&lt;br /&gt;
I think that if it&#039;s a choice between losing track of people&#039;s contributions and having a messy wiki, I&#039;d much prefer a messy wiki.  It&#039;s my hope that we can gather all of our notes, thoughts, and loosely related ephemera here, rather than having it scattered across correspondence and various sharing platforms.&lt;br /&gt;
&lt;br /&gt;
We don&#039;t have many conventions yet. Please follow the Golden Rule and treat others as you would be treated. Put your name on your changes. Consider posting to the discussion tab or appending to a page rather than overwriting it. Try not to overwrite the personal work of others.&lt;br /&gt;
&lt;br /&gt;
If it&#039;s installable/runnable/code of any sort, put it on GitHub as well - preferably under the edurange organization so that our future contributors will have seamless access to it too.&lt;br /&gt;
&lt;br /&gt;
Thanks for your time.&lt;br /&gt;
&lt;br /&gt;
~Joe G&lt;br /&gt;
&lt;br /&gt;
== Developer Links ==&lt;br /&gt;
[[Style Guidelines and Developer Tools]]&lt;br /&gt;
&lt;br /&gt;
[[Reference Materials]]&lt;br /&gt;
&lt;br /&gt;
[[:Category:Demonstrations|Demonstrations]]&lt;br /&gt;
&lt;br /&gt;
[[Interface Design]]&lt;br /&gt;
&lt;br /&gt;
[[Scenario Creation Guide]]&lt;br /&gt;
&lt;br /&gt;
[[Terraform Design]]&lt;br /&gt;
&lt;br /&gt;
[[Hosting Requirements]]&lt;br /&gt;
&lt;br /&gt;
[[Domain Modeling]]&lt;br /&gt;
&lt;br /&gt;
== Orphaned Default Media Wiki Content ==&lt;br /&gt;
&amp;lt;strong&amp;gt;Welcome to the EDURange Wiki&amp;lt;/strong&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Consult the [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents User&#039;s Guide] for information on using the wiki software.&lt;br /&gt;
&lt;br /&gt;
== Getting started with Media Wiki ==&lt;br /&gt;
* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Configuration settings list]&lt;br /&gt;
* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ MediaWiki FAQ]&lt;br /&gt;
* [https://lists.wikimedia.org/postorius/lists/mediawiki-announce.lists.wikimedia.org/ MediaWiki release mailing list]&lt;br /&gt;
* [https://www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Localise MediaWiki for your language]&lt;br /&gt;
* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Combating_spam Learn how to combat spam on your wiki]&lt;/div&gt;</summary>
		<author><name>Jack</name></author>
	</entry>
	<entry>
		<id>http://edurange.org/wiki/index.php?title=Main_Page&amp;diff=76</id>
		<title>Main Page</title>
		<link rel="alternate" type="text/html" href="http://edurange.org/wiki/index.php?title=Main_Page&amp;diff=76"/>
		<updated>2025-06-06T00:08:33Z</updated>

		<summary type="html">&lt;p&gt;Jack: Reverted edits by Jwgranville (talk) to last revision by Edurange weiss&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&amp;lt;strong&amp;gt;Welcome to the EDURange Wiki&amp;lt;/strong&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Consult the [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents User&#039;s Guide] for information on using the wiki software.&lt;br /&gt;
&lt;br /&gt;
== Getting started with Media Wiki ==&lt;br /&gt;
* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Configuration settings list]&lt;br /&gt;
* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ MediaWiki FAQ]&lt;br /&gt;
* [https://lists.wikimedia.org/postorius/lists/mediawiki-announce.lists.wikimedia.org/ MediaWiki release mailing list]&lt;br /&gt;
* [https://www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Localise MediaWiki for your language]&lt;br /&gt;
* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Combating_spam Learn how to combat spam on your wiki]&lt;br /&gt;
&lt;br /&gt;
== Developer Links ==&lt;br /&gt;
[[Style Guidelines and Developer Tools]]&lt;br /&gt;
&lt;br /&gt;
[[Interface Design]]&lt;br /&gt;
&lt;br /&gt;
[[Scenario Creation Guide]]&lt;br /&gt;
&lt;br /&gt;
[[Terraform Design]]&lt;br /&gt;
&lt;br /&gt;
[[Hosting Requirements]]&lt;br /&gt;
&lt;br /&gt;
[[Domain Modeling]]&lt;/div&gt;</summary>
		<author><name>Jack</name></author>
	</entry>
	<entry>
		<id>http://edurange.org/wiki/index.php?title=Thread:Talk:Main_Page/Thread_number_two/reply_(2)&amp;diff=40</id>
		<title>Thread:Talk:Main Page/Thread number two/reply (2)</title>
		<link rel="alternate" type="text/html" href="http://edurange.org/wiki/index.php?title=Thread:Talk:Main_Page/Thread_number_two/reply_(2)&amp;diff=40"/>
		<updated>2025-04-04T20:31:07Z</updated>

		<summary type="html">&lt;p&gt;Jack: Reply to Thread number two&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Double Reply&lt;/div&gt;</summary>
		<author><name>Jack</name></author>
	</entry>
	<entry>
		<id>http://edurange.org/wiki/index.php?title=Thread:Talk:Main_Page/Thread_number_two/reply&amp;diff=39</id>
		<title>Thread:Talk:Main Page/Thread number two/reply</title>
		<link rel="alternate" type="text/html" href="http://edurange.org/wiki/index.php?title=Thread:Talk:Main_Page/Thread_number_two/reply&amp;diff=39"/>
		<updated>2025-04-04T20:24:48Z</updated>

		<summary type="html">&lt;p&gt;Jack: Reply to Thread number two&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Test2&lt;/div&gt;</summary>
		<author><name>Jack</name></author>
	</entry>
	<entry>
		<id>http://edurange.org/wiki/index.php?title=Thread:Talk:Main_Page/Thread_number_two&amp;diff=38</id>
		<title>Thread:Talk:Main Page/Thread number two</title>
		<link rel="alternate" type="text/html" href="http://edurange.org/wiki/index.php?title=Thread:Talk:Main_Page/Thread_number_two&amp;diff=38"/>
		<updated>2025-04-04T20:24:19Z</updated>

		<summary type="html">&lt;p&gt;Jack: New thread: Thread number two&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Test&lt;/div&gt;</summary>
		<author><name>Jack</name></author>
	</entry>
	<entry>
		<id>http://edurange.org/wiki/index.php?title=Talk:Main_Page&amp;diff=37</id>
		<title>Talk:Main Page</title>
		<link rel="alternate" type="text/html" href="http://edurange.org/wiki/index.php?title=Talk:Main_Page&amp;diff=37"/>
		<updated>2025-04-04T19:53:13Z</updated>

		<summary type="html">&lt;p&gt;Jack: /* Discussion Board -- Jack (talk) 19:30, 4 April 2025 (UTC) */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Jack</name></author>
	</entry>
	<entry>
		<id>http://edurange.org/wiki/index.php?title=Thread:Talk:Main_Page/This_is_a_new_thread/reply&amp;diff=36</id>
		<title>Thread:Talk:Main Page/This is a new thread/reply</title>
		<link rel="alternate" type="text/html" href="http://edurange.org/wiki/index.php?title=Thread:Talk:Main_Page/This_is_a_new_thread/reply&amp;diff=36"/>
		<updated>2025-04-04T19:52:25Z</updated>

		<summary type="html">&lt;p&gt;Jack: Reply to This is a new thread&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;This looks way better&lt;/div&gt;</summary>
		<author><name>Jack</name></author>
	</entry>
	<entry>
		<id>http://edurange.org/wiki/index.php?title=Thread:Talk:Main_Page/This_is_a_new_thread&amp;diff=35</id>
		<title>Thread:Talk:Main Page/This is a new thread</title>
		<link rel="alternate" type="text/html" href="http://edurange.org/wiki/index.php?title=Thread:Talk:Main_Page/This_is_a_new_thread&amp;diff=35"/>
		<updated>2025-04-04T19:52:12Z</updated>

		<summary type="html">&lt;p&gt;Jack: New thread: This is a new thread&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Testing a new extension&lt;/div&gt;</summary>
		<author><name>Jack</name></author>
	</entry>
	<entry>
		<id>http://edurange.org/wiki/index.php?title=Talk:Main_Page&amp;diff=33</id>
		<title>Talk:Main Page</title>
		<link rel="alternate" type="text/html" href="http://edurange.org/wiki/index.php?title=Talk:Main_Page&amp;diff=33"/>
		<updated>2025-04-04T19:30:26Z</updated>

		<summary type="html">&lt;p&gt;Jack: /* Discussion Board */ new section&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Discussion Board -- [[User:Jack|Jack]] ([[User talk:Jack|talk]]) 19:30, 4 April 2025 (UTC) ==&lt;br /&gt;
&lt;br /&gt;
Notes go here&lt;/div&gt;</summary>
		<author><name>Jack</name></author>
	</entry>
	<entry>
		<id>http://edurange.org/wiki/index.php?title=Talk:Main_Page&amp;diff=32</id>
		<title>Talk:Main Page</title>
		<link rel="alternate" type="text/html" href="http://edurange.org/wiki/index.php?title=Talk:Main_Page&amp;diff=32"/>
		<updated>2025-04-04T19:29:54Z</updated>

		<summary type="html">&lt;p&gt;Jack: Blanked the page&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Jack</name></author>
	</entry>
	<entry>
		<id>http://edurange.org/wiki/index.php?title=Style_Guidelines_and_Developer_Tools&amp;diff=31</id>
		<title>Style Guidelines and Developer Tools</title>
		<link rel="alternate" type="text/html" href="http://edurange.org/wiki/index.php?title=Style_Guidelines_and_Developer_Tools&amp;diff=31"/>
		<updated>2025-04-04T19:28:49Z</updated>

		<summary type="html">&lt;p&gt;Jack: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Style Guidelines  go here&lt;/div&gt;</summary>
		<author><name>Jack</name></author>
	</entry>
	<entry>
		<id>http://edurange.org/wiki/index.php?title=Talk:Main_Page&amp;diff=29</id>
		<title>Talk:Main Page</title>
		<link rel="alternate" type="text/html" href="http://edurange.org/wiki/index.php?title=Talk:Main_Page&amp;diff=29"/>
		<updated>2025-04-04T19:27:25Z</updated>

		<summary type="html">&lt;p&gt;Jack: /* Section */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Fresh Start Test&lt;br /&gt;
&lt;br /&gt;
== Test ==&lt;br /&gt;
&lt;br /&gt;
Hello again&lt;br /&gt;
&lt;br /&gt;
=== Header2 ===&lt;br /&gt;
&lt;br /&gt;
Test Again&lt;br /&gt;
&lt;br /&gt;
====Re: Header2 -- [[User:Jwgranville|&amp;amp;#126;Joe]] ([[User talk:Jwgranville|talk]]) 19:25, 4 April 2025 (UTC)====&lt;br /&gt;
&lt;br /&gt;
:: Replace this text with your reply&lt;br /&gt;
&lt;br /&gt;
== Section T. -- [[User:T.mccoy|T.mccoy]] ([[User talk:T.mccoy|talk]]) 19:25, 4 April 2025 (UTC) ==&lt;br /&gt;
&lt;br /&gt;
--[[User:T.mccoy|T.mccoy]] ([[User talk:T.mccoy|talk]]) 19:25, 4 April 2025 (UTC)&lt;br /&gt;
Welcome to the T Show&lt;br /&gt;
&lt;br /&gt;
== Test ==&lt;br /&gt;
&lt;br /&gt;
[[User:Jack|Jack]] ([[User talk:Jack|talk]])&lt;br /&gt;
&lt;br /&gt;
== Section ==&lt;br /&gt;
&lt;br /&gt;
No Signature :(&lt;br /&gt;
&lt;br /&gt;
===Re: Section -- [[User:Jack|Jack]] ([[User talk:Jack|talk]]) 19:27, 4 April 2025 (UTC)===&lt;br /&gt;
&lt;br /&gt;
: Hello There&lt;/div&gt;</summary>
		<author><name>Jack</name></author>
	</entry>
	<entry>
		<id>http://edurange.org/wiki/index.php?title=Talk:Main_Page&amp;diff=28</id>
		<title>Talk:Main Page</title>
		<link rel="alternate" type="text/html" href="http://edurange.org/wiki/index.php?title=Talk:Main_Page&amp;diff=28"/>
		<updated>2025-04-04T19:26:28Z</updated>

		<summary type="html">&lt;p&gt;Jack: /* Section */ new section&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Fresh Start Test&lt;br /&gt;
&lt;br /&gt;
== Test ==&lt;br /&gt;
&lt;br /&gt;
Hello again&lt;br /&gt;
&lt;br /&gt;
=== Header2 ===&lt;br /&gt;
&lt;br /&gt;
Test Again&lt;br /&gt;
&lt;br /&gt;
====Re: Header2 -- [[User:Jwgranville|&amp;amp;#126;Joe]] ([[User talk:Jwgranville|talk]]) 19:25, 4 April 2025 (UTC)====&lt;br /&gt;
&lt;br /&gt;
:: Replace this text with your reply&lt;br /&gt;
&lt;br /&gt;
== Section T. -- [[User:T.mccoy|T.mccoy]] ([[User talk:T.mccoy|talk]]) 19:25, 4 April 2025 (UTC) ==&lt;br /&gt;
&lt;br /&gt;
--[[User:T.mccoy|T.mccoy]] ([[User talk:T.mccoy|talk]]) 19:25, 4 April 2025 (UTC)&lt;br /&gt;
Welcome to the T Show&lt;br /&gt;
&lt;br /&gt;
== Test ==&lt;br /&gt;
&lt;br /&gt;
[[User:Jack|Jack]] ([[User talk:Jack|talk]])&lt;br /&gt;
&lt;br /&gt;
== Section ==&lt;br /&gt;
&lt;br /&gt;
No Signature :(&lt;/div&gt;</summary>
		<author><name>Jack</name></author>
	</entry>
	<entry>
		<id>http://edurange.org/wiki/index.php?title=Talk:Main_Page&amp;diff=27</id>
		<title>Talk:Main Page</title>
		<link rel="alternate" type="text/html" href="http://edurange.org/wiki/index.php?title=Talk:Main_Page&amp;diff=27"/>
		<updated>2025-04-04T19:25:34Z</updated>

		<summary type="html">&lt;p&gt;Jack: /* Test */ new section&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Fresh Start Test&lt;br /&gt;
&lt;br /&gt;
== Test ==&lt;br /&gt;
&lt;br /&gt;
Hello again&lt;br /&gt;
&lt;br /&gt;
=== Header2 ===&lt;br /&gt;
&lt;br /&gt;
Test Again&lt;br /&gt;
&lt;br /&gt;
====Re: Header2 -- [[User:Jwgranville|&amp;amp;#126;Joe]] ([[User talk:Jwgranville|talk]]) 19:25, 4 April 2025 (UTC)====&lt;br /&gt;
&lt;br /&gt;
:: Replace this text with your reply&lt;br /&gt;
&lt;br /&gt;
== Section T. -- [[User:T.mccoy|T.mccoy]] ([[User talk:T.mccoy|talk]]) 19:25, 4 April 2025 (UTC) ==&lt;br /&gt;
&lt;br /&gt;
--[[User:T.mccoy|T.mccoy]] ([[User talk:T.mccoy|talk]]) 19:25, 4 April 2025 (UTC)&lt;br /&gt;
Welcome to the T Show&lt;br /&gt;
&lt;br /&gt;
== Test ==&lt;br /&gt;
&lt;br /&gt;
[[User:Jack|Jack]] ([[User talk:Jack|talk]])&lt;/div&gt;</summary>
		<author><name>Jack</name></author>
	</entry>
	<entry>
		<id>http://edurange.org/wiki/index.php?title=Talk:Main_Page&amp;diff=24</id>
		<title>Talk:Main Page</title>
		<link rel="alternate" type="text/html" href="http://edurange.org/wiki/index.php?title=Talk:Main_Page&amp;diff=24"/>
		<updated>2025-04-04T19:24:57Z</updated>

		<summary type="html">&lt;p&gt;Jack: /* Test */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Fresh Start Test&lt;br /&gt;
&lt;br /&gt;
== Test ==&lt;br /&gt;
&lt;br /&gt;
Hello again&lt;br /&gt;
&lt;br /&gt;
=== Header2 ===&lt;br /&gt;
&lt;br /&gt;
Test Again&lt;/div&gt;</summary>
		<author><name>Jack</name></author>
	</entry>
	<entry>
		<id>http://edurange.org/wiki/index.php?title=Talk:Main_Page&amp;diff=23</id>
		<title>Talk:Main Page</title>
		<link rel="alternate" type="text/html" href="http://edurange.org/wiki/index.php?title=Talk:Main_Page&amp;diff=23"/>
		<updated>2025-04-04T19:24:28Z</updated>

		<summary type="html">&lt;p&gt;Jack: /* Test */ new section&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Fresh Start Test&lt;br /&gt;
&lt;br /&gt;
== Test ==&lt;br /&gt;
&lt;br /&gt;
Hello again&lt;/div&gt;</summary>
		<author><name>Jack</name></author>
	</entry>
	<entry>
		<id>http://edurange.org/wiki/index.php?title=Talk:Main_Page&amp;diff=22</id>
		<title>Talk:Main Page</title>
		<link rel="alternate" type="text/html" href="http://edurange.org/wiki/index.php?title=Talk:Main_Page&amp;diff=22"/>
		<updated>2025-04-04T19:23:41Z</updated>

		<summary type="html">&lt;p&gt;Jack: Replaced content with &amp;quot;Fresh Start Test&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Fresh Start Test&lt;/div&gt;</summary>
		<author><name>Jack</name></author>
	</entry>
	<entry>
		<id>http://edurange.org/wiki/index.php?title=Talk:Main_Page&amp;diff=21</id>
		<title>Talk:Main Page</title>
		<link rel="alternate" type="text/html" href="http://edurange.org/wiki/index.php?title=Talk:Main_Page&amp;diff=21"/>
		<updated>2025-04-04T19:20:56Z</updated>

		<summary type="html">&lt;p&gt;Jack: /* -- T.mccoy (talk) 19:20, 4 April 2025 (UTC) */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Let&#039;s talk about the main page - Jack&lt;br /&gt;
===================================&lt;br /&gt;
&lt;br /&gt;
===================-- [[User:T.mccoy|T.mccoy]] ([[User talk:T.mccoy|talk]]) 19:20, 4 April 2025 (UTC)===================&lt;br /&gt;
&lt;br /&gt;
::::::::::::::::: Hello&lt;/div&gt;</summary>
		<author><name>Jack</name></author>
	</entry>
	<entry>
		<id>http://edurange.org/wiki/index.php?title=User:Jack&amp;diff=3</id>
		<title>User:Jack</title>
		<link rel="alternate" type="text/html" href="http://edurange.org/wiki/index.php?title=User:Jack&amp;diff=3"/>
		<updated>2025-04-04T13:11:07Z</updated>

		<summary type="html">&lt;p&gt;Jack: created page&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Hey woah it&#039;s me&lt;/div&gt;</summary>
		<author><name>Jack</name></author>
	</entry>
	<entry>
		<id>http://edurange.org/wiki/index.php?title=Main_Page&amp;diff=2</id>
		<title>Main Page</title>
		<link rel="alternate" type="text/html" href="http://edurange.org/wiki/index.php?title=Main_Page&amp;diff=2"/>
		<updated>2025-04-04T13:10:39Z</updated>

		<summary type="html">&lt;p&gt;Jack: made placeholders to see how adding new pages works&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&amp;lt;strong&amp;gt;Welcome to the EDURange Wiki&amp;lt;/strong&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Consult the [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents User&#039;s Guide] for information on using the wiki software.&lt;br /&gt;
&lt;br /&gt;
== Getting started ==&lt;br /&gt;
* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Configuration settings list]&lt;br /&gt;
* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ MediaWiki FAQ]&lt;br /&gt;
* [https://lists.wikimedia.org/postorius/lists/mediawiki-announce.lists.wikimedia.org/ MediaWiki release mailing list]&lt;br /&gt;
* [https://www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Localise MediaWiki for your language]&lt;br /&gt;
* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Combating_spam Learn how to combat spam on your wiki]&lt;br /&gt;
&lt;br /&gt;
== Developer Links ==&lt;br /&gt;
[[Scenario Creation Guide]]&lt;br /&gt;
&lt;br /&gt;
[[Domain Modeling]]&lt;/div&gt;</summary>
		<author><name>Jack</name></author>
	</entry>
</feed>