openapi: 3.0.3
info:
  title: Intelligence Cloud Platform - Proxy Server API Gateway
  description: |
    The Proxy Server acts as the centralized API gateway, security boundary, and service orchestrator of the Intelligence Cloud Platform.
    It authenticates requests using Keycloak (OIDC), handles workspace isolation via LXD projects, manages network forwarding rules,
    and supports dynamic interactive container terminals and infrastructure agent commands.
  version: 1.0.0
servers:
  - url: https://api.intelligence-cloud.gr
    description: Production TUC environment gateway server
paths:
  /api/agent:
    post:
      summary: AI Infrastructure Agent Integration
      description: Sends a prompt to the AI agent chatbot. The agent uses LLM capability to translate natural language into actions (e.g. provisioning instances, raising issues).
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/AgentRequest'
      responses:
        '200':
          description: Successful response from the agent.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AgentResponse'
        '400':
          description: Bad request (e.g. missing message parameter).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GenericError'
        '401':
          description: Unauthorized. Invalid or missing token.
        '403':
          description: Forbidden. Insufficient roles.
        '500':
          description: Internal Server Error.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GenericError'

  /api/issues:
    get:
      summary: List GitHub Issues
      description: Fetches open GitHub issues for the configured project repository.
      security:
        - bearerAuth: []
      parameters:
        - name: page
          in: query
          description: Page number for pagination
          required: false
          schema:
            type: integer
            default: 1
        - name: per_page
          in: query
          description: Number of items per page
          required: false
          schema:
            type: integer
            default: 10
      responses:
        '200':
          description: A list of open issues.
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Issue'
        '401':
          description: Unauthorized.
        '503':
          description: GitHub integration not configured.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GenericError'
        '500':
          description: Failed to retrieve issues.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GenericError'

    post:
      summary: Create GitHub Issue
      description: Reports a platform problem or enhancement suggestion to the development team.
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/IssueCreate'
      responses:
        '201':
          description: Issue created successfully. Returns the full created issue resource.
          content:
            application/json:
              schema:
                type: object
        '400':
          description: Bad Request. Missing title or body.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GenericError'
        '401':
          description: Unauthorized.
        '503':
          description: GitHub integration not configured.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GenericError'
        '500':
          description: Failed to create issue.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GenericError'

  /api/terraform/validate:
    post:
      summary: Validate Terraform Code
      description: Performs static validation of dynamic HCL configuration code inside isolated workspaces.
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              properties:
                file:
                  type: string
                  format: binary
                  description: The .tf HCL file to validate.
              required:
                - file
      responses:
        '200':
          description: HCL code successfully parsed and validated.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TerraformValidationResult'
        '400':
          description: No file uploaded or static validation checks failed.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GenericError'
        '401':
          description: Unauthorized.
        '500':
          description: Internal proxy or backend parsing failed.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GenericError'

  /api/terraform/plan:
    post:
      summary: Generate Terraform Execution Plan
      description: Prepares a dry-run execution plan showing what infrastructure resources will be created/destroyed.
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              properties:
                file:
                  type: string
                  format: binary
                  description: The .tf HCL file to plan.
              required:
                - file
      responses:
        '200':
          description: Plan successfully computed.
          content:
            application/json:
              schema:
                type: object
                properties:
                  output:
                    type: string
                    description: Standard terraform plan textual output representation.
        '400':
          description: No file uploaded.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GenericError'
        '403':
          description: Only editors or administrators are allowed to plan configurations.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GenericError'
        '500':
          description: Execution plan failed to calculate.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GenericError'

  /api/terraform/apply:
    post:
      summary: Execute Terraform Apply
      description: Launches the dynamic Terraform deployment stream. Returns real-time log buffers (chunked transfer encoding).
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              properties:
                file:
                  type: string
                  format: binary
                  description: The .tf HCL file to execute.
              required:
                - file
      responses:
        '200':
          description: Configuration successfully launched. Returns chunked text/plain output stream.
          content:
            text/plain:
              schema:
                type: string
        '400':
          description: No file uploaded.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GenericError'
        '403':
          description: Only editors or administrators are allowed to deploy configurations.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GenericError'
        '500':
          description: Failed to initialize apply run.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GenericError'

  /api/instances:
    get:
      summary: List LXD Instances
      description: Lists VMs and containers located within the caller's tenancy workspace project. Admins can query specific projects.
      security:
        - bearerAuth: []
      parameters:
        - name: project
          in: query
          description: The LXD project context to query (Admins only). Defaults to current user project.
          required: false
          schema:
            type: string
      responses:
        '200':
          description: Successfully fetched instances.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/InstancesResponse'
        '401':
          description: Unauthorized.
        '500':
          description: Failed to communicate with hypervisor.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GenericError'

  /api/instances/{name}:
    delete:
      summary: Delete LXD Instance
      description: Deletes a stopped container or virtual machine in the workspace project context.
      security:
        - bearerAuth: []
      parameters:
        - name: name
          in: path
          description: The unique name of the target container or VM.
          required: true
          schema:
            type: string
        - name: project
          in: query
          description: The LXD project context (Admins only). Defaults to current user project.
          required: false
          schema:
            type: string
      responses:
        '200':
          description: Instance deletion operation successfully queued or completed.
          content:
            application/json:
              schema:
                type: object
        '400':
          description: Instance is not stopped, or invalid delete arguments.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GenericError'
        '401':
          description: Unauthorized.
        '404':
          description: Instance not found.
        '500':
          description: Internal LXD command error.

  /api/instances/{name}/state:
    put:
      summary: Modify Instance Run State
      description: Controls the power state of a virtual machine or container (start, stop, restart, freeze, unfreeze).
      security:
        - bearerAuth: []
      parameters:
        - name: name
          in: path
          description: The name of the target instance.
          required: true
          schema:
            type: string
        - name: project
          in: query
          description: The project context (Admins only). Defaults to user's project.
          required: false
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                action:
                  type: string
                  enum: [start, stop, restart, freeze, unfreeze]
                  description: The execution state control signal.
              required:
                - action
      responses:
        '200':
          description: State modification request accepted by the hypervisor.
          content:
            application/json:
              schema:
                type: object
        '401':
          description: Unauthorized.
        '500':
          description: LXD state controller error.

  /api/instances/{name}/files:
    get:
      summary: Download File from Instance
      description: Fetches a text or binary file from the filesystems inside a specific container or VM.
      security:
        - bearerAuth: []
      parameters:
        - name: name
          in: path
          description: The container or VM name.
          required: true
          schema:
            type: string
        - name: path
          in: query
          description: Absolute path inside the instance filesystem (e.g., /etc/nginx/nginx.conf).
          required: true
          schema:
            type: string
        - name: location
          in: query
          description: Target cluster member node if clustered.
          required: false
          schema:
            type: string
        - name: project
          in: query
          description: The project context. Defaults to user's project.
          required: false
          schema:
            type: string
      responses:
        '200':
          description: File successfully retrieved. Returns as an attachment download stream.
          headers:
            Content-Disposition:
              schema:
                type: string
                example: attachment; filename="nginx.conf"
            Content-Type:
              schema:
                type: string
                example: application/octet-stream
          content:
            application/octet-stream:
              schema:
                type: string
                format: binary
        '400':
          description: Missing path query parameter.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GenericError'
        '401':
          description: Unauthorized.
        '404':
          description: Instance or file not found.
        '500':
          description: File reading failed.

    post:
      summary: Upload File to Instance
      description: Writes a file dynamically into the instance filesystem. Overwrites matching target paths.
      security:
        - bearerAuth: []
      parameters:
        - name: name
          in: path
          description: The container or VM name.
          required: true
          schema:
            type: string
        - name: path
          in: query
          description: The destination path inside the instance (e.g., /var/www/index.html).
          required: true
          schema:
            type: string
        - name: location
          in: query
          description: Target cluster member node if clustered.
          required: false
          schema:
            type: string
        - name: project
          in: query
          description: The project context. Defaults to user's project.
          required: false
          schema:
            type: string
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              properties:
                file:
                  type: string
                  format: binary
                  description: File content buffer.
              required:
                - file
      responses:
        '200':
          description: File successfully uploaded and written.
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  message:
                    type: string
                    example: File uploaded successfully to /var/www/index.html
        '400':
          description: Missing file body or missing destination path.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GenericError'
        '401':
          description: Unauthorized.
        '500':
          description: File upload failed.

  /api/networks/reserved-ips:
    get:
      summary: Retrieve Reserved IPs
      description: Returns list of IPs in the UPLINK pool showing status, ports, and owner info.
      security:
        - bearerAuth: []
      responses:
        '200':
          description: IP allocations fetched.
          content:
            application/json:
              schema:
                type: object
                properties:
                  ips:
                    type: array
                    items:
                      $ref: '#/components/schemas/IPReservation'
        '401':
          description: Unauthorized.
        '500':
          description: IP list read failed.

    put:
      summary: Update Reserved IPs (Deprecated)
      description: Formerly configured the IP address space. Uplink pools are now dynamically auto-configured.
      security:
        - bearerAuth: []
      responses:
        '200':
          description: Deprecated acknowledgement response.
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  message:
                    type: string
                    example: Admins no longer configure pools manually. Sourced from UPLINK network.

  /api/networks/reserved-ips/reserve:
    post:
      summary: Reserve Public IP
      description: Claims an available public IP and binds it to a running instance (configures hypervisor DNAT forward).
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                ip:
                  type: string
                  format: ipv4
                  description: The target public IP.
                instanceName:
                  type: string
                  description: The instance to bind to.
              required:
                - ip
                - instanceName
      responses:
        '200':
          description: IP successfully reserved and mapped.
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  message:
                    type: string
                    example: IP 147.27.50.10 reserved for instance "server-1".
        '400':
          description: IP or instance is invalid, instance is not running, or IP is already claimed.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GenericError'
        '403':
          description: Only editors or administrators are authorized to reserve IPs.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GenericError'
        '404':
          description: Instance not found.
        '500':
          description: Internal allocation error.

  /api/networks/reserved-ips/release:
    post:
      summary: Release Public IP
      description: Frees a public IP, deleting the hypervisor forward mapping and returning it to the pool.
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                ip:
                  type: string
                  format: ipv4
                  description: The public IP to release.
              required:
                - ip
      responses:
        '200':
          description: IP successfully released.
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  message:
                    type: string
                    example: IP 147.27.50.10 has been successfully released.
        '400':
          description: IP is missing or not currently reserved.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GenericError'
        '403':
          description: Unauthorized to release another project's IP mapping.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GenericError'
        '500':
          description: Internal mapping deletion error.

  /api/networks/reserved-ips/ports:
    put:
      summary: Configure Port Forwarding Rules
      description: Sets the array of external-to-internal port mapping rules for a reserved IP.
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                ip:
                  type: string
                  format: ipv4
                  description: Target public IP.
                ports:
                  type: array
                  items:
                    $ref: '#/components/schemas/PortForwardingRule'
              required:
                - ip
                - ports
      responses:
        '200':
          description: Forwarding rules updated.
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  message:
                    type: string
                    example: Port forwarding rules updated successfully.
        '400':
          description: Missing parameters, invalid rules, or target container IP is unresolvable.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GenericError'
        '403':
          description: Unauthorized to update rules.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GenericError'
        '404':
          description: IP reservation context not found.
        '500':
          description: Port configuration mapping failed.

  /api/images:
    get:
      summary: List Remote Images
      description: Fetches supported OS templates from remote LXD image streams (Canonical/Ubuntu).
      security:
        - bearerAuth: []
      responses:
        '200':
          description: Remote templates fetched.
          content:
            application/json:
              schema:
                type: object
                properties:
                  metadata:
                    type: array
                    items:
                      $ref: '#/components/schemas/Image'
        '401':
          description: Unauthorized.
        '500':
          description: Failed to fetch image list.

  /metrics:
    get:
      summary: Read Prometheus Metrics
      description: Exposes real-time runtime instrumentation metrics (process CPU, memory, endpoint counters).
      security:
        - bearerAuth: []
      responses:
        '200':
          description: Raw Prometheus instrumentation document.
          content:
            text/plain:
              schema:
                type: string
                example: |
                  # HELP process_cpu_seconds_total Total user and system CPU time spent in seconds.
                  # TYPE process_cpu_seconds_total counter
                  process_cpu_seconds_total 0.12
        '401':
          description: Unauthorized.
        '500':
          description: Metrics rendering error.

  /terminal:
    get:
      summary: Interactive WebSocket TTY Tunnel
      description: |
        Establishes an interactive bi-directional console stream proxying shell sessions (xterm-256color)
        inside the specified hypervisor instance. Handled over standard WebSocket protocols.
      parameters:
        - name: token
          in: query
          required: true
          schema:
            type: string
          description: Keycloak OIDC bearer authentication token string.
        - name: instance
          in: query
          required: true
          schema:
            type: string
          description: Name of the instance to enter.
        - name: location
          in: query
          required: true
          schema:
            type: string
          description: Target cluster location/member containing the instance.
        - name: project
          in: query
          required: false
          schema:
            type: string
          description: LXD project namespace override (Admins only).
      responses:
        '101':
          description: Successful WebSocket handshake protocol upgrade.
        '401':
          description: Authentication failed.

