Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
198 changes: 198 additions & 0 deletions API.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
# Inventory Management API

This is a CRUD application for tracking server states in a cloud service environment.

## Requirements

- Docker
- Docker Compose

## Running the Application

1. Clone the repository.
2. Navigate to the project directory.
3. Build and run the Docker containers:

ash
docker-compose up --build

4. The API will be accessible at `http://localhost:8000`.
- Swagger UI: `http://localhost:8000/docs`

## API Endpoints

- `POST /servers`: Create a server
- `GET /servers`: List all servers
- `GET /servers/{id}`: Get one server
- `PUT /servers/{id}`: Update server
- `DELETE /servers/{id}`: Delete server

## Project Structure

```
devops_inventory/
├── app/
│ ├── main.py # FastAPI application
│ └── database.py # Database connection and initialization
│ └── test_main.py # Pytest test suite
├── cli.py # Command-line interface
├── Dockerfile
├── docker-compose.yml
├── requirements.txt
├── README.md
├── API.md
└── CLI.md
```

## API Endpoints

### Create Server
```http
POST /servers
Content-Type: application/json

{
"hostname": "web-server-01",
"ip_address": "192.168.1.100",
"state": "active"
}
```

**Response:** `201 Created`
```json
{
"id": 1,
"hostname": "web-server-01",
"ip_address": "192.168.1.100",
"state": "active"
}
```

### List All Servers
```http
GET /servers
```

**Response:** `200 OK`
```json
[
{
"id": 1,
"hostname": "web-server-01",
"ip_address": "192.168.1.100",
"state": "active"
}
]
```

### Get Single Server
```http
GET /servers/{id}
```

**Response:** `200 OK` or `404 Not Found`

### Update Server
```http
PUT /servers/{id}
Content-Type: application/json

{
"hostname": "web-server-02",
"state": "offline"
}
```

**Response:** `200 OK` or `404 Not Found`

### Delete Server
```http
DELETE /servers/{id}
```

**Response:** `200 OK` or `404 Not Found`

## Validation Rules

### Hostname
- Must be unique across all servers
- Maximum length: 255 characters

### IP Address
- Must be a valid IPv4 address format
- Pattern: `0-255.0-255.0-255.0-255`
- Examples: `192.168.1.1`, `10.0.0.1`, `172.16.0.1`

### State
- Must be one of: `active`, `offline`, `retired`

## Development

### Stop the application
```bash
docker-compose down
```

### Stop and remove volumes (clean database)
```bash
docker-compose down -v
```

### View logs
```bash
docker-compose logs -f web
```

### Rebuild after code changes
```bash
docker-compose up --build
```

## Database

The application uses PostgreSQL with raw SQL queries as required. The database schema:

```sql
CREATE TABLE servers (
id SERIAL PRIMARY KEY,
hostname VARCHAR(255) UNIQUE NOT NULL,
ip_address VARCHAR(15) NOT NULL,
state VARCHAR(20) NOT NULL CHECK (state IN ('active', 'offline', 'retired'))
);
```

## Error Handling

The API returns appropriate HTTP status codes:

- `200 OK` - Successful GET, PUT, DELETE
- `201 Created` - Successful POST
- `400 Bad Request` - Validation errors (duplicate hostname, invalid data)
- `404 Not Found` - Resource not found
- `422 Unprocessable Entity` - Invalid request format
- `500 Internal Server Error` - Server errors

## Example Usage

### Using curl

```bash
# Create a server
curl -X POST http://localhost:8000/servers \
-H "Content-Type: application/json" \
-d '{"hostname":"web-01","ip_address":"192.168.1.100","state":"active"}'

# List all servers
curl http://localhost:8000/servers

# Get a specific server
curl http://localhost:8000/servers/1

# Update a server
curl -X PUT http://localhost:8000/servers/1 \
-H "Content-Type: application/json" \
-d '{"state":"offline"}'

# Delete a server
curl -X DELETE http://localhost:8000/servers/1
```
48 changes: 48 additions & 0 deletions CLI.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
## CLI Usage

The CLI provides an easy way to interact with the API from the command line.

### Install CLI dependencies (if running locally)

```bash
pip install -r requirements.txt
```

### CLI Commands

**Create a server:**
```bash
python cli.py create --hostname web-01 --ip 192.168.1.100 --state active
```

**List all servers:**
```bash
python cli.py list
```

**Get a specific server:**
```bash
python cli.py get 1
```

**Update a server:**
```bash
python cli.py update 1 --hostname web-02 --state offline
```

**Delete a server:**
```bash
python cli.py delete 1
```

### Using CLI inside Docker

```bash
docker-compose exec web python cli.py list
```

## Running Tests

```bash
docker-compose exec web -q
```
19 changes: 19 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
FROM python:3.11-slim

WORKDIR /app

# Install dependencies
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# Copy application code
COPY ./app ./app
COPY cli.py .



# Expose port
EXPOSE 8000

# Run the application
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
35 changes: 35 additions & 0 deletions app/database.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import psycopg2
import os

def get_db_connection():
"""Create and return a database connection"""
return psycopg2.connect(
host=os.getenv("DB_HOST", "db"),
database=os.getenv("DB_NAME", "inventory"),
user=os.getenv("DB_USER", "postgres"),
password=os.getenv("DB_PASSWORD", "postgres"),
port=os.getenv("DB_PORT", "5432")
)

def init_db():
"""Initialize the database with the servers table"""
conn = get_db_connection()
cursor = conn.cursor()

try:
cursor.execute("""
CREATE TABLE IF NOT EXISTS servers (
id SERIAL PRIMARY KEY,
hostname VARCHAR(255) UNIQUE NOT NULL,
ip_address VARCHAR(15) NOT NULL,
state VARCHAR(20) NOT NULL CHECK (state IN ('active', 'offline', 'retired'))
)
""")
conn.commit()
print("Database initialized successfully")
except Exception as e:
print(f"Error initializing database: {e}")
conn.rollback()
finally:
cursor.close()
conn.close()
Loading