diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..707ba52 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,9 @@ +FROM python:3.9-slim-buster + +WORKDIR /app + +COPY script.py ./ + +RUN pip install flask pyyaml + +CMD ["python", "script.py"] diff --git a/docker-compose.yaml b/docker-compose.yaml new file mode 100644 index 0000000..f22eac2 --- /dev/null +++ b/docker-compose.yaml @@ -0,0 +1,12 @@ +services: + api: + build: + context: . + dockerfile: Dockerfile + environment: + - PORT=5000 + - CONFIG_FILE=/app/config.yml + ports: + - "5003:5000" + volumes: + - ./config.yml:/app/config.yml diff --git a/main.go b/main.go index ca74269..3abd008 100644 --- a/main.go +++ b/main.go @@ -7,14 +7,16 @@ import ( "net/http" ) -func NewClient(url string) *Client { - return &Client{ - url: url, - } +type Client struct { + url string + client *http.Client } -type Client struct { - url string +func NewClient(url string) *Client { + return &Client{ + url: url, + client: &http.Client{}, //optional, use for custom timeouts etc + } } type Route struct { @@ -22,55 +24,49 @@ type Route struct { Backend string `json:"backend"` } -func (c *Client) listRoutes() ([]Route, error) { - body := bytes.NewBufferString(`{"action": "ListRoutes"}`) - resp, err := http.Post(c.url, "application/json", body) +type apiResponse struct { + Routes []Route `json:"routes"` + Message string `json:"message"` + Error string `json:"error"` +} + +func (c *Client) doRequest(action string, payload interface{}) (*apiResponse, error) { + data, err := json.Marshal(map[string]interface{}{ + "action": action, + "routes": payload, + }) if err != nil { - return nil, err + return nil, fmt.Errorf("failed marshalling payload: %w", err) + } + + resp, err := c.client.Post(c.url, "application/json", bytes.NewBuffer(data)) + if err != nil { + return nil, fmt.Errorf("failed to send request: %w", err) } defer resp.Body.Close() - var response struct { - Routes []Route `json:"routes"` - Error string `json:"error"` + var apiResp apiResponse + if err := json.NewDecoder(resp.Body).Decode(&apiResp); err != nil { + return nil, fmt.Errorf("failed to decode response: %w", err) } - if err := json.NewDecoder(resp.Body).Decode(&response); err != nil { + + if apiResp.Error != "" { + return nil, fmt.Errorf("API error: %s", apiResp.Error) + } + + return &apiResp, nil +} + +func (c *Client) ListRoutes() ([]Route, error) { + resp, err := c.doRequest("ListRoutes", nil) + if err != nil { return nil, err } - if response.Error != "" { - return nil, fmt.Errorf("error listing routes: %s", response.Error) - } - return response.Routes, nil - + return resp.Routes, nil } -func (c *Client) updateRoutes(routes []Route) error { - payload := struct { - Action string `json:"action"` - Routes []Route `json:"routes"` - }{ - Action: "UpdateRoutes", - Routes: routes, - } - payloadBytes, err := json.Marshal(payload) - if err != nil { - return err - } - resp, err := http.Post(c.url, "application/json", bytes.NewBuffer(payloadBytes)) - if err != nil { - return err - } - defer resp.Body.Close() - - var response struct { - Message string `json:"message"` - Error string `json:"error"` - } - if err := json.NewDecoder(resp.Body).Decode(&response); err != nil { - return err - } - if response.Error != "" { - return fmt.Errorf("error updating routes: %s", response.Error) - } - return nil +func (c *Client) UpdateRoutes(routes []Route) error { + _, err := c.doRequest("UpdateRoutes", routes) + return err } + diff --git a/script.py b/script.py index 13a9170..39e3cd6 100644 --- a/script.py +++ b/script.py @@ -1,10 +1,15 @@ +import os import yaml from flask import Flask, request, jsonify +from werkzeug.exceptions import HTTPException app = Flask(__name__) CONFIG_FILE = "config.yml" +# Load config from environment variable or default to config.yml +CONFIG_FILE = os.environ.get('CONFIG_FILE', 'config.yml') + def load_config(): with open(CONFIG_FILE, "r") as f: @@ -29,21 +34,32 @@ def api_endpoint(): elif action == "UpdateRoutes": new_routes = data.get('routes') if not isinstance(new_routes, list): - return jsonify({"error": "Invalid routes format"}), 400 + # More specific exception + raise ValueError("Invalid routes format") config['config']['lite']['routes'] = new_routes save_config(config) return jsonify({"message": "Routes updated successfully"}) else: - return jsonify({"error": "Invalid action"}), 400 + raise ValueError("Invalid action") # More specific exception except FileNotFoundError: return jsonify({"error": "Config file not found"}), 500 except yaml.YAMLError as e: return jsonify({"error": f"Error parsing YAML: {e}"}), 500 + except ValueError as e: + # Return 400 for client-side errors + return jsonify({"error": str(e)}), 400 + except HTTPException as e: + # Handle HTTP exceptions gracefully + return jsonify({"error": str(e)}), e.code except Exception as e: - return jsonify({"error": f"An error occurred: {e}"}), 500 + # Log unexpected error for debugging + return jsonify({"error": f"An unexpected error occurred: {e}"}), 500 + + # This is to ensure you handle all scenarios + finally: + pass if __name__ == "__main__": - app.run(debug=True, host='0.0.0.0', port=5000) - + app.run(host='0.0.0.0', port=int(os.environ.get('PORT', 5000)))