components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: Keycloak realm client roles authorization JWT token.

  schemas:
    AgentRequest:
      type: object
      properties:
        message:
          type: string
          description: Natural language instruction command.
          example: "Create an Ubuntu 24.04 VM named prod-db with 2GB memory"
        history:
          type: array
          description: Message logs.
          items:
            type: object
            properties:
              role:
                type: string
                enum: [user, assistant]
              content:
                type: string
            required:
              - role
              - content
      required:
        - message

    AgentResponse:
      type: object
      properties:
        role:
          type: string
          example: assistant
        content:
          type: string
          description: Natural language feedback reply.
        action:
          type: object
          description: Optional payload resolved by MCP tools.
          properties:
            action:
              type: string
              example: apply_terraform
            filename:
              type: string
              example: prod_db.tf
            content:
              type: string
              description: Generated terraform content.
            description:
              type: string
              example: "Generated Terraform for virtual-machine instance: prod-db"
            title:
              type: string
              example: "bug: container service crashed"
            body:
              type: string
              example: "Details of the bug trace..."
            labels:
              type: array
              items:
                type: string
              example: ["bug"]
      required:
        - role
        - content

    Issue:
      type: object
      properties:
        id:
          type: integer
          example: 108342938
        number:
          type: integer
          example: 14
        title:
          type: string
          example: "Instance provision times out"
        body:
          type: string
          example: "The creation of Ubuntu 24.04 containers hangs on LXD default profiles."
        user:
          type: string
          example: "john-doe"
        created_at:
          type: string
          format: date-time
          example: "2026-06-22T19:30:00Z"
        html_url:
          type: string
          format: uri
          example: "https://github.com/company/repo/issues/14"
        labels:
          type: array
          items:
            type: string
          example: ["bug", "lxd"]

    IssueCreate:
      type: object
      properties:
        title:
          type: string
          example: "Bug: LXD projects quota exceeded"
        body:
          type: string
          example: "When attempting to create a 5th project, LXD returns restricted workspace errors."
        labels:
          type: array
          items:
            type: string
          example: ["bug"]
      required:
        - title
        - body

    TerraformValidationResult:
      type: object
      properties:
        valid:
          type: boolean
          example: true
        error_count:
          type: integer
          example: 0
        warning_count:
          type: integer
          example: 0
        diagnostics:
          type: array
          items:
            type: object
            properties:
              severity:
                type: string
                example: "error"
              summary:
                type: string
                example: "Invalid attribute definition"
              detail:
                type: string
                example: "The attribute 'project' is not supported in the root config."

    InstanceDetail:
      type: object
      properties:
        name:
          type: string
          example: "web-server"
        status:
          type: string
          example: "Running"
        status_code:
          type: integer
          example: 103
        project:
          type: string
          example: "u-9a8b7c6d-5e4f"
        ipv4_address:
          type: string
          format: ipv4
          example: "10.0.110.12"
        location:
          type: string
          example: "node-1"

    InstancesResponse:
      type: object
      properties:
        type:
          type: string
          example: "sync"
        status:
          type: string
          example: "Success"
        status_code:
          type: integer
          example: 200
        metadata:
          type: array
          items:
            $ref: '#/components/schemas/InstanceDetail'

    PortForwardingRule:
      type: object
      properties:
        protocol:
          type: string
          enum: [tcp, udp]
          example: "tcp"
        listen_port:
          type: string
          example: "80"
        target_port:
          type: string
          example: "80"
      required:
        - protocol
        - listen_port
        - target_port

    IPReservation:
      type: object
      properties:
        ip:
          type: string
          format: ipv4
          example: "147.27.50.10"
        reserved:
          type: boolean
          example: true
        projectId:
          type: string
          nullable: true
          example: "u-9a8b7c6d-5e4f"
        instanceName:
          type: string
          nullable: true
          example: "web-server"
        ports:
          type: array
          items:
            $ref: '#/components/schemas/PortForwardingRule'
        isMine:
          type: boolean
          example: true
        username:
          type: string
          nullable: true
          example: "john-doe"
          description: "Tenant owner's username. Visible to admin users only."

    Image:
      type: object
      properties:
        name:
          type: string
          example: "ubuntu:24.04"
        description:
          type: string
          example: "Ubuntu 24.04 LTS (Noble Numbat)"
        os:
          type: string
          example: "ubuntu"
        release:
          type: string
          example: "24.04"
        remote:
          type: string
          example: "ubuntu"

    GenericError:
      type: object
      properties:
        error:
          type: string
          example: "Validation failed"
        details:
          type: string
          example: "No file was found on the incoming body request."
