You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@airavata.apache.org by la...@apache.org on 2026/06/12 22:19:17 UTC
(airavata-custos) 01/01: Integrated swag-driven OpenAPI spec generation
This is an automated email from the ASF dual-hosted git repository.
lahirujayathilake pushed a commit to branch swag-api
in repository https://gitbox.apache.org/repos/asf/airavata-custos.git
commit 1ec838001799aa57ff997c784bb175980047261a
Author: lahiruj <la...@gmail.com>
AuthorDate: Fri Jun 12 18:19:06 2026 -0400
Integrated swag-driven OpenAPI spec generation
---
Makefile | 30 +-
api/README.md | 55 +
api/core.openapi.yaml | 3766 ++++++++++++++++++++
cmd/server/main.go | 12 +
.../ACCESS/AMIE-Processor/api/amie.openapi.yaml | 81 +
.../ACCESS/AMIE-Processor/pkg/amie/loader.go | 12 +
.../ACCESS/AMIE-Processor/server/handlers.go | 14 +-
go.mod | 20 +
go.sum | 71 +
internal/server/audit_handlers.go | 45 +-
internal/server/privilege.go | 74 +-
internal/server/role.go | 130 +-
internal/server/server.go | 660 ++++
13 files changed, 4944 insertions(+), 26 deletions(-)
diff --git a/Makefile b/Makefile
index cc758c242..8eb569f9c 100644
--- a/Makefile
+++ b/Makefile
@@ -1,11 +1,39 @@
SHELL := /bin/bash
-.PHONY: help integration-test-amie integration-test-amie-down
+.PHONY: help gen-api build verify-no-drift test integration-test verify integration-test-amie integration-test-amie-down
help:
@echo "Targets:"
+ @echo " gen-api regenerate every OpenAPI spec from swag annotations"
+ @echo " build gen-api, then go build ./..."
+ @echo " verify-no-drift gen-api, then fail if any *.openapi.yaml changed"
+ @echo " test go test ./..."
+ @echo " integration-test full integration suite (AMIE for now)"
+ @echo " verify verify-no-drift + go vet + test"
@echo " integration-test-amie run the AMIE connector integration suite end-to-end"
@echo " integration-test-amie-down tear down the AMIE test stack only"
+gen-api:
+ go generate ./...
+
+build: gen-api
+ go build ./...
+
+verify-no-drift: gen-api
+ @if [ -n "$$(git status --porcelain api/ connectors/*/api/ 2>/dev/null)" ]; then \
+ echo "OpenAPI drift detected — run 'make gen-api' and commit the result."; \
+ git status --porcelain api/ connectors/*/api/; \
+ exit 1; \
+ fi
+
+test:
+ go test ./...
+
+verify: verify-no-drift
+ go vet ./...
+ $(MAKE) test
+
+integration-test: integration-test-amie
+
integration-test-amie:
bash scripts/run-amie-integration-tests.sh
diff --git a/api/README.md b/api/README.md
new file mode 100644
index 000000000..11f9ea4d1
--- /dev/null
+++ b/api/README.md
@@ -0,0 +1,55 @@
+<!--
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements. See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership. The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied. See the License for the
+specific language governing permissions and limitations
+under the License.
+-->
+
+# API specs
+
+Auto-generated OpenAPI specs for Apache Custos.
+
+## Layout
+
+One spec per code-ownership unit:
+
+| Spec | Path | Scope |
+|---|---|---|
+| Core | `api/core.openapi.yaml` | Core REST endpoints. |
+| Connector | `connectors/<NAME>/api/<name>.openapi.yaml` | A connector's endpoints under `/connectors/<name>/`. |
+
+Connector specs ship alongside the connector code; the core spec never
+declares connector endpoints. The reference connector today is AMIE at
+`connectors/ACCESS/AMIE-Processor/api/amie.openapi.yaml`.
+
+## Generating
+
+Specs are produced from [`swag`](https://github.com/swaggo/swag)
+annotations on each handler in the Go source. Regenerate after editing
+any annotation:
+
+```
+make gen-api
+```
+
+CI enforces freshness via `make verify-no-drift`: modifying a handler
+annotation without committing the regenerated spec fails the build. The
+`swag` version is pinned in `go.sum`; `go generate` invokes it through
+`go run`, so contributors do not need to install the binary separately.
+
+## Consuming
+
+Both files are standard OpenAPI documents and load into any
+OpenAPI-aware client, viewer, or codegen tool.
diff --git a/api/core.openapi.yaml b/api/core.openapi.yaml
new file mode 100644
index 000000000..e3334c3d2
--- /dev/null
+++ b/api/core.openapi.yaml
@@ -0,0 +1,3766 @@
+basePath: /
+definitions:
+ models.AllocationStatus:
+ enum:
+ - ACTIVE
+ - INACTIVE
+ - DELETED
+ type: string
+ x-enum-varnames:
+ - ACTIVE
+ - INACTIVE
+ - DELETED
+ models.ComputeAllocation:
+ properties:
+ compute_cluster_id:
+ description: The ID of the compute cluster where the allocation is provisioned.
+ type: string
+ end_time:
+ type: string
+ id:
+ type: string
+ initial_su_amount:
+ description: SUs allocated at the time of allocation creation.
+ type: integer
+ name:
+ type: string
+ project_id:
+ type: string
+ start_time:
+ type: string
+ status:
+ allOf:
+ - $ref: '#/definitions/models.AllocationStatus'
+ description: ACTIVE, INACTIVE, DELETED, etc.
+ type: object
+ models.ComputeAllocationChangeRequest:
+ properties:
+ approver_id:
+ description: The ID of the user who approved/rejected the change request,
+ if applicable.
+ type: string
+ change_status:
+ description: '"PENDING", "APPROVED", "REJECTED", etc.'
+ type: string
+ compute_allocation_id:
+ type: string
+ id:
+ type: string
+ reason:
+ description: The reason for the change request, e.g., "Need more SUs for upcoming
+ jobs", "Requesting reduction in SUs due to project completion", etc.
+ type: string
+ requested_status:
+ allOf:
+ - $ref: '#/definitions/models.AllocationStatus'
+ description: ACTIVE, INACTIVE, DELETED, etc.
+ requested_su_amount:
+ description: The requested allocation amount in SUs, e.g., 1200 SUs, etc.
+ type: integer
+ requester_id:
+ description: The ID of the user who made the change request.
+ type: string
+ timestamp:
+ description: The time when the change request was made.
+ type: string
+ type: object
+ models.ComputeAllocationChangeRequestEvent:
+ properties:
+ compute_allocation_change_request_id:
+ type: string
+ description:
+ description: Optional description of the event, e.g., "Change request created
+ by user", "Change request approved by admin", etc.
+ type: string
+ event_type:
+ description: '"CREATED", "APPROVED", "REJECTED", etc.'
+ type: string
+ id:
+ type: string
+ timestamp:
+ description: The time when the event occurred.
+ type: string
+ type: object
+ models.ComputeAllocationDiff:
+ properties:
+ compute_allocation_id:
+ type: string
+ description:
+ description: Optional description of the diff, e.g., "SU usage updated based
+ on job completion", "Allocation marked as INACTIVE due to end time reached",
+ etc.
+ type: string
+ diff_type:
+ description: '"USAGE_UPDATE", "ALLOCATION_STATUS_CHANGE", etc.'
+ type: string
+ id:
+ type: string
+ new_su_amount:
+ description: New allocation amount in SUs, e.g., 900 SUs, etc.
+ type: integer
+ status:
+ allOf:
+ - $ref: '#/definitions/models.AllocationStatus'
+ description: ACTIVE, INACTIVE, DELETED, etc.
+ timestamp:
+ description: The time when the diff was generated.
+ type: string
+ type: object
+ models.ComputeAllocationMembership:
+ properties:
+ compute_allocation_id:
+ type: string
+ end_time:
+ type: string
+ id:
+ type: string
+ membership_status:
+ allOf:
+ - $ref: '#/definitions/models.AllocationStatus'
+ description: ACTIVE, INACTIVE, etc.
+ start_time:
+ type: string
+ user_id:
+ type: string
+ type: object
+ models.ComputeAllocationMembershipResourceOverride:
+ properties:
+ compute_allocation_membership_id:
+ type: string
+ compute_allocation_resource_id:
+ type: string
+ id:
+ type: string
+ override_resource_amount:
+ description: The overridden amount of the resource for the user (e.g., number
+ of CPUs, GPUs).
+ type: integer
+ override_resource_time:
+ description: The overridden wall-clock time in minutes that the resource amount
+ is granted for.
+ type: integer
+ type: object
+ models.ComputeAllocationResource:
+ properties:
+ compute_cluster_id:
+ description: The ID of the compute cluster the resource (partition) belongs
+ to.
+ type: string
+ id:
+ type: string
+ name:
+ description: resource / partition name, e.g., "cpu-01", "gpu-01", "gpu-interactive",
+ etc.
+ type: string
+ resource_amount:
+ description: Number of CPUs, GPUs.
+ type: integer
+ resource_type:
+ description: 'TRES: cpu, gres/gpu'
+ type: string
+ type: object
+ models.ComputeAllocationResourceMapping:
+ properties:
+ compute_allocation_id:
+ type: string
+ compute_allocation_resource_id:
+ type: string
+ id:
+ type: string
+ resource_amount:
+ description: Amount of the resource allocated to this allocation (e.g., number
+ of CPUs, GPUs).
+ type: integer
+ resource_time:
+ description: Wall-clock time in minutes that the allocated amount is granted
+ for.
+ type: integer
+ type: object
+ models.ComputeAllocationResourceRate:
+ properties:
+ compute_allocation_resource_id:
+ type: string
+ end_time:
+ description: The time when this rate expires.
+ type: string
+ id:
+ type: string
+ rate:
+ description: The rate for the resource in SUs per unit, e.g., 0.5 SU per CPU
+ hour, 2 SU per GPU hour, etc.
+ type: number
+ start_time:
+ description: The time when this rate becomes effective.
+ type: string
+ type: object
+ models.ComputeAllocationUsage:
+ properties:
+ compute_allocation_id:
+ type: string
+ compute_allocation_resource_id:
+ description: The specific resource consumed, e.g., 20 CPU hours, 10 GPU hours,
+ etc.
+ type: string
+ id:
+ type: string
+ job_id:
+ description: The ID of the job that consumed the allocation.
+ type: string
+ last_updated:
+ description: The last time the usage was updated. SU should be calculated
+ up to this point in time and charge rates should be applied based on the
+ rates effective at this time.
+ type: string
+ used_raw_amount:
+ description: The raw amount of resource used, e.g., 20 CPU hours, 10 GPU hours,
+ etc.
+ type: number
+ used_su_amount:
+ description: SUs used by the allocation, e.g., 200 SUs, etc.
+ type: number
+ user_id:
+ description: The ID of the user who used the allocation.
+ type: string
+ type: object
+ models.ComputeCluster:
+ properties:
+ id:
+ type: string
+ name:
+ description: A human-readable name for the compute cluster, e.g., "Cluster
+ A", "Cluster B", etc.
+ type: string
+ type: object
+ models.ComputeClusterUser:
+ properties:
+ compute_cluster_id:
+ type: string
+ id:
+ type: string
+ local_username:
+ description: The username of the user on the compute cluster, which may be
+ different from their Airavata Custos username.
+ type: string
+ user_id:
+ type: string
+ type: object
+ models.Organization:
+ properties:
+ id:
+ type: string
+ name:
+ type: string
+ originated_id:
+ description: 'The ID of the organization in origination. For example: ACCESS
+ Record ID.'
+ type: string
+ type: object
+ models.PrivilegeKey:
+ enum:
+ - amie:read
+ - amie:write
+ - hpc:read
+ - hpc:write
+ - signer:read
+ - signer:write
+ - privileges:grant
+ - roles:manage
+ type: string
+ x-enum-varnames:
+ - PrivilegeAMIERead
+ - PrivilegeAMIEWrite
+ - PrivilegeHPCRead
+ - PrivilegeHPCWrite
+ - PrivilegeSignerRead
+ - PrivilegeSignerWrite
+ - PrivilegeGrant
+ - PrivilegeRolesManage
+ models.Project:
+ properties:
+ created_time:
+ type: string
+ id:
+ type: string
+ originated_id:
+ description: 'The ID of the project in origination. For example: ACCESS Record
+ ID.'
+ type: string
+ origination:
+ description: ACCESS, NAIRR, XRASS, etc.
+ type: string
+ project_pi_id:
+ type: string
+ status:
+ $ref: '#/definitions/models.ProjectStatus'
+ title:
+ type: string
+ type: object
+ models.ProjectStatus:
+ enum:
+ - ACTIVE
+ - INACTIVE
+ - DELETED
+ type: string
+ x-enum-varnames:
+ - ProjectActive
+ - ProjectInactive
+ - ProjectDeleted
+ models.Role:
+ properties:
+ created_at:
+ type: string
+ description:
+ type: string
+ id:
+ type: string
+ is_system:
+ type: boolean
+ name:
+ type: string
+ type: object
+ models.TraceEvent:
+ properties:
+ created_at:
+ type: string
+ description:
+ type: string
+ entity_id:
+ type: string
+ entity_type:
+ type: string
+ event_type:
+ type: string
+ parent_span_id:
+ type: string
+ source:
+ type: string
+ span_id:
+ type: string
+ status:
+ type: string
+ type: object
+ models.TraceNode:
+ properties:
+ children:
+ items:
+ $ref: '#/definitions/models.TraceNode'
+ type: array
+ created_at:
+ type: string
+ description:
+ type: string
+ entity_id:
+ type: string
+ entity_type:
+ type: string
+ event_type:
+ type: string
+ parent_span_id:
+ type: string
+ source:
+ type: string
+ span_id:
+ type: string
+ status:
+ type: string
+ type: object
+ models.TraceSummary:
+ properties:
+ ended_at:
+ type: string
+ event_count:
+ type: integer
+ root_operation:
+ type: string
+ source:
+ type: string
+ started_at:
+ type: string
+ status:
+ type: string
+ trace_id:
+ type: string
+ type: object
+ models.User:
+ properties:
+ email:
+ type: string
+ first_name:
+ type: string
+ id:
+ type: string
+ last_name:
+ type: string
+ middle_name:
+ type: string
+ organization_id:
+ type: string
+ status:
+ $ref: '#/definitions/models.UserStatus'
+ type: object
+ models.UserIdentity:
+ properties:
+ created_at:
+ type: string
+ email:
+ description: the email configured by the source
+ type: string
+ external_id:
+ description: the source's native identifier
+ type: string
+ id:
+ type: string
+ metadata:
+ description: JSON-encoded source-specific fields
+ type: string
+ oidc_sub:
+ description: OIDC subject when the source issues one
+ type: string
+ source:
+ description: e.g. access, nairr, cilogon
+ type: string
+ user_id:
+ type: string
+ type: object
+ models.UserPrivilege:
+ properties:
+ granted_at:
+ type: string
+ granted_by:
+ type: string
+ id:
+ type: string
+ privilege:
+ $ref: '#/definitions/models.PrivilegeKey'
+ reason:
+ type: string
+ user_id:
+ type: string
+ type: object
+ models.UserRole:
+ properties:
+ granted_at:
+ type: string
+ granted_by:
+ type: string
+ reason:
+ type: string
+ role_id:
+ type: string
+ user_id:
+ type: string
+ type: object
+ models.UserStatus:
+ enum:
+ - ACTIVE
+ - INACTIVE
+ - SUSPENDED
+ - MERGED
+ type: string
+ x-enum-varnames:
+ - UserActive
+ - UserInactive
+ - UserSuspended
+ - UserMerged
+ server.attachResourceRequest:
+ properties:
+ compute_allocation_resource_id:
+ type: string
+ resource_amount:
+ type: integer
+ resource_time:
+ type: integer
+ type: object
+ server.createRoleRequest:
+ properties:
+ description:
+ type: string
+ name:
+ type: string
+ type: object
+ server.grantPrivilegeRequest:
+ properties:
+ privilege:
+ $ref: '#/definitions/models.PrivilegeKey'
+ reason:
+ type: string
+ type: object
+ server.grantRoleRequest:
+ properties:
+ reason:
+ type: string
+ roleID:
+ type: string
+ type: object
+ server.mergeUsersRequest:
+ properties:
+ retiring_user_id:
+ type: string
+ surviving_user_id:
+ type: string
+ type: object
+ server.revokePrivilegeRequest:
+ properties:
+ reason:
+ type: string
+ type: object
+ server.revokeRoleRequest:
+ properties:
+ reason:
+ type: string
+ type: object
+ server.rolePrivilegeRequest:
+ properties:
+ privilege:
+ $ref: '#/definitions/models.PrivilegeKey'
+ type: object
+ server.updateAllocationResourceMappingRequest:
+ properties:
+ resource_amount:
+ type: integer
+ resource_time:
+ type: integer
+ type: object
+ server.updateRoleRequest:
+ properties:
+ description:
+ type: string
+ name:
+ type: string
+ type: object
+host: localhost:8080
+info:
+ contact: {}
+ description: 'REST API for Apache Custos: organizations, users, projects, compute
+ clusters and allocations, audit traces, and the privilege/role authorization layer.
+ Authenticate every request with the X-Custos-User-Id header. Connector endpoints
+ (/connectors/<name>/...) are documented in separate specs.'
+ title: Apache Custos Core API
+ version: 0.2.0
+paths:
+ /audit/events:
+ get:
+ parameters:
+ - description: 32-char lowercase hex
+ in: query
+ name: trace_id
+ required: true
+ type: string
+ - description: 16-char lowercase hex
+ in: query
+ name: span_id
+ type: string
+ produces:
+ - application/json
+ responses:
+ "200":
+ description: OK
+ schema:
+ properties:
+ events:
+ items:
+ $ref: '#/definitions/models.TraceEvent'
+ type: array
+ type: object
+ "400":
+ description: Malformed trace_id or span_id
+ schema:
+ properties:
+ error:
+ type: string
+ type: object
+ "503":
+ description: Audit trace store not configured
+ schema:
+ properties:
+ error:
+ type: string
+ type: object
+ security:
+ - CustosUserHeader: []
+ summary: List audit events for a trace (optionally one span)
+ tags:
+ - Audit
+ /audit/sources:
+ get:
+ produces:
+ - application/json
+ responses:
+ "200":
+ description: OK
+ schema:
+ properties:
+ sources:
+ items:
+ type: string
+ type: array
+ type: object
+ "503":
+ description: Audit trace store not configured
+ schema:
+ properties:
+ error:
+ type: string
+ type: object
+ security:
+ - CustosUserHeader: []
+ summary: List distinct audit-event sources
+ tags:
+ - Audit
+ /audit/traces:
+ get:
+ description: One row per distinct trace_id. `q` matches trace_id hex prefix
+ or root-operation substring.
+ parameters:
+ - collectionFormat: multi
+ description: Restrict to listed sources (repeatable)
+ in: query
+ items:
+ type: string
+ name: source
+ type: array
+ - collectionFormat: multi
+ description: ok | error | in_progress (repeatable)
+ in: query
+ items:
+ type: string
+ name: status
+ type: array
+ - description: RFC3339 lower bound on started_at
+ in: query
+ name: from
+ type: string
+ - description: RFC3339 upper bound on started_at
+ in: query
+ name: to
+ type: string
+ - description: Substring match on trace_id hex or root operation
+ in: query
+ name: q
+ type: string
+ - description: Page size (default 50, max 200)
+ in: query
+ name: limit
+ type: integer
+ - description: Page offset (default 0)
+ in: query
+ name: offset
+ type: integer
+ produces:
+ - application/json
+ responses:
+ "200":
+ description: OK
+ schema:
+ properties:
+ limit:
+ type: integer
+ offset:
+ type: integer
+ total:
+ type: integer
+ traces:
+ items:
+ $ref: '#/definitions/models.TraceSummary'
+ type: array
+ type: object
+ "400":
+ description: Invalid filter value
+ schema:
+ properties:
+ error:
+ type: string
+ type: object
+ "503":
+ description: Audit trace store not configured
+ schema:
+ properties:
+ error:
+ type: string
+ type: object
+ security:
+ - CustosUserHeader: []
+ summary: List audit traces with filters
+ tags:
+ - Audit
+ /audit/traces/{trace_id}:
+ get:
+ parameters:
+ - description: 32-char lowercase hex
+ in: path
+ name: trace_id
+ required: true
+ type: string
+ produces:
+ - application/json
+ responses:
+ "200":
+ description: OK
+ schema:
+ properties:
+ trace_id:
+ type: string
+ tree:
+ items:
+ $ref: '#/definitions/models.TraceNode'
+ type: array
+ truncated:
+ type: boolean
+ type: object
+ "400":
+ description: Malformed trace_id
+ schema:
+ properties:
+ error:
+ type: string
+ type: object
+ "404":
+ description: Trace not found
+ schema:
+ properties:
+ error:
+ type: string
+ type: object
+ "503":
+ description: Audit trace store not configured
+ schema:
+ properties:
+ error:
+ type: string
+ type: object
+ security:
+ - CustosUserHeader: []
+ summary: Get a trace's nested span tree
+ tags:
+ - Audit
+ /compute-allocation-change-request-events:
+ post:
+ consumes:
+ - application/json
+ parameters:
+ - description: Event payload
+ in: body
+ name: request
+ required: true
+ schema:
+ $ref: '#/definitions/models.ComputeAllocationChangeRequestEvent'
+ produces:
+ - application/json
+ responses:
+ "201":
+ description: Created
+ schema:
+ $ref: '#/definitions/models.ComputeAllocationChangeRequestEvent'
+ "400":
+ description: Bad Request
+ schema:
+ properties:
+ error:
+ type: string
+ type: object
+ security:
+ - CustosUserHeader: []
+ summary: Create a change request event
+ tags:
+ - Change Request Events
+ /compute-allocation-change-request-events/{id}:
+ delete:
+ parameters:
+ - description: Event ID
+ in: path
+ name: id
+ required: true
+ type: string
+ responses:
+ "204":
+ description: No Content
+ "404":
+ description: Not Found
+ schema:
+ properties:
+ error:
+ type: string
+ type: object
+ security:
+ - CustosUserHeader: []
+ summary: Delete a change request event
+ tags:
+ - Change Request Events
+ get:
+ parameters:
+ - description: Event ID
+ in: path
+ name: id
+ required: true
+ type: string
+ produces:
+ - application/json
+ responses:
+ "200":
+ description: OK
+ schema:
+ $ref: '#/definitions/models.ComputeAllocationChangeRequestEvent'
+ "404":
+ description: Not Found
+ schema:
+ properties:
+ error:
+ type: string
+ type: object
+ security:
+ - CustosUserHeader: []
+ summary: Get a change request event
+ tags:
+ - Change Request Events
+ /compute-allocation-change-requests:
+ post:
+ consumes:
+ - application/json
+ parameters:
+ - description: Change request payload
+ in: body
+ name: request
+ required: true
+ schema:
+ $ref: '#/definitions/models.ComputeAllocationChangeRequest'
+ produces:
+ - application/json
+ responses:
+ "201":
+ description: Created
+ schema:
+ $ref: '#/definitions/models.ComputeAllocationChangeRequest'
+ "400":
+ description: Bad Request
+ schema:
+ properties:
+ error:
+ type: string
+ type: object
+ security:
+ - CustosUserHeader: []
+ summary: Create a compute allocation change request
+ tags:
+ - Compute Allocation Change Requests
+ /compute-allocation-change-requests/{id}:
+ delete:
+ parameters:
+ - description: Change request ID
+ in: path
+ name: id
+ required: true
+ type: string
+ responses:
+ "204":
+ description: No Content
+ "404":
+ description: Not Found
+ schema:
+ properties:
+ error:
+ type: string
+ type: object
+ security:
+ - CustosUserHeader: []
+ summary: Delete a compute allocation change request
+ tags:
+ - Compute Allocation Change Requests
+ get:
+ parameters:
+ - description: Change request ID
+ in: path
+ name: id
+ required: true
+ type: string
+ produces:
+ - application/json
+ responses:
+ "200":
+ description: OK
+ schema:
+ $ref: '#/definitions/models.ComputeAllocationChangeRequest'
+ "404":
+ description: Not Found
+ schema:
+ properties:
+ error:
+ type: string
+ type: object
+ security:
+ - CustosUserHeader: []
+ summary: Get a compute allocation change request
+ tags:
+ - Compute Allocation Change Requests
+ put:
+ consumes:
+ - application/json
+ parameters:
+ - description: Change request ID
+ in: path
+ name: id
+ required: true
+ type: string
+ - description: Change request payload
+ in: body
+ name: request
+ required: true
+ schema:
+ $ref: '#/definitions/models.ComputeAllocationChangeRequest'
+ produces:
+ - application/json
+ responses:
+ "200":
+ description: OK
+ schema:
+ $ref: '#/definitions/models.ComputeAllocationChangeRequest'
+ "400":
+ description: Bad Request
+ schema:
+ properties:
+ error:
+ type: string
+ type: object
+ "404":
+ description: Not Found
+ schema:
+ properties:
+ error:
+ type: string
+ type: object
+ security:
+ - CustosUserHeader: []
+ summary: Update a compute allocation change request
+ tags:
+ - Compute Allocation Change Requests
+ /compute-allocation-change-requests/{id}/events:
+ get:
+ parameters:
+ - description: Change request ID
+ in: path
+ name: id
+ required: true
+ type: string
+ produces:
+ - application/json
+ responses:
+ "200":
+ description: OK
+ schema:
+ items:
+ $ref: '#/definitions/models.ComputeAllocationChangeRequestEvent'
+ type: array
+ "404":
+ description: Not Found
+ schema:
+ properties:
+ error:
+ type: string
+ type: object
+ security:
+ - CustosUserHeader: []
+ summary: List events for a change request
+ tags:
+ - Change Request Events
+ /compute-allocation-change-requests/{id}/events/latest:
+ get:
+ parameters:
+ - description: Change request ID
+ in: path
+ name: id
+ required: true
+ type: string
+ produces:
+ - application/json
+ responses:
+ "200":
+ description: OK
+ schema:
+ $ref: '#/definitions/models.ComputeAllocationChangeRequestEvent'
+ "404":
+ description: Not Found
+ schema:
+ properties:
+ error:
+ type: string
+ type: object
+ security:
+ - CustosUserHeader: []
+ summary: Get the most recent event for a change request
+ tags:
+ - Change Request Events
+ /compute-allocation-diffs:
+ post:
+ consumes:
+ - application/json
+ description: Records a discrete change against a compute allocation (e.g. USAGE_UPDATE
+ or ALLOCATION_STATUS_CHANGE).
+ parameters:
+ - description: Diff payload
+ in: body
+ name: request
+ required: true
+ schema:
+ $ref: '#/definitions/models.ComputeAllocationDiff'
+ produces:
+ - application/json
+ responses:
+ "201":
+ description: Created
+ schema:
+ $ref: '#/definitions/models.ComputeAllocationDiff'
+ "400":
+ description: Bad Request
+ schema:
+ properties:
+ error:
+ type: string
+ type: object
+ "404":
+ description: Not Found
+ schema:
+ properties:
+ error:
+ type: string
+ type: object
+ security:
+ - CustosUserHeader: []
+ summary: Create a compute allocation diff
+ tags:
+ - Compute Allocation Diffs
+ /compute-allocation-diffs/{id}:
+ delete:
+ parameters:
+ - description: Diff ID
+ in: path
+ name: id
+ required: true
+ type: string
+ responses:
+ "204":
+ description: No Content
+ "404":
+ description: Not Found
+ schema:
+ properties:
+ error:
+ type: string
+ type: object
+ security:
+ - CustosUserHeader: []
+ summary: Delete a compute allocation diff
+ tags:
+ - Compute Allocation Diffs
+ get:
+ parameters:
+ - description: Diff ID
+ in: path
+ name: id
+ required: true
+ type: string
+ produces:
+ - application/json
+ responses:
+ "200":
+ description: OK
+ schema:
+ $ref: '#/definitions/models.ComputeAllocationDiff'
+ "404":
+ description: Not Found
+ schema:
+ properties:
+ error:
+ type: string
+ type: object
+ security:
+ - CustosUserHeader: []
+ summary: Get a compute allocation diff
+ tags:
+ - Compute Allocation Diffs
+ /compute-allocation-membership-resource-overrides:
+ post:
+ consumes:
+ - application/json
+ parameters:
+ - description: Override payload
+ in: body
+ name: request
+ required: true
+ schema:
+ $ref: '#/definitions/models.ComputeAllocationMembershipResourceOverride'
+ produces:
+ - application/json
+ responses:
+ "201":
+ description: Created
+ schema:
+ $ref: '#/definitions/models.ComputeAllocationMembershipResourceOverride'
+ "400":
+ description: Bad Request
+ schema:
+ properties:
+ error:
+ type: string
+ type: object
+ security:
+ - CustosUserHeader: []
+ summary: Create a membership resource override
+ tags:
+ - Membership Resource Overrides
+ /compute-allocation-membership-resource-overrides/{id}:
+ delete:
+ parameters:
+ - description: Override ID
+ in: path
+ name: id
+ required: true
+ type: string
+ responses:
+ "204":
+ description: No Content
+ "404":
+ description: Not Found
+ schema:
+ properties:
+ error:
+ type: string
+ type: object
+ security:
+ - CustosUserHeader: []
+ summary: Delete a membership resource override
+ tags:
+ - Membership Resource Overrides
+ get:
+ parameters:
+ - description: Override ID
+ in: path
+ name: id
+ required: true
+ type: string
+ produces:
+ - application/json
+ responses:
+ "200":
+ description: OK
+ schema:
+ $ref: '#/definitions/models.ComputeAllocationMembershipResourceOverride'
+ "404":
+ description: Not Found
+ schema:
+ properties:
+ error:
+ type: string
+ type: object
+ security:
+ - CustosUserHeader: []
+ summary: Get a membership resource override
+ tags:
+ - Membership Resource Overrides
+ put:
+ consumes:
+ - application/json
+ parameters:
+ - description: Override ID
+ in: path
+ name: id
+ required: true
+ type: string
+ - description: Override payload
+ in: body
+ name: request
+ required: true
+ schema:
+ $ref: '#/definitions/models.ComputeAllocationMembershipResourceOverride'
+ produces:
+ - application/json
+ responses:
+ "200":
+ description: OK
+ schema:
+ $ref: '#/definitions/models.ComputeAllocationMembershipResourceOverride'
+ "400":
+ description: Bad Request
+ schema:
+ properties:
+ error:
+ type: string
+ type: object
+ "404":
+ description: Not Found
+ schema:
+ properties:
+ error:
+ type: string
+ type: object
+ security:
+ - CustosUserHeader: []
+ summary: Update a membership resource override
+ tags:
+ - Membership Resource Overrides
+ /compute-allocation-memberships:
+ post:
+ consumes:
+ - application/json
+ parameters:
+ - description: Membership payload
+ in: body
+ name: request
+ required: true
+ schema:
+ $ref: '#/definitions/models.ComputeAllocationMembership'
+ produces:
+ - application/json
+ responses:
+ "201":
+ description: Created
+ schema:
+ $ref: '#/definitions/models.ComputeAllocationMembership'
+ "400":
+ description: Bad Request
+ schema:
+ properties:
+ error:
+ type: string
+ type: object
+ security:
+ - CustosUserHeader: []
+ summary: Create a compute allocation membership
+ tags:
+ - Compute Allocation Memberships
+ /compute-allocation-memberships/{id}:
+ delete:
+ parameters:
+ - description: Membership ID
+ in: path
+ name: id
+ required: true
+ type: string
+ responses:
+ "204":
+ description: No Content
+ "404":
+ description: Not Found
+ schema:
+ properties:
+ error:
+ type: string
+ type: object
+ security:
+ - CustosUserHeader: []
+ summary: Delete a compute allocation membership
+ tags:
+ - Compute Allocation Memberships
+ get:
+ parameters:
+ - description: Membership ID
+ in: path
+ name: id
+ required: true
+ type: string
+ produces:
+ - application/json
+ responses:
+ "200":
+ description: OK
+ schema:
+ $ref: '#/definitions/models.ComputeAllocationMembership'
+ "404":
+ description: Not Found
+ schema:
+ properties:
+ error:
+ type: string
+ type: object
+ security:
+ - CustosUserHeader: []
+ summary: Get a compute allocation membership
+ tags:
+ - Compute Allocation Memberships
+ put:
+ consumes:
+ - application/json
+ parameters:
+ - description: Membership ID
+ in: path
+ name: id
+ required: true
+ type: string
+ - description: Membership payload
+ in: body
+ name: request
+ required: true
+ schema:
+ $ref: '#/definitions/models.ComputeAllocationMembership'
+ produces:
+ - application/json
+ responses:
+ "200":
+ description: OK
+ schema:
+ $ref: '#/definitions/models.ComputeAllocationMembership'
+ "400":
+ description: Bad Request
+ schema:
+ properties:
+ error:
+ type: string
+ type: object
+ "404":
+ description: Not Found
+ schema:
+ properties:
+ error:
+ type: string
+ type: object
+ security:
+ - CustosUserHeader: []
+ summary: Update a compute allocation membership
+ tags:
+ - Compute Allocation Memberships
+ /compute-allocation-memberships/{id}/resource-overrides:
+ get:
+ parameters:
+ - description: Membership ID
+ in: path
+ name: id
+ required: true
+ type: string
+ produces:
+ - application/json
+ responses:
+ "200":
+ description: OK
+ schema:
+ items:
+ $ref: '#/definitions/models.ComputeAllocationMembershipResourceOverride'
+ type: array
+ "404":
+ description: Not Found
+ schema:
+ properties:
+ error:
+ type: string
+ type: object
+ security:
+ - CustosUserHeader: []
+ summary: List resource overrides for a membership
+ tags:
+ - Membership Resource Overrides
+ /compute-allocation-memberships/{id}/status:
+ put:
+ consumes:
+ - application/json
+ parameters:
+ - description: Membership ID
+ in: path
+ name: id
+ required: true
+ type: string
+ - description: Status patch
+ in: body
+ name: request
+ required: true
+ schema:
+ properties:
+ membership_status:
+ $ref: '#/definitions/models.AllocationStatus'
+ type: object
+ produces:
+ - application/json
+ responses:
+ "200":
+ description: OK
+ schema:
+ $ref: '#/definitions/models.ComputeAllocationMembership'
+ "400":
+ description: Bad Request
+ schema:
+ properties:
+ error:
+ type: string
+ type: object
+ "404":
+ description: Not Found
+ schema:
+ properties:
+ error:
+ type: string
+ type: object
+ security:
+ - CustosUserHeader: []
+ summary: Update a membership's status
+ tags:
+ - Compute Allocation Memberships
+ /compute-allocation-resource-rates:
+ post:
+ consumes:
+ - application/json
+ description: Records the SU rate (e.g. SU per CPU-hour) for a resource over
+ a time window.
+ parameters:
+ - description: Rate payload
+ in: body
+ name: request
+ required: true
+ schema:
+ $ref: '#/definitions/models.ComputeAllocationResourceRate'
+ produces:
+ - application/json
+ responses:
+ "201":
+ description: Created
+ schema:
+ $ref: '#/definitions/models.ComputeAllocationResourceRate'
+ "400":
+ description: Bad Request
+ schema:
+ properties:
+ error:
+ type: string
+ type: object
+ "404":
+ description: Not Found
+ schema:
+ properties:
+ error:
+ type: string
+ type: object
+ security:
+ - CustosUserHeader: []
+ summary: Create a compute allocation resource rate
+ tags:
+ - Compute Allocation Resource Rates
+ /compute-allocation-resource-rates/{id}:
+ get:
+ parameters:
+ - description: Rate ID
+ in: path
+ name: id
+ required: true
+ type: string
+ produces:
+ - application/json
+ responses:
+ "200":
+ description: OK
+ schema:
+ $ref: '#/definitions/models.ComputeAllocationResourceRate'
+ "404":
+ description: Not Found
+ schema:
+ properties:
+ error:
+ type: string
+ type: object
+ security:
+ - CustosUserHeader: []
+ summary: Get a compute allocation resource rate
+ tags:
+ - Compute Allocation Resource Rates
+ /compute-allocation-resources:
+ get:
+ produces:
+ - application/json
+ responses:
+ "200":
+ description: OK
+ schema:
+ items:
+ $ref: '#/definitions/models.ComputeAllocationResource'
+ type: array
+ "401":
+ description: Unauthorized
+ schema:
+ properties:
+ error:
+ type: string
+ type: object
+ security:
+ - CustosUserHeader: []
+ summary: List all compute allocation resources
+ tags:
+ - Compute Allocation Resources
+ post:
+ consumes:
+ - application/json
+ description: Defines a resource (partition) — e.g. a CPU or GPU partition that
+ allocations can be attached to.
+ parameters:
+ - description: Resource payload
+ in: body
+ name: request
+ required: true
+ schema:
+ $ref: '#/definitions/models.ComputeAllocationResource'
+ produces:
+ - application/json
+ responses:
+ "201":
+ description: Created
+ schema:
+ $ref: '#/definitions/models.ComputeAllocationResource'
+ "400":
+ description: Bad Request
+ schema:
+ properties:
+ error:
+ type: string
+ type: object
+ "401":
+ description: Unauthorized
+ schema:
+ properties:
+ error:
+ type: string
+ type: object
+ security:
+ - CustosUserHeader: []
+ summary: Create a compute allocation resource
+ tags:
+ - Compute Allocation Resources
+ /compute-allocation-resources/{id}:
+ get:
+ parameters:
+ - description: Resource ID
+ in: path
+ name: id
+ required: true
+ type: string
+ produces:
+ - application/json
+ responses:
+ "200":
+ description: OK
+ schema:
+ $ref: '#/definitions/models.ComputeAllocationResource'
+ "404":
+ description: Not Found
+ schema:
+ properties:
+ error:
+ type: string
+ type: object
+ security:
+ - CustosUserHeader: []
+ summary: Get a compute allocation resource
+ tags:
+ - Compute Allocation Resources
+ /compute-allocation-resources/{id}/allocations:
+ get:
+ parameters:
+ - description: Compute allocation resource ID
+ in: path
+ name: id
+ required: true
+ type: string
+ produces:
+ - application/json
+ responses:
+ "200":
+ description: OK
+ schema:
+ items:
+ $ref: '#/definitions/models.ComputeAllocationResourceMapping'
+ type: array
+ "404":
+ description: Not Found
+ schema:
+ properties:
+ error:
+ type: string
+ type: object
+ security:
+ - CustosUserHeader: []
+ summary: List compute allocations attached to a resource
+ tags:
+ - Compute Allocation Resources
+ /compute-allocation-resources/{id}/membership-overrides:
+ get:
+ parameters:
+ - description: Compute allocation resource ID
+ in: path
+ name: id
+ required: true
+ type: string
+ produces:
+ - application/json
+ responses:
+ "200":
+ description: OK
+ schema:
+ items:
+ $ref: '#/definitions/models.ComputeAllocationMembershipResourceOverride'
+ type: array
+ "404":
+ description: Not Found
+ schema:
+ properties:
+ error:
+ type: string
+ type: object
+ security:
+ - CustosUserHeader: []
+ summary: List membership overrides referencing a resource
+ tags:
+ - Membership Resource Overrides
+ /compute-allocation-resources/{id}/rates:
+ get:
+ parameters:
+ - description: Compute allocation resource ID
+ in: path
+ name: id
+ required: true
+ type: string
+ produces:
+ - application/json
+ responses:
+ "200":
+ description: OK
+ schema:
+ items:
+ $ref: '#/definitions/models.ComputeAllocationResourceRate'
+ type: array
+ "404":
+ description: Not Found
+ schema:
+ properties:
+ error:
+ type: string
+ type: object
+ security:
+ - CustosUserHeader: []
+ summary: List rate history for a resource
+ tags:
+ - Compute Allocation Resource Rates
+ /compute-allocation-resources/{id}/rates/effective:
+ get:
+ description: Returns the rate whose `[start_time, end_time)` window contains
+ the `at` timestamp. Defaults to now when `at` is omitted.
+ parameters:
+ - description: Compute allocation resource ID
+ in: path
+ name: id
+ required: true
+ type: string
+ - description: RFC3339 time; defaults to now
+ in: query
+ name: at
+ type: string
+ produces:
+ - application/json
+ responses:
+ "200":
+ description: OK
+ schema:
+ $ref: '#/definitions/models.ComputeAllocationResourceRate'
+ "400":
+ description: Invalid 'at' query parameter
+ schema:
+ properties:
+ error:
+ type: string
+ type: object
+ "404":
+ description: Not Found
+ schema:
+ properties:
+ error:
+ type: string
+ type: object
+ security:
+ - CustosUserHeader: []
+ summary: Get the effective rate for a resource at a given time
+ tags:
+ - Compute Allocation Resource Rates
+ /compute-allocation-usages:
+ post:
+ consumes:
+ - application/json
+ parameters:
+ - description: Usage payload
+ in: body
+ name: request
+ required: true
+ schema:
+ $ref: '#/definitions/models.ComputeAllocationUsage'
+ produces:
+ - application/json
+ responses:
+ "201":
+ description: Created
+ schema:
+ $ref: '#/definitions/models.ComputeAllocationUsage'
+ "400":
+ description: Bad Request
+ schema:
+ properties:
+ error:
+ type: string
+ type: object
+ security:
+ - CustosUserHeader: []
+ summary: Create a compute allocation usage record
+ tags:
+ - Compute Allocation Usages
+ /compute-allocation-usages/{id}:
+ delete:
+ parameters:
+ - description: Usage ID
+ in: path
+ name: id
+ required: true
+ type: string
+ responses:
+ "204":
+ description: No Content
+ "404":
+ description: Not Found
+ schema:
+ properties:
+ error:
+ type: string
+ type: object
+ security:
+ - CustosUserHeader: []
+ summary: Delete a compute allocation usage record
+ tags:
+ - Compute Allocation Usages
+ get:
+ parameters:
+ - description: Usage ID
+ in: path
+ name: id
+ required: true
+ type: string
+ produces:
+ - application/json
+ responses:
+ "200":
+ description: OK
+ schema:
+ $ref: '#/definitions/models.ComputeAllocationUsage'
+ "404":
+ description: Not Found
+ schema:
+ properties:
+ error:
+ type: string
+ type: object
+ security:
+ - CustosUserHeader: []
+ summary: Get a compute allocation usage record
+ tags:
+ - Compute Allocation Usages
+ /compute-allocations:
+ post:
+ consumes:
+ - application/json
+ parameters:
+ - description: Compute allocation payload
+ in: body
+ name: request
+ required: true
+ schema:
+ $ref: '#/definitions/models.ComputeAllocation'
+ produces:
+ - application/json
+ responses:
+ "201":
+ description: Created
+ schema:
+ $ref: '#/definitions/models.ComputeAllocation'
+ "400":
+ description: Bad Request
+ schema:
+ properties:
+ error:
+ type: string
+ type: object
+ security:
+ - CustosUserHeader: []
+ summary: Create a compute allocation
+ tags:
+ - Compute Allocations
+ /compute-allocations/{id}:
+ get:
+ parameters:
+ - description: Compute allocation ID
+ in: path
+ name: id
+ required: true
+ type: string
+ produces:
+ - application/json
+ responses:
+ "200":
+ description: OK
+ schema:
+ $ref: '#/definitions/models.ComputeAllocation'
+ "404":
+ description: Not Found
+ schema:
+ properties:
+ error:
+ type: string
+ type: object
+ security:
+ - CustosUserHeader: []
+ summary: Get a compute allocation by ID
+ tags:
+ - Compute Allocations
+ /compute-allocations/{id}/change-requests:
+ get:
+ parameters:
+ - description: Compute allocation ID
+ in: path
+ name: id
+ required: true
+ type: string
+ produces:
+ - application/json
+ responses:
+ "200":
+ description: OK
+ schema:
+ items:
+ $ref: '#/definitions/models.ComputeAllocationChangeRequest'
+ type: array
+ "404":
+ description: Not Found
+ schema:
+ properties:
+ error:
+ type: string
+ type: object
+ security:
+ - CustosUserHeader: []
+ summary: List change requests for a compute allocation
+ tags:
+ - Compute Allocation Change Requests
+ /compute-allocations/{id}/diffs:
+ get:
+ parameters:
+ - description: Compute allocation ID
+ in: path
+ name: id
+ required: true
+ type: string
+ produces:
+ - application/json
+ responses:
+ "200":
+ description: OK
+ schema:
+ items:
+ $ref: '#/definitions/models.ComputeAllocationDiff'
+ type: array
+ "404":
+ description: Not Found
+ schema:
+ properties:
+ error:
+ type: string
+ type: object
+ security:
+ - CustosUserHeader: []
+ summary: List diffs for a compute allocation
+ tags:
+ - Compute Allocation Diffs
+ /compute-allocations/{id}/diffs/latest:
+ get:
+ parameters:
+ - description: Compute allocation ID
+ in: path
+ name: id
+ required: true
+ type: string
+ produces:
+ - application/json
+ responses:
+ "200":
+ description: OK
+ schema:
+ $ref: '#/definitions/models.ComputeAllocationDiff'
+ "404":
+ description: Not Found
+ schema:
+ properties:
+ error:
+ type: string
+ type: object
+ security:
+ - CustosUserHeader: []
+ summary: Get the most recent diff for a compute allocation
+ tags:
+ - Compute Allocation Diffs
+ /compute-allocations/{id}/memberships:
+ get:
+ parameters:
+ - description: Compute allocation ID
+ in: path
+ name: id
+ required: true
+ type: string
+ produces:
+ - application/json
+ responses:
+ "200":
+ description: OK
+ schema:
+ items:
+ $ref: '#/definitions/models.ComputeAllocationMembership'
+ type: array
+ "404":
+ description: Not Found
+ schema:
+ properties:
+ error:
+ type: string
+ type: object
+ security:
+ - CustosUserHeader: []
+ summary: List members of a compute allocation
+ tags:
+ - Compute Allocation Memberships
+ /compute-allocations/{id}/resources:
+ get:
+ parameters:
+ - description: Compute allocation ID
+ in: path
+ name: id
+ required: true
+ type: string
+ produces:
+ - application/json
+ responses:
+ "200":
+ description: OK
+ schema:
+ items:
+ $ref: '#/definitions/models.ComputeAllocationResourceMapping'
+ type: array
+ "404":
+ description: Not Found
+ schema:
+ properties:
+ error:
+ type: string
+ type: object
+ security:
+ - CustosUserHeader: []
+ summary: List resources attached to a compute allocation
+ tags:
+ - Compute Allocation Resources
+ post:
+ consumes:
+ - application/json
+ description: Creates a mapping between a compute allocation and a resource (partition)
+ with a specific amount and wall-clock time grant.
+ parameters:
+ - description: Compute allocation ID
+ in: path
+ name: id
+ required: true
+ type: string
+ - description: Attach payload
+ in: body
+ name: request
+ required: true
+ schema:
+ $ref: '#/definitions/server.attachResourceRequest'
+ produces:
+ - application/json
+ responses:
+ "201":
+ description: Created
+ schema:
+ $ref: '#/definitions/models.ComputeAllocationResourceMapping'
+ "400":
+ description: Bad Request
+ schema:
+ properties:
+ error:
+ type: string
+ type: object
+ "404":
+ description: Not Found
+ schema:
+ properties:
+ error:
+ type: string
+ type: object
+ security:
+ - CustosUserHeader: []
+ summary: Attach a resource to a compute allocation
+ tags:
+ - Compute Allocation Resources
+ /compute-allocations/{id}/resources/{resourceId}:
+ delete:
+ parameters:
+ - description: Compute allocation ID
+ in: path
+ name: id
+ required: true
+ type: string
+ - description: Compute allocation resource ID
+ in: path
+ name: resourceId
+ required: true
+ type: string
+ responses:
+ "204":
+ description: No Content
+ "404":
+ description: Not Found
+ schema:
+ properties:
+ error:
+ type: string
+ type: object
+ security:
+ - CustosUserHeader: []
+ summary: Detach a resource from a compute allocation
+ tags:
+ - Compute Allocation Resources
+ put:
+ consumes:
+ - application/json
+ parameters:
+ - description: Compute allocation ID
+ in: path
+ name: id
+ required: true
+ type: string
+ - description: Compute allocation resource ID
+ in: path
+ name: resourceId
+ required: true
+ type: string
+ - description: Mapping update payload
+ in: body
+ name: request
+ required: true
+ schema:
+ $ref: '#/definitions/server.updateAllocationResourceMappingRequest'
+ produces:
+ - application/json
+ responses:
+ "200":
+ description: OK
+ schema:
+ $ref: '#/definitions/models.ComputeAllocationResourceMapping'
+ "400":
+ description: Bad Request
+ schema:
+ properties:
+ error:
+ type: string
+ type: object
+ "404":
+ description: Not Found
+ schema:
+ properties:
+ error:
+ type: string
+ type: object
+ security:
+ - CustosUserHeader: []
+ summary: Update a compute allocation -> resource mapping
+ tags:
+ - Compute Allocation Resources
+ /compute-allocations/{id}/usages:
+ get:
+ parameters:
+ - description: Compute allocation ID
+ in: path
+ name: id
+ required: true
+ type: string
+ produces:
+ - application/json
+ responses:
+ "200":
+ description: OK
+ schema:
+ items:
+ $ref: '#/definitions/models.ComputeAllocationUsage'
+ type: array
+ "404":
+ description: Not Found
+ schema:
+ properties:
+ error:
+ type: string
+ type: object
+ security:
+ - CustosUserHeader: []
+ summary: List usages for a compute allocation
+ tags:
+ - Compute Allocation Usages
+ /compute-allocations/{id}/usages/total:
+ get:
+ parameters:
+ - description: Compute allocation ID
+ in: path
+ name: id
+ required: true
+ type: string
+ produces:
+ - application/json
+ responses:
+ "200":
+ description: OK
+ schema:
+ properties:
+ compute_allocation_id:
+ type: string
+ total_su_amount:
+ type: integer
+ type: object
+ "404":
+ description: Not Found
+ schema:
+ properties:
+ error:
+ type: string
+ type: object
+ security:
+ - CustosUserHeader: []
+ summary: Get total SU usage for a compute allocation
+ tags:
+ - Compute Allocation Usages
+ /compute-allocations/{id}/users/{userId}/usages/total:
+ get:
+ parameters:
+ - description: Compute allocation ID
+ in: path
+ name: id
+ required: true
+ type: string
+ - description: User ID
+ in: path
+ name: userId
+ required: true
+ type: string
+ produces:
+ - application/json
+ responses:
+ "200":
+ description: OK
+ schema:
+ properties:
+ compute_allocation_id:
+ type: string
+ total_su_amount:
+ type: integer
+ user_id:
+ type: string
+ type: object
+ "404":
+ description: Not Found
+ schema:
+ properties:
+ error:
+ type: string
+ type: object
+ security:
+ - CustosUserHeader: []
+ summary: Get total SU usage for a user within a compute allocation
+ tags:
+ - Compute Allocation Usages
+ /compute-cluster-users:
+ post:
+ consumes:
+ - application/json
+ parameters:
+ - description: Cluster user payload
+ in: body
+ name: request
+ required: true
+ schema:
+ $ref: '#/definitions/models.ComputeClusterUser'
+ produces:
+ - application/json
+ responses:
+ "201":
+ description: Created
+ schema:
+ $ref: '#/definitions/models.ComputeClusterUser'
+ "400":
+ description: Bad Request
+ schema:
+ properties:
+ error:
+ type: string
+ type: object
+ security:
+ - CustosUserHeader: []
+ summary: Create a compute cluster user
+ tags:
+ - Compute Cluster Users
+ /compute-cluster-users/{id}:
+ delete:
+ parameters:
+ - description: Compute cluster user ID
+ in: path
+ name: id
+ required: true
+ type: string
+ responses:
+ "204":
+ description: No Content
+ "404":
+ description: Not Found
+ schema:
+ properties:
+ error:
+ type: string
+ type: object
+ security:
+ - CustosUserHeader: []
+ summary: Delete a compute cluster user
+ tags:
+ - Compute Cluster Users
+ get:
+ parameters:
+ - description: Compute cluster user ID
+ in: path
+ name: id
+ required: true
+ type: string
+ produces:
+ - application/json
+ responses:
+ "200":
+ description: OK
+ schema:
+ $ref: '#/definitions/models.ComputeClusterUser'
+ "404":
+ description: Not Found
+ schema:
+ properties:
+ error:
+ type: string
+ type: object
+ security:
+ - CustosUserHeader: []
+ summary: Get a compute cluster user by ID
+ tags:
+ - Compute Cluster Users
+ put:
+ consumes:
+ - application/json
+ parameters:
+ - description: Compute cluster user ID
+ in: path
+ name: id
+ required: true
+ type: string
+ - description: Cluster user payload
+ in: body
+ name: request
+ required: true
+ schema:
+ $ref: '#/definitions/models.ComputeClusterUser'
+ produces:
+ - application/json
+ responses:
+ "200":
+ description: OK
+ schema:
+ $ref: '#/definitions/models.ComputeClusterUser'
+ "400":
+ description: Bad Request
+ schema:
+ properties:
+ error:
+ type: string
+ type: object
+ "404":
+ description: Not Found
+ schema:
+ properties:
+ error:
+ type: string
+ type: object
+ security:
+ - CustosUserHeader: []
+ summary: Update a compute cluster user
+ tags:
+ - Compute Cluster Users
+ /compute-clusters:
+ get:
+ produces:
+ - application/json
+ responses:
+ "200":
+ description: OK
+ schema:
+ items:
+ $ref: '#/definitions/models.ComputeCluster'
+ type: array
+ "500":
+ description: Internal Server Error
+ schema:
+ properties:
+ error:
+ type: string
+ type: object
+ security:
+ - CustosUserHeader: []
+ summary: List compute clusters
+ tags:
+ - Compute Clusters
+ post:
+ consumes:
+ - application/json
+ parameters:
+ - description: Cluster payload
+ in: body
+ name: request
+ required: true
+ schema:
+ $ref: '#/definitions/models.ComputeCluster'
+ produces:
+ - application/json
+ responses:
+ "201":
+ description: Created
+ schema:
+ $ref: '#/definitions/models.ComputeCluster'
+ "400":
+ description: Bad Request
+ schema:
+ properties:
+ error:
+ type: string
+ type: object
+ security:
+ - CustosUserHeader: []
+ summary: Create a compute cluster
+ tags:
+ - Compute Clusters
+ /compute-clusters/{id}:
+ get:
+ parameters:
+ - description: Compute cluster ID
+ in: path
+ name: id
+ required: true
+ type: string
+ produces:
+ - application/json
+ responses:
+ "200":
+ description: OK
+ schema:
+ $ref: '#/definitions/models.ComputeCluster'
+ "404":
+ description: Not Found
+ schema:
+ properties:
+ error:
+ type: string
+ type: object
+ security:
+ - CustosUserHeader: []
+ summary: Get a compute cluster by ID
+ tags:
+ - Compute Clusters
+ /compute-clusters/{id}/users:
+ get:
+ parameters:
+ - description: Compute cluster ID
+ in: path
+ name: id
+ required: true
+ type: string
+ produces:
+ - application/json
+ responses:
+ "200":
+ description: OK
+ schema:
+ items:
+ $ref: '#/definitions/models.ComputeClusterUser'
+ type: array
+ "404":
+ description: Not Found
+ schema:
+ properties:
+ error:
+ type: string
+ type: object
+ security:
+ - CustosUserHeader: []
+ summary: List users on a compute cluster
+ tags:
+ - Compute Cluster Users
+ /compute-clusters/{id}/users/{userId}:
+ get:
+ parameters:
+ - description: Compute cluster ID
+ in: path
+ name: id
+ required: true
+ type: string
+ - description: User ID
+ in: path
+ name: userId
+ required: true
+ type: string
+ produces:
+ - application/json
+ responses:
+ "200":
+ description: OK
+ schema:
+ $ref: '#/definitions/models.ComputeClusterUser'
+ "404":
+ description: Not Found
+ schema:
+ properties:
+ error:
+ type: string
+ type: object
+ security:
+ - CustosUserHeader: []
+ summary: Get a compute cluster user by (cluster, user) pair
+ tags:
+ - Compute Cluster Users
+ /organizations:
+ post:
+ consumes:
+ - application/json
+ parameters:
+ - description: Organization payload
+ in: body
+ name: request
+ required: true
+ schema:
+ $ref: '#/definitions/models.Organization'
+ produces:
+ - application/json
+ responses:
+ "201":
+ description: Created
+ schema:
+ $ref: '#/definitions/models.Organization'
+ "400":
+ description: Bad Request
+ schema:
+ properties:
+ error:
+ type: string
+ type: object
+ security:
+ - CustosUserHeader: []
+ summary: Create an organization
+ tags:
+ - Organizations
+ /organizations/{id}:
+ get:
+ parameters:
+ - description: Organization ID
+ in: path
+ name: id
+ required: true
+ type: string
+ produces:
+ - application/json
+ responses:
+ "200":
+ description: OK
+ schema:
+ $ref: '#/definitions/models.Organization'
+ "404":
+ description: Not Found
+ schema:
+ properties:
+ error:
+ type: string
+ type: object
+ security:
+ - CustosUserHeader: []
+ summary: Get an organization by ID
+ tags:
+ - Organizations
+ /privileges/{key}/holders:
+ get:
+ description: Role-derived holders are NOT listed here.
+ parameters:
+ - description: Privilege key
+ enum:
+ - amie:read
+ - amie:write
+ - hpc:read
+ - hpc:write
+ - signer:read
+ - signer:write
+ - privileges:grant
+ - roles:manage
+ in: path
+ name: key
+ required: true
+ type: string
+ produces:
+ - application/json
+ responses:
+ "200":
+ description: OK
+ schema:
+ items:
+ $ref: '#/definitions/models.UserPrivilege'
+ type: array
+ "400":
+ description: Unknown privilege key
+ schema:
+ properties:
+ error:
+ type: string
+ type: object
+ security:
+ - CustosUserHeader: []
+ summary: List direct holders of a privilege
+ tags:
+ - Privileges
+ /privileges/catalog:
+ get:
+ produces:
+ - application/json
+ responses:
+ "200":
+ description: OK
+ schema:
+ items:
+ $ref: '#/definitions/models.PrivilegeKey'
+ type: array
+ "401":
+ description: Missing caller header
+ schema:
+ properties:
+ error:
+ type: string
+ type: object
+ "403":
+ description: Caller lacks privileges:grant
+ schema:
+ properties:
+ error:
+ type: string
+ type: object
+ security:
+ - CustosUserHeader: []
+ summary: List the declared privilege catalog
+ tags:
+ - Privileges
+ /projects:
+ post:
+ consumes:
+ - application/json
+ parameters:
+ - description: Project payload
+ in: body
+ name: request
+ required: true
+ schema:
+ $ref: '#/definitions/models.Project'
+ produces:
+ - application/json
+ responses:
+ "201":
+ description: Created
+ schema:
+ $ref: '#/definitions/models.Project'
+ "400":
+ description: Bad Request
+ schema:
+ properties:
+ error:
+ type: string
+ type: object
+ security:
+ - CustosUserHeader: []
+ summary: Create a project
+ tags:
+ - Projects
+ /projects/{id}:
+ get:
+ parameters:
+ - description: Project ID
+ in: path
+ name: id
+ required: true
+ type: string
+ produces:
+ - application/json
+ responses:
+ "200":
+ description: OK
+ schema:
+ $ref: '#/definitions/models.Project'
+ "404":
+ description: Not Found
+ schema:
+ properties:
+ error:
+ type: string
+ type: object
+ security:
+ - CustosUserHeader: []
+ summary: Get a project by ID
+ tags:
+ - Projects
+ /projects/{id}/status:
+ put:
+ consumes:
+ - application/json
+ parameters:
+ - description: Project ID
+ in: path
+ name: id
+ required: true
+ type: string
+ - description: Status patch
+ in: body
+ name: request
+ required: true
+ schema:
+ properties:
+ status:
+ $ref: '#/definitions/models.ProjectStatus'
+ type: object
+ produces:
+ - application/json
+ responses:
+ "200":
+ description: OK
+ schema:
+ $ref: '#/definitions/models.Project'
+ "400":
+ description: Bad Request
+ schema:
+ properties:
+ error:
+ type: string
+ type: object
+ "404":
+ description: Not Found
+ schema:
+ properties:
+ error:
+ type: string
+ type: object
+ security:
+ - CustosUserHeader: []
+ summary: Update a project's status
+ tags:
+ - Projects
+ /roles:
+ get:
+ produces:
+ - application/json
+ responses:
+ "200":
+ description: OK
+ schema:
+ items:
+ $ref: '#/definitions/models.Role'
+ type: array
+ "401":
+ description: Unauthorized
+ schema:
+ properties:
+ error:
+ type: string
+ type: object
+ "403":
+ description: Caller lacks roles:manage
+ schema:
+ properties:
+ error:
+ type: string
+ type: object
+ security:
+ - CustosUserHeader: []
+ summary: List all roles
+ tags:
+ - Roles
+ post:
+ consumes:
+ - application/json
+ parameters:
+ - description: Role payload
+ in: body
+ name: request
+ required: true
+ schema:
+ $ref: '#/definitions/server.createRoleRequest'
+ produces:
+ - application/json
+ responses:
+ "201":
+ description: Created
+ schema:
+ $ref: '#/definitions/models.Role'
+ "400":
+ description: Bad Request
+ schema:
+ properties:
+ error:
+ type: string
+ type: object
+ "401":
+ description: Unauthorized
+ schema:
+ properties:
+ error:
+ type: string
+ type: object
+ "409":
+ description: Role name collides
+ schema:
+ properties:
+ error:
+ type: string
+ type: object
+ security:
+ - CustosUserHeader: []
+ summary: Create a role
+ tags:
+ - Roles
+ /roles/{id}:
+ delete:
+ description: System roles cannot be deleted. CASCADE drops every assignment
+ of this role.
+ parameters:
+ - description: Role ID
+ in: path
+ name: id
+ required: true
+ type: string
+ responses:
+ "204":
+ description: No Content
+ "400":
+ description: System role / unknown role
+ schema:
+ properties:
+ error:
+ type: string
+ type: object
+ "401":
+ description: Unauthorized
+ schema:
+ properties:
+ error:
+ type: string
+ type: object
+ security:
+ - CustosUserHeader: []
+ summary: Delete a role
+ tags:
+ - Roles
+ get:
+ parameters:
+ - description: Role ID
+ in: path
+ name: id
+ required: true
+ type: string
+ produces:
+ - application/json
+ responses:
+ "200":
+ description: OK
+ schema:
+ properties:
+ privileges:
+ items:
+ $ref: '#/definitions/models.PrivilegeKey'
+ type: array
+ role:
+ $ref: '#/definitions/models.Role'
+ type: object
+ "400":
+ description: Bad Request
+ schema:
+ properties:
+ error:
+ type: string
+ type: object
+ "404":
+ description: Not Found
+ schema:
+ properties:
+ error:
+ type: string
+ type: object
+ security:
+ - CustosUserHeader: []
+ summary: Get a role with its privilege bundle
+ tags:
+ - Roles
+ put:
+ consumes:
+ - application/json
+ description: System roles cannot be renamed.
+ parameters:
+ - description: Role ID
+ in: path
+ name: id
+ required: true
+ type: string
+ - description: Role patch
+ in: body
+ name: request
+ required: true
+ schema:
+ $ref: '#/definitions/server.updateRoleRequest'
+ produces:
+ - application/json
+ responses:
+ "200":
+ description: OK
+ schema:
+ $ref: '#/definitions/models.Role'
+ "400":
+ description: Bad Request
+ schema:
+ properties:
+ error:
+ type: string
+ type: object
+ "401":
+ description: Unauthorized
+ schema:
+ properties:
+ error:
+ type: string
+ type: object
+ security:
+ - CustosUserHeader: []
+ summary: Update role name / description
+ tags:
+ - Roles
+ /roles/{id}/holders:
+ get:
+ parameters:
+ - description: Role ID
+ in: path
+ name: id
+ required: true
+ type: string
+ produces:
+ - application/json
+ responses:
+ "200":
+ description: OK
+ schema:
+ items:
+ $ref: '#/definitions/models.UserRole'
+ type: array
+ "400":
+ description: Bad Request
+ schema:
+ properties:
+ error:
+ type: string
+ type: object
+ security:
+ - CustosUserHeader: []
+ summary: List users holding the role
+ tags:
+ - Roles
+ /roles/{id}/privileges:
+ post:
+ consumes:
+ - application/json
+ description: The new privilege propagates to every current holder.
+ parameters:
+ - description: Role ID
+ in: path
+ name: id
+ required: true
+ type: string
+ - description: Privilege key
+ in: body
+ name: request
+ required: true
+ schema:
+ $ref: '#/definitions/server.rolePrivilegeRequest'
+ responses:
+ "204":
+ description: No Content
+ "400":
+ description: Unknown key
+ schema:
+ properties:
+ error:
+ type: string
+ type: object
+ "401":
+ description: Unauthorized
+ schema:
+ properties:
+ error:
+ type: string
+ type: object
+ "409":
+ description: Role already carries that privilege
+ schema:
+ properties:
+ error:
+ type: string
+ type: object
+ security:
+ - CustosUserHeader: []
+ summary: Add a privilege to a role
+ tags:
+ - Roles
+ /roles/{id}/privileges/{key}:
+ delete:
+ description: Removal propagates to every holder. Refuses to remove `privileges:grant`
+ or `roles:manage` if that would leave no role anywhere carrying it.
+ parameters:
+ - description: Role ID
+ in: path
+ name: id
+ required: true
+ type: string
+ - description: Privilege key
+ enum:
+ - amie:read
+ - amie:write
+ - hpc:read
+ - hpc:write
+ - signer:read
+ - signer:write
+ - privileges:grant
+ - roles:manage
+ in: path
+ name: key
+ required: true
+ type: string
+ responses:
+ "204":
+ description: No Content
+ "400":
+ description: Last-source guard or unknown key
+ schema:
+ properties:
+ error:
+ type: string
+ type: object
+ "401":
+ description: Unauthorized
+ schema:
+ properties:
+ error:
+ type: string
+ type: object
+ "404":
+ description: Role does not carry that privilege
+ schema:
+ properties:
+ error:
+ type: string
+ type: object
+ security:
+ - CustosUserHeader: []
+ summary: Remove a privilege from a role
+ tags:
+ - Roles
+ /user-identities:
+ post:
+ consumes:
+ - application/json
+ parameters:
+ - description: Identity payload
+ in: body
+ name: request
+ required: true
+ schema:
+ $ref: '#/definitions/models.UserIdentity'
+ produces:
+ - application/json
+ responses:
+ "201":
+ description: Created
+ schema:
+ $ref: '#/definitions/models.UserIdentity'
+ "400":
+ description: Bad Request
+ schema:
+ properties:
+ error:
+ type: string
+ type: object
+ security:
+ - CustosUserHeader: []
+ summary: Create a user identity
+ tags:
+ - User Identities
+ /user-identities/{id}:
+ delete:
+ parameters:
+ - description: Identity ID
+ in: path
+ name: id
+ required: true
+ type: string
+ responses:
+ "204":
+ description: No Content
+ "404":
+ description: Not Found
+ schema:
+ properties:
+ error:
+ type: string
+ type: object
+ security:
+ - CustosUserHeader: []
+ summary: Delete a user identity
+ tags:
+ - User Identities
+ get:
+ parameters:
+ - description: Identity ID
+ in: path
+ name: id
+ required: true
+ type: string
+ produces:
+ - application/json
+ responses:
+ "200":
+ description: OK
+ schema:
+ $ref: '#/definitions/models.UserIdentity'
+ "404":
+ description: Not Found
+ schema:
+ properties:
+ error:
+ type: string
+ type: object
+ security:
+ - CustosUserHeader: []
+ summary: Get a user identity
+ tags:
+ - User Identities
+ put:
+ consumes:
+ - application/json
+ parameters:
+ - description: Identity ID
+ in: path
+ name: id
+ required: true
+ type: string
+ - description: Identity payload
+ in: body
+ name: request
+ required: true
+ schema:
+ $ref: '#/definitions/models.UserIdentity'
+ produces:
+ - application/json
+ responses:
+ "200":
+ description: OK
+ schema:
+ $ref: '#/definitions/models.UserIdentity'
+ "400":
+ description: Bad Request
+ schema:
+ properties:
+ error:
+ type: string
+ type: object
+ "404":
+ description: Not Found
+ schema:
+ properties:
+ error:
+ type: string
+ type: object
+ security:
+ - CustosUserHeader: []
+ summary: Update a user identity
+ tags:
+ - User Identities
+ /user-identities/oidc-subjects/{oidcSub}:
+ get:
+ parameters:
+ - description: OIDC subject claim
+ in: path
+ name: oidcSub
+ required: true
+ type: string
+ produces:
+ - application/json
+ responses:
+ "200":
+ description: OK
+ schema:
+ $ref: '#/definitions/models.UserIdentity'
+ "404":
+ description: Not Found
+ schema:
+ properties:
+ error:
+ type: string
+ type: object
+ security:
+ - CustosUserHeader: []
+ summary: Get a user identity by its OIDC subject claim
+ tags:
+ - User Identities
+ /user-identities/sources/{source}/external/{externalId}:
+ get:
+ parameters:
+ - description: Identity source
+ in: path
+ name: source
+ required: true
+ type: string
+ - description: Identity's external ID at that source
+ in: path
+ name: externalId
+ required: true
+ type: string
+ produces:
+ - application/json
+ responses:
+ "200":
+ description: OK
+ schema:
+ $ref: '#/definitions/models.UserIdentity'
+ "404":
+ description: Not Found
+ schema:
+ properties:
+ error:
+ type: string
+ type: object
+ security:
+ - CustosUserHeader: []
+ summary: Get a user identity by source and external ID
+ tags:
+ - User Identities
+ /user/privileges:
+ get:
+ description: Effective set is direct grants UNION every privilege carried by
+ every role the caller holds.
+ produces:
+ - application/json
+ responses:
+ "200":
+ description: Effective privilege set
+ schema:
+ properties:
+ privileges:
+ items:
+ $ref: '#/definitions/models.PrivilegeKey'
+ type: array
+ type: object
+ "401":
+ description: Missing X-Custos-User-Id header
+ schema:
+ properties:
+ error:
+ type: string
+ type: object
+ "503":
+ description: Auth lookup failed
+ schema:
+ properties:
+ error:
+ type: string
+ type: object
+ security:
+ - CustosUserHeader: []
+ summary: Get caller's effective privileges
+ tags:
+ - Caller
+ /users:
+ post:
+ consumes:
+ - application/json
+ parameters:
+ - description: User payload
+ in: body
+ name: request
+ required: true
+ schema:
+ $ref: '#/definitions/models.User'
+ produces:
+ - application/json
+ responses:
+ "201":
+ description: Created
+ schema:
+ $ref: '#/definitions/models.User'
+ "400":
+ description: Bad Request
+ schema:
+ properties:
+ error:
+ type: string
+ type: object
+ security:
+ - CustosUserHeader: []
+ summary: Create a user
+ tags:
+ - Users
+ /users/{id}:
+ get:
+ parameters:
+ - description: User ID
+ in: path
+ name: id
+ required: true
+ type: string
+ produces:
+ - application/json
+ responses:
+ "200":
+ description: OK
+ schema:
+ $ref: '#/definitions/models.User'
+ "404":
+ description: Not Found
+ schema:
+ properties:
+ error:
+ type: string
+ type: object
+ security:
+ - CustosUserHeader: []
+ summary: Get a user by ID
+ tags:
+ - Users
+ /users/{id}/change-requests:
+ get:
+ parameters:
+ - description: User ID
+ in: path
+ name: id
+ required: true
+ type: string
+ produces:
+ - application/json
+ responses:
+ "200":
+ description: OK
+ schema:
+ items:
+ $ref: '#/definitions/models.ComputeAllocationChangeRequest'
+ type: array
+ "404":
+ description: Not Found
+ schema:
+ properties:
+ error:
+ type: string
+ type: object
+ security:
+ - CustosUserHeader: []
+ summary: List change requests submitted by a user
+ tags:
+ - Compute Allocation Change Requests
+ /users/{id}/compute-allocation-memberships:
+ get:
+ parameters:
+ - description: User ID
+ in: path
+ name: id
+ required: true
+ type: string
+ produces:
+ - application/json
+ responses:
+ "200":
+ description: OK
+ schema:
+ items:
+ $ref: '#/definitions/models.ComputeAllocationMembership'
+ type: array
+ "404":
+ description: Not Found
+ schema:
+ properties:
+ error:
+ type: string
+ type: object
+ security:
+ - CustosUserHeader: []
+ summary: List a user's compute allocation memberships
+ tags:
+ - Compute Allocation Memberships
+ /users/{id}/compute-allocation-usages:
+ get:
+ parameters:
+ - description: User ID
+ in: path
+ name: id
+ required: true
+ type: string
+ produces:
+ - application/json
+ responses:
+ "200":
+ description: OK
+ schema:
+ items:
+ $ref: '#/definitions/models.ComputeAllocationUsage'
+ type: array
+ "404":
+ description: Not Found
+ schema:
+ properties:
+ error:
+ type: string
+ type: object
+ security:
+ - CustosUserHeader: []
+ summary: List usages submitted by a user
+ tags:
+ - Compute Allocation Usages
+ /users/{id}/compute-cluster-users:
+ get:
+ parameters:
+ - description: User ID
+ in: path
+ name: id
+ required: true
+ type: string
+ produces:
+ - application/json
+ responses:
+ "200":
+ description: OK
+ schema:
+ items:
+ $ref: '#/definitions/models.ComputeClusterUser'
+ type: array
+ "404":
+ description: Not Found
+ schema:
+ properties:
+ error:
+ type: string
+ type: object
+ security:
+ - CustosUserHeader: []
+ summary: List compute cluster users for a user
+ tags:
+ - Compute Cluster Users
+ /users/{id}/privileges:
+ get:
+ description: Returns DIRECT grants only (not role-derived). Combine with `GET
+ /users/{id}/roles` for the full picture.
+ parameters:
+ - description: User ID
+ in: path
+ name: id
+ required: true
+ type: string
+ produces:
+ - application/json
+ responses:
+ "200":
+ description: OK
+ schema:
+ items:
+ $ref: '#/definitions/models.UserPrivilege'
+ type: array
+ "400":
+ description: Bad Request
+ schema:
+ properties:
+ error:
+ type: string
+ type: object
+ "403":
+ description: Caller lacks privileges:grant
+ schema:
+ properties:
+ error:
+ type: string
+ type: object
+ security:
+ - CustosUserHeader: []
+ summary: List a user's direct privilege grants
+ tags:
+ - Privileges
+ post:
+ consumes:
+ - application/json
+ parameters:
+ - description: User ID
+ in: path
+ name: id
+ required: true
+ type: string
+ - description: Grant payload
+ in: body
+ name: request
+ required: true
+ schema:
+ $ref: '#/definitions/server.grantPrivilegeRequest'
+ produces:
+ - application/json
+ responses:
+ "201":
+ description: Created
+ schema:
+ $ref: '#/definitions/models.UserPrivilege'
+ "400":
+ description: Bad request
+ schema:
+ properties:
+ error:
+ type: string
+ type: object
+ "401":
+ description: Missing caller header
+ schema:
+ properties:
+ error:
+ type: string
+ type: object
+ "403":
+ description: Caller lacks privileges:grant
+ schema:
+ properties:
+ error:
+ type: string
+ type: object
+ "409":
+ description: Privilege already active for user
+ schema:
+ properties:
+ error:
+ type: string
+ type: object
+ security:
+ - CustosUserHeader: []
+ summary: Grant a direct privilege to a user
+ tags:
+ - Privileges
+ /users/{id}/privileges/{key}:
+ delete:
+ consumes:
+ - application/json
+ description: '`privileges:grant` itself cannot be self-revoked or revoked from
+ the last holder.'
+ parameters:
+ - description: User ID
+ in: path
+ name: id
+ required: true
+ type: string
+ - description: Privilege key
+ enum:
+ - amie:read
+ - amie:write
+ - hpc:read
+ - hpc:write
+ - signer:read
+ - signer:write
+ - privileges:grant
+ - roles:manage
+ in: path
+ name: key
+ required: true
+ type: string
+ - description: Optional reason
+ in: body
+ name: request
+ schema:
+ $ref: '#/definitions/server.revokePrivilegeRequest'
+ responses:
+ "204":
+ description: No Content
+ "400":
+ description: Self-revoke meta, last-holder, or unknown key
+ schema:
+ properties:
+ error:
+ type: string
+ type: object
+ "401":
+ description: Missing caller header
+ schema:
+ properties:
+ error:
+ type: string
+ type: object
+ "403":
+ description: Caller lacks privileges:grant
+ schema:
+ properties:
+ error:
+ type: string
+ type: object
+ "404":
+ description: No active grant for that key
+ schema:
+ properties:
+ error:
+ type: string
+ type: object
+ security:
+ - CustosUserHeader: []
+ summary: Revoke a direct privilege from a user
+ tags:
+ - Privileges
+ /users/{id}/roles:
+ get:
+ parameters:
+ - description: User ID
+ in: path
+ name: id
+ required: true
+ type: string
+ produces:
+ - application/json
+ responses:
+ "200":
+ description: OK
+ schema:
+ items:
+ $ref: '#/definitions/models.UserRole'
+ type: array
+ "400":
+ description: Bad Request
+ schema:
+ properties:
+ error:
+ type: string
+ type: object
+ security:
+ - CustosUserHeader: []
+ summary: List roles a user holds
+ tags:
+ - Role Assignments
+ post:
+ consumes:
+ - application/json
+ parameters:
+ - description: User ID
+ in: path
+ name: id
+ required: true
+ type: string
+ - description: Role assignment payload
+ in: body
+ name: request
+ required: true
+ schema:
+ $ref: '#/definitions/server.grantRoleRequest'
+ produces:
+ - application/json
+ responses:
+ "201":
+ description: Created
+ schema:
+ $ref: '#/definitions/models.UserRole'
+ "400":
+ description: Bad Request
+ schema:
+ properties:
+ error:
+ type: string
+ type: object
+ "401":
+ description: Unauthorized
+ schema:
+ properties:
+ error:
+ type: string
+ type: object
+ "409":
+ description: User already holds that role
+ schema:
+ properties:
+ error:
+ type: string
+ type: object
+ security:
+ - CustosUserHeader: []
+ summary: Grant a role to a user
+ tags:
+ - Role Assignments
+ /users/{id}/roles/{roleId}:
+ delete:
+ consumes:
+ - application/json
+ description: Refuses if revoking would leave no holder of `privileges:grant`
+ or `roles:manage` anywhere (last-meta-holder guard).
+ parameters:
+ - description: User ID
+ in: path
+ name: id
+ required: true
+ type: string
+ - description: Role ID
+ in: path
+ name: roleId
+ required: true
+ type: string
+ - description: Optional reason
+ in: body
+ name: request
+ schema:
+ $ref: '#/definitions/server.revokeRoleRequest'
+ responses:
+ "204":
+ description: No Content
+ "400":
+ description: Last-meta-holder guard
+ schema:
+ properties:
+ error:
+ type: string
+ type: object
+ "401":
+ description: Unauthorized
+ schema:
+ properties:
+ error:
+ type: string
+ type: object
+ "404":
+ description: User does not hold that role
+ schema:
+ properties:
+ error:
+ type: string
+ type: object
+ security:
+ - CustosUserHeader: []
+ summary: Revoke a role from a user
+ tags:
+ - Role Assignments
+ /users/{id}/status:
+ put:
+ consumes:
+ - application/json
+ parameters:
+ - description: User ID
+ in: path
+ name: id
+ required: true
+ type: string
+ - description: Status patch
+ in: body
+ name: request
+ required: true
+ schema:
+ properties:
+ status:
+ $ref: '#/definitions/models.UserStatus'
+ type: object
+ produces:
+ - application/json
+ responses:
+ "200":
+ description: OK
+ schema:
+ $ref: '#/definitions/models.User'
+ "400":
+ description: Bad Request
+ schema:
+ properties:
+ error:
+ type: string
+ type: object
+ "404":
+ description: Not Found
+ schema:
+ properties:
+ error:
+ type: string
+ type: object
+ security:
+ - CustosUserHeader: []
+ summary: Update a user's status
+ tags:
+ - Users
+ /users/{id}/user-identities:
+ get:
+ parameters:
+ - description: User ID
+ in: path
+ name: id
+ required: true
+ type: string
+ produces:
+ - application/json
+ responses:
+ "200":
+ description: OK
+ schema:
+ items:
+ $ref: '#/definitions/models.UserIdentity'
+ type: array
+ "404":
+ description: Not Found
+ schema:
+ properties:
+ error:
+ type: string
+ type: object
+ security:
+ - CustosUserHeader: []
+ summary: List a user's identities
+ tags:
+ - User Identities
+ /users/merge:
+ post:
+ consumes:
+ - application/json
+ description: Merges the retiring user into the surviving user; the surviving
+ record is returned.
+ parameters:
+ - description: Merge payload
+ in: body
+ name: request
+ required: true
+ schema:
+ $ref: '#/definitions/server.mergeUsersRequest'
+ produces:
+ - application/json
+ responses:
+ "200":
+ description: OK
+ schema:
+ $ref: '#/definitions/models.User'
+ "400":
+ description: Bad Request
+ schema:
+ properties:
+ error:
+ type: string
+ type: object
+ "404":
+ description: Not Found
+ schema:
+ properties:
+ error:
+ type: string
+ type: object
+ security:
+ - CustosUserHeader: []
+ summary: Merge two users
+ tags:
+ - Users
+securityDefinitions:
+ CustosUserHeader:
+ in: header
+ name: X-Custos-User-Id
+ type: apiKey
+swagger: "2.0"
diff --git a/cmd/server/main.go b/cmd/server/main.go
index c1c94ea44..318dffcbb 100644
--- a/cmd/server/main.go
+++ b/cmd/server/main.go
@@ -18,6 +18,9 @@
// Command server starts the Custos HTTP API.
package main
+//go:generate go run github.com/swaggo/swag/cmd/swag init -g main.go -d .,../../internal/server,../../pkg/models -o ../../api --outputTypes yaml --parseDependency
+//go:generate mv ../../api/swagger.yaml ../../api/core.openapi.yaml
+
import (
"context"
"errors"
@@ -39,6 +42,15 @@ import (
"github.com/apache/airavata-custos/pkg/service"
)
+// @title Apache Custos Core API
+// @version 0.2.0
+// @description REST API for Apache Custos: organizations, users, projects, compute clusters and allocations, audit traces, and the privilege/role authorization layer. Authenticate every request with the X-Custos-User-Id header. Connector endpoints (/connectors/<name>/...) are documented in separate specs.
+// @host localhost:8080
+// @BasePath /
+// @securityDefinitions.apikey CustosUserHeader
+// @in header
+// @name X-Custos-User-Id
+// @description.CustosUserHeader Identifies the calling user. Dev: dev-admin, dev-operator, dev-auditor, dev-researcher (see dev-ops/compose/seeds/dev_users_and_roles.sql).
func main() {
slog.SetDefault(slog.New(tracing.SlogHandler(slog.NewJSONHandler(os.Stdout, nil))))
diff --git a/connectors/ACCESS/AMIE-Processor/api/amie.openapi.yaml b/connectors/ACCESS/AMIE-Processor/api/amie.openapi.yaml
new file mode 100644
index 000000000..4828073c1
--- /dev/null
+++ b/connectors/ACCESS/AMIE-Processor/api/amie.openapi.yaml
@@ -0,0 +1,81 @@
+basePath: /
+host: localhost:8080
+info:
+ contact: {}
+ description: REST endpoints for the ACCESS-CI AMIE connector, all under /connectors/amie/.
+ title: AMIE Connector API
+ version: 0.1.0
+paths:
+ /connectors/amie/packets/{packet_id}/audits:
+ get:
+ parameters:
+ - description: AMIE packet ID
+ in: path
+ name: packet_id
+ required: true
+ type: string
+ produces:
+ - application/json
+ responses:
+ "200":
+ description: OK
+ schema:
+ properties:
+ events:
+ items:
+ properties:
+ created_at:
+ type: string
+ description:
+ type: string
+ entity_id:
+ type: string
+ entity_type:
+ type: string
+ event_type:
+ type: string
+ parent_span_id:
+ type: string
+ source:
+ type: string
+ span_id:
+ type: string
+ status:
+ type: string
+ type: object
+ type: array
+ packet_id:
+ type: string
+ type: object
+ "400":
+ description: packet_id is required
+ schema:
+ properties:
+ error:
+ type: string
+ type: object
+ "500":
+ description: Store lookup failed
+ schema:
+ properties:
+ error:
+ type: string
+ type: object
+ "503":
+ description: AMIE packet audit store not configured
+ schema:
+ properties:
+ error:
+ type: string
+ type: object
+ security:
+ - CustosUserHeader: []
+ summary: List audit events for an AMIE packet
+ tags:
+ - AMIE Audit
+securityDefinitions:
+ CustosUserHeader:
+ in: header
+ name: X-Custos-User-Id
+ type: apiKey
+swagger: "2.0"
diff --git a/connectors/ACCESS/AMIE-Processor/pkg/amie/loader.go b/connectors/ACCESS/AMIE-Processor/pkg/amie/loader.go
index 03e35f7c7..7363a8bfa 100644
--- a/connectors/ACCESS/AMIE-Processor/pkg/amie/loader.go
+++ b/connectors/ACCESS/AMIE-Processor/pkg/amie/loader.go
@@ -18,6 +18,9 @@
// Package amie wires the ACCESS-AMIE connector into the core binary.
package amie
+//go:generate go run github.com/swaggo/swag/cmd/swag init -g loader.go -d .,../../server,../../store -o ../../api --outputTypes yaml --parseDependency
+//go:generate mv ../../api/swagger.yaml ../../api/amie.openapi.yaml
+
import (
"context"
"log/slog"
@@ -54,6 +57,15 @@ const connectorName = "amie"
// LoadConnector skips silently when AMIE_BASE_URL / AMIE_SITE_CODE /
// AMIE_API_KEY are not all set. When enabled, it attaches /connectors/amie/*
// endpoints to mux.
+//
+// @title AMIE Connector API
+// @version 0.1.0
+// @description REST endpoints for the ACCESS-CI AMIE connector, all under /connectors/amie/.
+// @host localhost:8080
+// @BasePath /
+// @securityDefinitions.apikey CustosUserHeader
+// @in header
+// @name X-Custos-User-Id
func LoadConnector(ctx context.Context, database *sqlx.DB, eventBus *events.Bus, coreService *coreservice.Service, wg *sync.WaitGroup, mux *http.ServeMux) error {
cfg := loadConfig()
if cfg.AMIE.APIKey == "" || cfg.AMIE.BaseURL == "" || cfg.AMIE.SiteCode == "" {
diff --git a/connectors/ACCESS/AMIE-Processor/server/handlers.go b/connectors/ACCESS/AMIE-Processor/server/handlers.go
index 5e675069d..408dda313 100644
--- a/connectors/ACCESS/AMIE-Processor/server/handlers.go
+++ b/connectors/ACCESS/AMIE-Processor/server/handlers.go
@@ -15,7 +15,7 @@
// specific language governing permissions and limitations
// under the License.
-// Package server exposes the AMIE connector's HTTP read surface.
+// Package server exposes the AMIE connector's REST read endpoints.
package server
import (
@@ -40,8 +40,16 @@ func (h *Handlers) RegisterRoutes(mux *http.ServeMux) {
mux.HandleFunc("GET /connectors/amie/packets/{packet_id}/audits", h.listPacketAudits)
}
-// listPacketAudits returns every audit_events row written for one AMIE packet,
-// joined through amie_audit_extras.
+// @Summary List audit events for an AMIE packet
+// @Tags AMIE Audit
+// @Security CustosUserHeader
+// @Produce json
+// @Param packet_id path string true "AMIE packet ID"
+// @Success 200 {object} object{packet_id=string,events=[]object{span_id=string,parent_span_id=string,source=string,event_type=string,entity_type=string,entity_id=string,description=string,status=string,created_at=string}}
+// @Failure 400 {object} object{error=string} "packet_id is required"
+// @Failure 500 {object} object{error=string} "Store lookup failed"
+// @Failure 503 {object} object{error=string} "AMIE packet audit store not configured"
+// @Router /connectors/amie/packets/{packet_id}/audits [get]
func (h *Handlers) listPacketAudits(w http.ResponseWriter, r *http.Request) {
if h.audits == nil {
writeError(w, http.StatusServiceUnavailable, errors.New("amie packet audit store not configured"))
diff --git a/go.mod b/go.mod
index dc178be85..9da28975c 100644
--- a/go.mod
+++ b/go.mod
@@ -10,6 +10,7 @@ require (
github.com/prometheus/client_golang v1.23.2
github.com/prometheus/client_model v0.6.2
github.com/stretchr/testify v1.11.1
+ github.com/swaggo/swag v1.16.6
go.opentelemetry.io/otel v1.41.0
go.opentelemetry.io/otel/sdk v1.41.0
go.opentelemetry.io/otel/trace v1.41.0
@@ -19,18 +20,37 @@ require (
require (
filippo.io/edwards25519 v1.1.1 // indirect
+ github.com/KyleBanks/depth v1.2.1 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
+ github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
+ github.com/go-openapi/jsonpointer v0.21.0 // indirect
+ github.com/go-openapi/jsonreference v0.21.0 // indirect
+ github.com/go-openapi/spec v0.20.4 // indirect
+ github.com/go-openapi/swag v0.23.0 // indirect
+ github.com/josharian/intern v1.0.0 // indirect
+ github.com/mailru/easyjson v0.7.7 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/prometheus/common v0.66.1 // indirect
github.com/prometheus/procfs v0.16.1 // indirect
+ github.com/russross/blackfriday/v2 v2.0.1 // indirect
+ github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect
github.com/stretchr/objx v0.5.2 // indirect
+ github.com/urfave/cli/v2 v2.3.0 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/otel/metric v1.41.0 // indirect
go.yaml.in/yaml/v2 v2.4.2 // indirect
+ golang.org/x/mod v0.29.0 // indirect
+ golang.org/x/sync v0.18.0 // indirect
golang.org/x/sys v0.41.0 // indirect
+ golang.org/x/text v0.31.0 // indirect
+ golang.org/x/tools v0.38.0 // indirect
+ gopkg.in/yaml.v2 v2.4.0 // indirect
+ sigs.k8s.io/yaml v1.3.0 // indirect
)
+
+tool github.com/swaggo/swag/cmd/swag
diff --git a/go.sum b/go.sum
index 34b9608f5..fc3195913 100644
--- a/go.sum
+++ b/go.sum
@@ -3,8 +3,13 @@ filippo.io/edwards25519 v1.1.1 h1:YpjwWWlNmGIDyXOn8zLzqiD+9TyIlPhGFG96P39uBpw=
filippo.io/edwards25519 v1.1.1/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0=
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
+github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
+github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
+github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
+github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
+github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
@@ -13,6 +18,11 @@ github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG
github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M=
github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE=
github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk=
+github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY=
+github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
+github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dhui/dktest v0.4.6 h1:+DPKyScKSEp3VLtbMDHcUq6V5Lm5zfZZVb0Sk7Ahom4=
@@ -32,6 +42,19 @@ github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
+github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
+github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
+github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
+github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
+github.com/go-openapi/jsonreference v0.19.6/go.mod h1:diGHMEHg2IqXZGKxqyvWdfWU/aim5Dprw5bqpKkTvns=
+github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ=
+github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4=
+github.com/go-openapi/spec v0.20.4 h1:O8hJrt0UMnhHcluhIdUgCLRWyM2x7QkBXRvOs7m+O1M=
+github.com/go-openapi/spec v0.20.4/go.mod h1:faYFR1CvsJZ0mNsmsphTMSoRrNV3TEDoAM7FOEWeq8I=
+github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
+github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
+github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
+github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
@@ -44,12 +67,22 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o=
github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY=
+github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
+github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
+github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
+github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
+github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
+github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
+github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
+github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
+github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
@@ -60,12 +93,14 @@ github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
+github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug=
github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=
@@ -78,10 +113,21 @@ github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzM
github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
+github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
+github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
+github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
+github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
+github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
+github.com/swaggo/swag v1.16.6 h1:qBNcx53ZaX+M5dxVyTrgQ0PJ/ACK+NzhwcbieTt+9yI=
+github.com/swaggo/swag v1.16.6/go.mod h1:ngP2etMK5a0P3QBizic5MEwpRmluJZPHjXcMoj4Xesg=
+github.com/urfave/cli/v2 v2.3.0 h1:qph92Y649prgesehzOrQjdWyxFOp/QVM+6imKHad91M=
+github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus=
@@ -100,12 +146,37 @@ go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI=
go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU=
+golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA=
+golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w=
+golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM=
+golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
+golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
+golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
+golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
+golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
+golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ=
+golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs=
google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc=
google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
+gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
+gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo=
+sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8=
diff --git a/internal/server/audit_handlers.go b/internal/server/audit_handlers.go
index db884a966..e3ba7a450 100644
--- a/internal/server/audit_handlers.go
+++ b/internal/server/audit_handlers.go
@@ -45,8 +45,22 @@ func (s *Server) requireAuditStore(w http.ResponseWriter) (store.AuditTraceStore
return s.admin.AuditTraces, true
}
-// `q` matches trace_id hex prefix OR action substring. `total` is
-// pre-status-filter so the response may have fewer rows than `total` indicates.
+// @Summary List audit traces with filters
+// @Description One row per distinct trace_id. `q` matches trace_id hex prefix or root-operation substring.
+// @Tags Audit
+// @Security CustosUserHeader
+// @Produce json
+// @Param source query []string false "Restrict to listed sources (repeatable)" collectionFormat(multi)
+// @Param status query []string false "ok | error | in_progress (repeatable)" collectionFormat(multi)
+// @Param from query string false "RFC3339 lower bound on started_at"
+// @Param to query string false "RFC3339 upper bound on started_at"
+// @Param q query string false "Substring match on trace_id hex or root operation"
+// @Param limit query integer false "Page size (default 50, max 200)"
+// @Param offset query integer false "Page offset (default 0)"
+// @Success 200 {object} object{traces=[]models.TraceSummary,total=integer,limit=integer,offset=integer}
+// @Failure 400 {object} object{error=string} "Invalid filter value"
+// @Failure 503 {object} object{error=string} "Audit trace store not configured"
+// @Router /audit/traces [get]
func (s *Server) handleListTraces(w http.ResponseWriter, r *http.Request) {
ts, ok := s.requireAuditStore(w)
if !ok {
@@ -70,6 +84,16 @@ func (s *Server) handleListTraces(w http.ResponseWriter, r *http.Request) {
})
}
+// @Summary Get a trace's nested span tree
+// @Tags Audit
+// @Security CustosUserHeader
+// @Produce json
+// @Param trace_id path string true "32-char lowercase hex"
+// @Success 200 {object} object{trace_id=string,tree=[]models.TraceNode,truncated=boolean}
+// @Failure 400 {object} object{error=string} "Malformed trace_id"
+// @Failure 404 {object} object{error=string} "Trace not found"
+// @Failure 503 {object} object{error=string} "Audit trace store not configured"
+// @Router /audit/traces/{trace_id} [get]
func (s *Server) handleGetTrace(w http.ResponseWriter, r *http.Request) {
ts, ok := s.requireAuditStore(w)
if !ok {
@@ -98,6 +122,16 @@ func (s *Server) handleGetTrace(w http.ResponseWriter, r *http.Request) {
writeJSON(w, http.StatusOK, resp)
}
+// @Summary List audit events for a trace (optionally one span)
+// @Tags Audit
+// @Security CustosUserHeader
+// @Produce json
+// @Param trace_id query string true "32-char lowercase hex"
+// @Param span_id query string false "16-char lowercase hex"
+// @Success 200 {object} object{events=[]models.TraceEvent}
+// @Failure 400 {object} object{error=string} "Malformed trace_id or span_id"
+// @Failure 503 {object} object{error=string} "Audit trace store not configured"
+// @Router /audit/events [get]
func (s *Server) handleListEvents(w http.ResponseWriter, r *http.Request) {
ts, ok := s.requireAuditStore(w)
if !ok {
@@ -128,6 +162,13 @@ func (s *Server) handleListEvents(w http.ResponseWriter, r *http.Request) {
writeJSON(w, http.StatusOK, map[string]any{"events": events})
}
+// @Summary List distinct audit-event sources
+// @Tags Audit
+// @Security CustosUserHeader
+// @Produce json
+// @Success 200 {object} object{sources=[]string}
+// @Failure 503 {object} object{error=string} "Audit trace store not configured"
+// @Router /audit/sources [get]
func (s *Server) handleListSources(w http.ResponseWriter, r *http.Request) {
ts, ok := s.requireAuditStore(w)
if !ok {
diff --git a/internal/server/privilege.go b/internal/server/privilege.go
index b882e6932..e33b1acb1 100644
--- a/internal/server/privilege.go
+++ b/internal/server/privilege.go
@@ -24,8 +24,15 @@ import (
"github.com/apache/airavata-custos/pkg/models"
)
-// getCallerPrivileges handles GET /user/privileges. Returns the
-// authenticated caller's effective privilege set.
+// @Summary Get caller's effective privileges
+// @Description Effective set is direct grants UNION every privilege carried by every role the caller holds.
+// @Tags Caller
+// @Security CustosUserHeader
+// @Produce json
+// @Success 200 {object} object{privileges=[]models.PrivilegeKey} "Effective privilege set"
+// @Failure 401 {object} object{error=string} "Missing X-Custos-User-Id header"
+// @Failure 503 {object} object{error=string} "Auth lookup failed"
+// @Router /user/privileges [get]
func (s *Server) getCallerPrivileges(w http.ResponseWriter, r *http.Request) {
callerID := r.Header.Get(callerHeader)
if callerID == "" {
@@ -44,14 +51,28 @@ func (s *Server) getCallerPrivileges(w http.ResponseWriter, r *http.Request) {
writeJSON(w, http.StatusOK, map[string]any{"privileges": keys})
}
-// getPrivilegeCatalog handles GET /privileges/catalog. Returns the static
-// catalog of declared privilege keys. Gated on privileges:grant.
+// @Summary List the declared privilege catalog
+// @Tags Privileges
+// @Security CustosUserHeader
+// @Produce json
+// @Success 200 {array} models.PrivilegeKey
+// @Failure 401 {object} object{error=string} "Missing caller header"
+// @Failure 403 {object} object{error=string} "Caller lacks privileges:grant"
+// @Router /privileges/catalog [get]
func (s *Server) getPrivilegeCatalog(w http.ResponseWriter, _ *http.Request) {
writeJSON(w, http.StatusOK, s.svc.PrivilegeCatalog())
}
-// listUserPrivileges handles GET /users/{id}/privileges. Returns the active
-// privileges of the target user. Gated on privileges:grant.
+// @Summary List a user's direct privilege grants
+// @Description Returns DIRECT grants only (not role-derived). Combine with `GET /users/{id}/roles` for the full picture.
+// @Tags Privileges
+// @Security CustosUserHeader
+// @Produce json
+// @Param id path string true "User ID"
+// @Success 200 {array} models.UserPrivilege
+// @Failure 400 {object} object{error=string}
+// @Failure 403 {object} object{error=string} "Caller lacks privileges:grant"
+// @Router /users/{id}/privileges [get]
func (s *Server) listUserPrivileges(w http.ResponseWriter, r *http.Request) {
userID := r.PathValue("id")
if userID == "" {
@@ -66,8 +87,15 @@ func (s *Server) listUserPrivileges(w http.ResponseWriter, r *http.Request) {
writeJSON(w, http.StatusOK, rows)
}
-// listPrivilegeHolders handles GET /privileges/{key}/holders. Gated on
-// privileges:grant.
+// @Summary List direct holders of a privilege
+// @Description Role-derived holders are NOT listed here.
+// @Tags Privileges
+// @Security CustosUserHeader
+// @Produce json
+// @Param key path models.PrivilegeKey true "Privilege key"
+// @Success 200 {array} models.UserPrivilege
+// @Failure 400 {object} object{error=string} "Unknown privilege key"
+// @Router /privileges/{key}/holders [get]
func (s *Server) listPrivilegeHolders(w http.ResponseWriter, r *http.Request) {
key := models.PrivilegeKey(r.PathValue("key"))
if !models.IsKnownPrivilege(key) {
@@ -87,7 +115,19 @@ type grantPrivilegeRequest struct {
Reason string `json:"reason"`
}
-// grantPrivilege handles POST /users/{id}/privileges. Gated on privileges:grant.
+// @Summary Grant a direct privilege to a user
+// @Tags Privileges
+// @Security CustosUserHeader
+// @Accept json
+// @Produce json
+// @Param id path string true "User ID"
+// @Param request body grantPrivilegeRequest true "Grant payload"
+// @Success 201 {object} models.UserPrivilege
+// @Failure 400 {object} object{error=string} "Bad request"
+// @Failure 401 {object} object{error=string} "Missing caller header"
+// @Failure 403 {object} object{error=string} "Caller lacks privileges:grant"
+// @Failure 409 {object} object{error=string} "Privilege already active for user"
+// @Router /users/{id}/privileges [post]
func (s *Server) grantPrivilege(w http.ResponseWriter, r *http.Request) {
userID := r.PathValue("id")
if userID == "" {
@@ -117,8 +157,20 @@ type revokePrivilegeRequest struct {
Reason string `json:"reason"`
}
-// revokePrivilege handles DELETE /users/{id}/privileges/{key}. Gated on
-// privileges:grant.
+// @Summary Revoke a direct privilege from a user
+// @Description `privileges:grant` itself cannot be self-revoked or revoked from the last holder.
+// @Tags Privileges
+// @Security CustosUserHeader
+// @Accept json
+// @Param id path string true "User ID"
+// @Param key path models.PrivilegeKey true "Privilege key"
+// @Param request body revokePrivilegeRequest false "Optional reason"
+// @Success 204 "No Content"
+// @Failure 400 {object} object{error=string} "Self-revoke meta, last-holder, or unknown key"
+// @Failure 401 {object} object{error=string} "Missing caller header"
+// @Failure 403 {object} object{error=string} "Caller lacks privileges:grant"
+// @Failure 404 {object} object{error=string} "No active grant for that key"
+// @Router /users/{id}/privileges/{key} [delete]
func (s *Server) revokePrivilege(w http.ResponseWriter, r *http.Request) {
userID := r.PathValue("id")
key := models.PrivilegeKey(r.PathValue("key"))
diff --git a/internal/server/role.go b/internal/server/role.go
index 360763417..ee91ea7b7 100644
--- a/internal/server/role.go
+++ b/internal/server/role.go
@@ -24,6 +24,14 @@ import (
"github.com/apache/airavata-custos/pkg/models"
)
+// @Summary List all roles
+// @Tags Roles
+// @Security CustosUserHeader
+// @Produce json
+// @Success 200 {array} models.Role
+// @Failure 401 {object} object{error=string}
+// @Failure 403 {object} object{error=string} "Caller lacks roles:manage"
+// @Router /roles [get]
func (s *Server) listRoles(w http.ResponseWriter, r *http.Request) {
rows, err := s.svc.ListRoles(r.Context())
if err != nil {
@@ -33,7 +41,15 @@ func (s *Server) listRoles(w http.ResponseWriter, r *http.Request) {
writeJSON(w, http.StatusOK, rows)
}
-// getRole returns the role plus its privilege bundle in one response.
+// @Summary Get a role with its privilege bundle
+// @Tags Roles
+// @Security CustosUserHeader
+// @Produce json
+// @Param id path string true "Role ID"
+// @Success 200 {object} object{role=models.Role,privileges=[]models.PrivilegeKey}
+// @Failure 400 {object} object{error=string}
+// @Failure 404 {object} object{error=string}
+// @Router /roles/{id} [get]
func (s *Server) getRole(w http.ResponseWriter, r *http.Request) {
roleID := r.PathValue("id")
if roleID == "" {
@@ -57,10 +73,21 @@ func (s *Server) getRole(w http.ResponseWriter, r *http.Request) {
}
type createRoleRequest struct {
- Name string `json:"name"`
- Description string `json:"description"`
+ Name string `JSON:"name"`
+ Description string `JSON:"description"`
}
+// @Summary Create a role
+// @Tags Roles
+// @Security CustosUserHeader
+// @Accept json
+// @Produce json
+// @Param request body createRoleRequest true "Role payload"
+// @Success 201 {object} models.Role
+// @Failure 400 {object} object{error=string}
+// @Failure 401 {object} object{error=string}
+// @Failure 409 {object} object{error=string} "Role name collides"
+// @Router /roles [post]
func (s *Server) createRole(w http.ResponseWriter, r *http.Request) {
actorID := r.Header.Get(callerHeader)
if actorID == "" {
@@ -81,10 +108,22 @@ func (s *Server) createRole(w http.ResponseWriter, r *http.Request) {
}
type updateRoleRequest struct {
- Name string `json:"name"`
- Description string `json:"description"`
+ Name string `JSON:"name"`
+ Description string `JSON:"description"`
}
+// @Summary Update role name / description
+// @Description System roles cannot be renamed.
+// @Tags Roles
+// @Security CustosUserHeader
+// @Accept json
+// @Produce json
+// @Param id path string true "Role ID"
+// @Param request body updateRoleRequest true "Role patch"
+// @Success 200 {object} models.Role
+// @Failure 400 {object} object{error=string}
+// @Failure 401 {object} object{error=string}
+// @Router /roles/{id} [put]
func (s *Server) updateRole(w http.ResponseWriter, r *http.Request) {
roleID := r.PathValue("id")
if roleID == "" {
@@ -110,6 +149,15 @@ func (s *Server) updateRole(w http.ResponseWriter, r *http.Request) {
writeJSON(w, http.StatusOK, role)
}
+// @Summary Delete a role
+// @Description System roles cannot be deleted. CASCADE drops every assignment of this role.
+// @Tags Roles
+// @Security CustosUserHeader
+// @Param id path string true "Role ID"
+// @Success 204 "No Content"
+// @Failure 400 {object} object{error=string} "System role / unknown role"
+// @Failure 401 {object} object{error=string}
+// @Router /roles/{id} [delete]
func (s *Server) deleteRole(w http.ResponseWriter, r *http.Request) {
roleID := r.PathValue("id")
if roleID == "" {
@@ -130,9 +178,21 @@ func (s *Server) deleteRole(w http.ResponseWriter, r *http.Request) {
}
type rolePrivilegeRequest struct {
- Privilege models.PrivilegeKey `json:"privilege"`
+ Privilege models.PrivilegeKey `JSON:"privilege"`
}
+// @Summary Add a privilege to a role
+// @Description The new privilege propagates to every current holder.
+// @Tags Roles
+// @Security CustosUserHeader
+// @Accept json
+// @Param id path string true "Role ID"
+// @Param request body rolePrivilegeRequest true "Privilege key"
+// @Success 204 "No Content"
+// @Failure 400 {object} object{error=string} "Unknown key"
+// @Failure 401 {object} object{error=string}
+// @Failure 409 {object} object{error=string} "Role already carries that privilege"
+// @Router /roles/{id}/privileges [post]
func (s *Server) addRolePrivilege(w http.ResponseWriter, r *http.Request) {
roleID := r.PathValue("id")
if roleID == "" {
@@ -157,6 +217,17 @@ func (s *Server) addRolePrivilege(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusNoContent)
}
+// @Summary Remove a privilege from a role
+// @Description Removal propagates to every holder. Refuses to remove `privileges:grant` or `roles:manage` if that would leave no role anywhere carrying it.
+// @Tags Roles
+// @Security CustosUserHeader
+// @Param id path string true "Role ID"
+// @Param key path models.PrivilegeKey true "Privilege key"
+// @Success 204 "No Content"
+// @Failure 400 {object} object{error=string} "Last-source guard or unknown key"
+// @Failure 401 {object} object{error=string}
+// @Failure 404 {object} object{error=string} "Role does not carry that privilege"
+// @Router /roles/{id}/privileges/{key} [delete]
func (s *Server) removeRolePrivilege(w http.ResponseWriter, r *http.Request) {
roleID := r.PathValue("id")
key := models.PrivilegeKey(r.PathValue("key"))
@@ -177,6 +248,14 @@ func (s *Server) removeRolePrivilege(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusNoContent)
}
+// @Summary List roles a user holds
+// @Tags Role Assignments
+// @Security CustosUserHeader
+// @Produce json
+// @Param id path string true "User ID"
+// @Success 200 {array} models.UserRole
+// @Failure 400 {object} object{error=string}
+// @Router /users/{id}/roles [get]
func (s *Server) listUserRoles(w http.ResponseWriter, r *http.Request) {
userID := r.PathValue("id")
if userID == "" {
@@ -191,6 +270,14 @@ func (s *Server) listUserRoles(w http.ResponseWriter, r *http.Request) {
writeJSON(w, http.StatusOK, rows)
}
+// @Summary List users holding the role
+// @Tags Roles
+// @Security CustosUserHeader
+// @Produce json
+// @Param id path string true "Role ID"
+// @Success 200 {array} models.UserRole
+// @Failure 400 {object} object{error=string}
+// @Router /roles/{id}/holders [get]
func (s *Server) listRoleHolders(w http.ResponseWriter, r *http.Request) {
roleID := r.PathValue("id")
if roleID == "" {
@@ -206,10 +293,22 @@ func (s *Server) listRoleHolders(w http.ResponseWriter, r *http.Request) {
}
type grantRoleRequest struct {
- RoleID string `json:"role_id"`
- Reason string `json:"reason"`
+ RoleID string `JSON:"role_id"`
+ Reason string `JSON:"reason"`
}
+// @Summary Grant a role to a user
+// @Tags Role Assignments
+// @Security CustosUserHeader
+// @Accept json
+// @Produce json
+// @Param id path string true "User ID"
+// @Param request body grantRoleRequest true "Role assignment payload"
+// @Success 201 {object} models.UserRole
+// @Failure 400 {object} object{error=string}
+// @Failure 401 {object} object{error=string}
+// @Failure 409 {object} object{error=string} "User already holds that role"
+// @Router /users/{id}/roles [post]
func (s *Server) grantRoleToUser(w http.ResponseWriter, r *http.Request) {
userID := r.PathValue("id")
if userID == "" {
@@ -236,9 +335,22 @@ func (s *Server) grantRoleToUser(w http.ResponseWriter, r *http.Request) {
}
type revokeRoleRequest struct {
- Reason string `json:"reason"`
+ Reason string `JSON:"reason"`
}
+// @Summary Revoke a role from a user
+// @Description Refuses if revoking would leave no holder of `privileges:grant` or `roles:manage` anywhere (last-meta-holder guard).
+// @Tags Role Assignments
+// @Security CustosUserHeader
+// @Accept json
+// @Param id path string true "User ID"
+// @Param roleId path string true "Role ID"
+// @Param request body revokeRoleRequest false "Optional reason"
+// @Success 204 "No Content"
+// @Failure 400 {object} object{error=string} "Last-meta-holder guard"
+// @Failure 401 {object} object{error=string}
+// @Failure 404 {object} object{error=string} "User does not hold that role"
+// @Router /users/{id}/roles/{roleId} [delete]
func (s *Server) revokeRoleFromUser(w http.ResponseWriter, r *http.Request) {
userID := r.PathValue("id")
roleID := r.PathValue("roleId")
diff --git a/internal/server/server.go b/internal/server/server.go
index 2dace2e82..9dca02b27 100644
--- a/internal/server/server.go
+++ b/internal/server/server.go
@@ -191,6 +191,15 @@ func (s *Server) healthz(w http.ResponseWriter, _ *http.Request) {
writeJSON(w, http.StatusOK, map[string]string{"status": "ok"})
}
+// @Summary Create an organization
+// @Tags Organizations
+// @Security CustosUserHeader
+// @Accept json
+// @Produce json
+// @Param request body models.Organization true "Organization payload"
+// @Success 201 {object} models.Organization
+// @Failure 400 {object} object{error=string}
+// @Router /organizations [post]
func (s *Server) createOrganization(w http.ResponseWriter, r *http.Request) {
var org models.Organization
if err := decodeJSON(r, &org); err != nil {
@@ -205,6 +214,14 @@ func (s *Server) createOrganization(w http.ResponseWriter, r *http.Request) {
writeJSON(w, http.StatusCreated, created)
}
+// @Summary Get an organization by ID
+// @Tags Organizations
+// @Security CustosUserHeader
+// @Produce json
+// @Param id path string true "Organization ID"
+// @Success 200 {object} models.Organization
+// @Failure 404 {object} object{error=string}
+// @Router /organizations/{id} [get]
func (s *Server) getOrganization(w http.ResponseWriter, r *http.Request) {
org, err := s.svc.GetOrganization(r.Context(), r.PathValue("id"))
if err != nil {
@@ -214,6 +231,15 @@ func (s *Server) getOrganization(w http.ResponseWriter, r *http.Request) {
writeJSON(w, http.StatusOK, org)
}
+// @Summary Create a user
+// @Tags Users
+// @Security CustosUserHeader
+// @Accept json
+// @Produce json
+// @Param request body models.User true "User payload"
+// @Success 201 {object} models.User
+// @Failure 400 {object} object{error=string}
+// @Router /users [post]
func (s *Server) createUser(w http.ResponseWriter, r *http.Request) {
var u models.User
if err := decodeJSON(r, &u); err != nil {
@@ -228,6 +254,14 @@ func (s *Server) createUser(w http.ResponseWriter, r *http.Request) {
writeJSON(w, http.StatusCreated, created)
}
+// @Summary Get a user by ID
+// @Tags Users
+// @Security CustosUserHeader
+// @Produce json
+// @Param id path string true "User ID"
+// @Success 200 {object} models.User
+// @Failure 404 {object} object{error=string}
+// @Router /users/{id} [get]
func (s *Server) getUser(w http.ResponseWriter, r *http.Request) {
u, err := s.svc.GetUser(r.Context(), r.PathValue("id"))
if err != nil {
@@ -237,6 +271,15 @@ func (s *Server) getUser(w http.ResponseWriter, r *http.Request) {
writeJSON(w, http.StatusOK, u)
}
+// @Summary Create a project
+// @Tags Projects
+// @Security CustosUserHeader
+// @Accept json
+// @Produce json
+// @Param request body models.Project true "Project payload"
+// @Success 201 {object} models.Project
+// @Failure 400 {object} object{error=string}
+// @Router /projects [post]
func (s *Server) createProject(w http.ResponseWriter, r *http.Request) {
var p models.Project
if err := decodeJSON(r, &p); err != nil {
@@ -251,6 +294,14 @@ func (s *Server) createProject(w http.ResponseWriter, r *http.Request) {
writeJSON(w, http.StatusCreated, created)
}
+// @Summary Get a project by ID
+// @Tags Projects
+// @Security CustosUserHeader
+// @Produce json
+// @Param id path string true "Project ID"
+// @Success 200 {object} models.Project
+// @Failure 404 {object} object{error=string}
+// @Router /projects/{id} [get]
func (s *Server) getProject(w http.ResponseWriter, r *http.Request) {
p, err := s.svc.GetProject(r.Context(), r.PathValue("id"))
if err != nil {
@@ -260,6 +311,15 @@ func (s *Server) getProject(w http.ResponseWriter, r *http.Request) {
writeJSON(w, http.StatusOK, p)
}
+// @Summary Create a compute cluster
+// @Tags Compute Clusters
+// @Security CustosUserHeader
+// @Accept json
+// @Produce json
+// @Param request body models.ComputeCluster true "Cluster payload"
+// @Success 201 {object} models.ComputeCluster
+// @Failure 400 {object} object{error=string}
+// @Router /compute-clusters [post]
func (s *Server) createComputeCluster(w http.ResponseWriter, r *http.Request) {
var c models.ComputeCluster
if err := decodeJSON(r, &c); err != nil {
@@ -274,6 +334,14 @@ func (s *Server) createComputeCluster(w http.ResponseWriter, r *http.Request) {
writeJSON(w, http.StatusCreated, created)
}
+// @Summary Get a compute cluster by ID
+// @Tags Compute Clusters
+// @Security CustosUserHeader
+// @Produce json
+// @Param id path string true "Compute cluster ID"
+// @Success 200 {object} models.ComputeCluster
+// @Failure 404 {object} object{error=string}
+// @Router /compute-clusters/{id} [get]
func (s *Server) getComputeCluster(w http.ResponseWriter, r *http.Request) {
c, err := s.svc.GetComputeCluster(r.Context(), r.PathValue("id"))
if err != nil {
@@ -283,6 +351,13 @@ func (s *Server) getComputeCluster(w http.ResponseWriter, r *http.Request) {
writeJSON(w, http.StatusOK, c)
}
+// @Summary List compute clusters
+// @Tags Compute Clusters
+// @Security CustosUserHeader
+// @Produce json
+// @Success 200 {array} models.ComputeCluster
+// @Failure 500 {object} object{error=string}
+// @Router /compute-clusters [get]
func (s *Server) listComputeClusters(w http.ResponseWriter, r *http.Request) {
clusters, err := s.svc.ListComputeClusters(r.Context())
if err != nil {
@@ -292,6 +367,15 @@ func (s *Server) listComputeClusters(w http.ResponseWriter, r *http.Request) {
writeJSON(w, http.StatusOK, clusters)
}
+// @Summary Create a compute cluster user
+// @Tags Compute Cluster Users
+// @Security CustosUserHeader
+// @Accept json
+// @Produce json
+// @Param request body models.ComputeClusterUser true "Cluster user payload"
+// @Success 201 {object} models.ComputeClusterUser
+// @Failure 400 {object} object{error=string}
+// @Router /compute-cluster-users [post]
func (s *Server) createComputeClusterUser(w http.ResponseWriter, r *http.Request) {
var cu models.ComputeClusterUser
if err := decodeJSON(r, &cu); err != nil {
@@ -306,6 +390,14 @@ func (s *Server) createComputeClusterUser(w http.ResponseWriter, r *http.Request
writeJSON(w, http.StatusCreated, created)
}
+// @Summary Get a compute cluster user by ID
+// @Tags Compute Cluster Users
+// @Security CustosUserHeader
+// @Produce json
+// @Param id path string true "Compute cluster user ID"
+// @Success 200 {object} models.ComputeClusterUser
+// @Failure 404 {object} object{error=string}
+// @Router /compute-cluster-users/{id} [get]
func (s *Server) getComputeClusterUser(w http.ResponseWriter, r *http.Request) {
cu, err := s.svc.GetComputeClusterUser(r.Context(), r.PathValue("id"))
if err != nil {
@@ -315,6 +407,17 @@ func (s *Server) getComputeClusterUser(w http.ResponseWriter, r *http.Request) {
writeJSON(w, http.StatusOK, cu)
}
+// @Summary Update a compute cluster user
+// @Tags Compute Cluster Users
+// @Security CustosUserHeader
+// @Accept json
+// @Produce json
+// @Param id path string true "Compute cluster user ID"
+// @Param request body models.ComputeClusterUser true "Cluster user payload"
+// @Success 200 {object} models.ComputeClusterUser
+// @Failure 400 {object} object{error=string}
+// @Failure 404 {object} object{error=string}
+// @Router /compute-cluster-users/{id} [put]
func (s *Server) updateComputeClusterUser(w http.ResponseWriter, r *http.Request) {
var cu models.ComputeClusterUser
if err := decodeJSON(r, &cu); err != nil {
@@ -329,6 +432,13 @@ func (s *Server) updateComputeClusterUser(w http.ResponseWriter, r *http.Request
writeJSON(w, http.StatusOK, &cu)
}
+// @Summary Delete a compute cluster user
+// @Tags Compute Cluster Users
+// @Security CustosUserHeader
+// @Param id path string true "Compute cluster user ID"
+// @Success 204 "No Content"
+// @Failure 404 {object} object{error=string}
+// @Router /compute-cluster-users/{id} [delete]
func (s *Server) deleteComputeClusterUser(w http.ResponseWriter, r *http.Request) {
if err := s.svc.DeleteComputeClusterUser(r.Context(), r.PathValue("id")); err != nil {
writeServiceError(w, err)
@@ -337,6 +447,14 @@ func (s *Server) deleteComputeClusterUser(w http.ResponseWriter, r *http.Request
w.WriteHeader(http.StatusNoContent)
}
+// @Summary List users on a compute cluster
+// @Tags Compute Cluster Users
+// @Security CustosUserHeader
+// @Produce json
+// @Param id path string true "Compute cluster ID"
+// @Success 200 {array} models.ComputeClusterUser
+// @Failure 404 {object} object{error=string}
+// @Router /compute-clusters/{id}/users [get]
func (s *Server) listComputeClusterUsersByCluster(w http.ResponseWriter, r *http.Request) {
users, err := s.svc.ListComputeClusterUsersByCluster(r.Context(), r.PathValue("id"))
if err != nil {
@@ -346,6 +464,15 @@ func (s *Server) listComputeClusterUsersByCluster(w http.ResponseWriter, r *http
writeJSON(w, http.StatusOK, users)
}
+// @Summary Get a compute cluster user by (cluster, user) pair
+// @Tags Compute Cluster Users
+// @Security CustosUserHeader
+// @Produce json
+// @Param id path string true "Compute cluster ID"
+// @Param userId path string true "User ID"
+// @Success 200 {object} models.ComputeClusterUser
+// @Failure 404 {object} object{error=string}
+// @Router /compute-clusters/{id}/users/{userId} [get]
func (s *Server) getComputeClusterUserByPair(w http.ResponseWriter, r *http.Request) {
cu, err := s.svc.GetComputeClusterUserByPair(r.Context(), r.PathValue("id"), r.PathValue("userId"))
if err != nil {
@@ -355,6 +482,14 @@ func (s *Server) getComputeClusterUserByPair(w http.ResponseWriter, r *http.Requ
writeJSON(w, http.StatusOK, cu)
}
+// @Summary List compute cluster users for a user
+// @Tags Compute Cluster Users
+// @Security CustosUserHeader
+// @Produce json
+// @Param id path string true "User ID"
+// @Success 200 {array} models.ComputeClusterUser
+// @Failure 404 {object} object{error=string}
+// @Router /users/{id}/compute-cluster-users [get]
func (s *Server) listComputeClusterUsersByUser(w http.ResponseWriter, r *http.Request) {
users, err := s.svc.ListComputeClusterUsersByUser(r.Context(), r.PathValue("id"))
if err != nil {
@@ -364,6 +499,15 @@ func (s *Server) listComputeClusterUsersByUser(w http.ResponseWriter, r *http.Re
writeJSON(w, http.StatusOK, users)
}
+// @Summary Create a compute allocation
+// @Tags Compute Allocations
+// @Security CustosUserHeader
+// @Accept json
+// @Produce json
+// @Param request body models.ComputeAllocation true "Compute allocation payload"
+// @Success 201 {object} models.ComputeAllocation
+// @Failure 400 {object} object{error=string}
+// @Router /compute-allocations [post]
func (s *Server) createComputeAllocation(w http.ResponseWriter, r *http.Request) {
var a models.ComputeAllocation
if err := decodeJSON(r, &a); err != nil {
@@ -378,6 +522,14 @@ func (s *Server) createComputeAllocation(w http.ResponseWriter, r *http.Request)
writeJSON(w, http.StatusCreated, created)
}
+// @Summary Get a compute allocation by ID
+// @Tags Compute Allocations
+// @Security CustosUserHeader
+// @Produce json
+// @Param id path string true "Compute allocation ID"
+// @Success 200 {object} models.ComputeAllocation
+// @Failure 404 {object} object{error=string}
+// @Router /compute-allocations/{id} [get]
func (s *Server) getComputeAllocation(w http.ResponseWriter, r *http.Request) {
a, err := s.svc.GetComputeAllocation(r.Context(), r.PathValue("id"))
if err != nil {
@@ -387,6 +539,17 @@ func (s *Server) getComputeAllocation(w http.ResponseWriter, r *http.Request) {
writeJSON(w, http.StatusOK, a)
}
+// @Summary Create a compute allocation resource
+// @Description Defines a resource (partition) — e.g. a CPU or GPU partition that allocations can be attached to.
+// @Tags Compute Allocation Resources
+// @Security CustosUserHeader
+// @Accept json
+// @Produce json
+// @Param request body models.ComputeAllocationResource true "Resource payload"
+// @Success 201 {object} models.ComputeAllocationResource
+// @Failure 400 {object} object{error=string}
+// @Failure 401 {object} object{error=string}
+// @Router /compute-allocation-resources [post]
func (s *Server) createComputeAllocationResource(w http.ResponseWriter, r *http.Request) {
var res models.ComputeAllocationResource
if err := decodeJSON(r, &res); err != nil {
@@ -401,6 +564,14 @@ func (s *Server) createComputeAllocationResource(w http.ResponseWriter, r *http.
writeJSON(w, http.StatusCreated, created)
}
+// @Summary Get a compute allocation resource
+// @Tags Compute Allocation Resources
+// @Security CustosUserHeader
+// @Produce json
+// @Param id path string true "Resource ID"
+// @Success 200 {object} models.ComputeAllocationResource
+// @Failure 404 {object} object{error=string}
+// @Router /compute-allocation-resources/{id} [get]
func (s *Server) getComputeAllocationResource(w http.ResponseWriter, r *http.Request) {
res, err := s.svc.GetComputeAllocationResource(r.Context(), r.PathValue("id"))
if err != nil {
@@ -410,6 +581,13 @@ func (s *Server) getComputeAllocationResource(w http.ResponseWriter, r *http.Req
writeJSON(w, http.StatusOK, res)
}
+// @Summary List all compute allocation resources
+// @Tags Compute Allocation Resources
+// @Security CustosUserHeader
+// @Produce json
+// @Success 200 {array} models.ComputeAllocationResource
+// @Failure 401 {object} object{error=string}
+// @Router /compute-allocation-resources [get]
func (s *Server) listComputeAllocationResources(w http.ResponseWriter, r *http.Request) {
resources, err := s.svc.ListComputeAllocationResources(r.Context())
if err != nil {
@@ -425,6 +603,18 @@ type attachResourceRequest struct {
ResourceTime int64 `json:"resource_time"`
}
+// @Summary Attach a resource to a compute allocation
+// @Description Creates a mapping between a compute allocation and a resource (partition) with a specific amount and wall-clock time grant.
+// @Tags Compute Allocation Resources
+// @Security CustosUserHeader
+// @Accept json
+// @Produce json
+// @Param id path string true "Compute allocation ID"
+// @Param request body attachResourceRequest true "Attach payload"
+// @Success 201 {object} models.ComputeAllocationResourceMapping
+// @Failure 400 {object} object{error=string}
+// @Failure 404 {object} object{error=string}
+// @Router /compute-allocations/{id}/resources [post]
func (s *Server) attachResourceToAllocation(w http.ResponseWriter, r *http.Request) {
var body attachResourceRequest
if err := decodeJSON(r, &body); err != nil {
@@ -444,6 +634,18 @@ type updateAllocationResourceMappingRequest struct {
ResourceTime int64 `json:"resource_time"`
}
+// @Summary Update a compute allocation -> resource mapping
+// @Tags Compute Allocation Resources
+// @Security CustosUserHeader
+// @Accept json
+// @Produce json
+// @Param id path string true "Compute allocation ID"
+// @Param resourceId path string true "Compute allocation resource ID"
+// @Param request body updateAllocationResourceMappingRequest true "Mapping update payload"
+// @Success 200 {object} models.ComputeAllocationResourceMapping
+// @Failure 400 {object} object{error=string}
+// @Failure 404 {object} object{error=string}
+// @Router /compute-allocations/{id}/resources/{resourceId} [put]
func (s *Server) updateAllocationResourceMapping(w http.ResponseWriter, r *http.Request) {
var body updateAllocationResourceMappingRequest
if err := decodeJSON(r, &body); err != nil {
@@ -458,6 +660,14 @@ func (s *Server) updateAllocationResourceMapping(w http.ResponseWriter, r *http.
writeJSON(w, http.StatusOK, mapping)
}
+// @Summary Detach a resource from a compute allocation
+// @Tags Compute Allocation Resources
+// @Security CustosUserHeader
+// @Param id path string true "Compute allocation ID"
+// @Param resourceId path string true "Compute allocation resource ID"
+// @Success 204 "No Content"
+// @Failure 404 {object} object{error=string}
+// @Router /compute-allocations/{id}/resources/{resourceId} [delete]
func (s *Server) detachResourceFromAllocation(w http.ResponseWriter, r *http.Request) {
if err := s.svc.DetachResourceFromAllocation(r.Context(), r.PathValue("id"), r.PathValue("resourceId")); err != nil {
writeServiceError(w, err)
@@ -466,6 +676,14 @@ func (s *Server) detachResourceFromAllocation(w http.ResponseWriter, r *http.Req
w.WriteHeader(http.StatusNoContent)
}
+// @Summary List resources attached to a compute allocation
+// @Tags Compute Allocation Resources
+// @Security CustosUserHeader
+// @Produce json
+// @Param id path string true "Compute allocation ID"
+// @Success 200 {array} models.ComputeAllocationResourceMapping
+// @Failure 404 {object} object{error=string}
+// @Router /compute-allocations/{id}/resources [get]
func (s *Server) listResourcesForAllocation(w http.ResponseWriter, r *http.Request) {
resources, err := s.svc.ListResourcesForAllocation(r.Context(), r.PathValue("id"))
if err != nil {
@@ -475,6 +693,14 @@ func (s *Server) listResourcesForAllocation(w http.ResponseWriter, r *http.Reque
writeJSON(w, http.StatusOK, resources)
}
+// @Summary List compute allocations attached to a resource
+// @Tags Compute Allocation Resources
+// @Security CustosUserHeader
+// @Produce json
+// @Param id path string true "Compute allocation resource ID"
+// @Success 200 {array} models.ComputeAllocationResourceMapping
+// @Failure 404 {object} object{error=string}
+// @Router /compute-allocation-resources/{id}/allocations [get]
func (s *Server) listAllocationsForResource(w http.ResponseWriter, r *http.Request) {
allocs, err := s.svc.ListAllocationsForResource(r.Context(), r.PathValue("id"))
if err != nil {
@@ -484,6 +710,17 @@ func (s *Server) listAllocationsForResource(w http.ResponseWriter, r *http.Reque
writeJSON(w, http.StatusOK, allocs)
}
+// @Summary Create a compute allocation resource rate
+// @Description Records the SU rate (e.g. SU per CPU-hour) for a resource over a time window.
+// @Tags Compute Allocation Resource Rates
+// @Security CustosUserHeader
+// @Accept json
+// @Produce json
+// @Param request body models.ComputeAllocationResourceRate true "Rate payload"
+// @Success 201 {object} models.ComputeAllocationResourceRate
+// @Failure 400 {object} object{error=string}
+// @Failure 404 {object} object{error=string}
+// @Router /compute-allocation-resource-rates [post]
func (s *Server) createComputeAllocationResourceRate(w http.ResponseWriter, r *http.Request) {
var rate models.ComputeAllocationResourceRate
if err := decodeJSON(r, &rate); err != nil {
@@ -498,6 +735,14 @@ func (s *Server) createComputeAllocationResourceRate(w http.ResponseWriter, r *h
writeJSON(w, http.StatusCreated, created)
}
+// @Summary Get a compute allocation resource rate
+// @Tags Compute Allocation Resource Rates
+// @Security CustosUserHeader
+// @Produce json
+// @Param id path string true "Rate ID"
+// @Success 200 {object} models.ComputeAllocationResourceRate
+// @Failure 404 {object} object{error=string}
+// @Router /compute-allocation-resource-rates/{id} [get]
func (s *Server) getComputeAllocationResourceRate(w http.ResponseWriter, r *http.Request) {
rate, err := s.svc.GetComputeAllocationResourceRate(r.Context(), r.PathValue("id"))
if err != nil {
@@ -507,6 +752,14 @@ func (s *Server) getComputeAllocationResourceRate(w http.ResponseWriter, r *http
writeJSON(w, http.StatusOK, rate)
}
+// @Summary List rate history for a resource
+// @Tags Compute Allocation Resource Rates
+// @Security CustosUserHeader
+// @Produce json
+// @Param id path string true "Compute allocation resource ID"
+// @Success 200 {array} models.ComputeAllocationResourceRate
+// @Failure 404 {object} object{error=string}
+// @Router /compute-allocation-resources/{id}/rates [get]
func (s *Server) listRatesForResource(w http.ResponseWriter, r *http.Request) {
rates, err := s.svc.ListRatesForResource(r.Context(), r.PathValue("id"))
if err != nil {
@@ -516,6 +769,17 @@ func (s *Server) listRatesForResource(w http.ResponseWriter, r *http.Request) {
writeJSON(w, http.StatusOK, rates)
}
+// @Summary Get the effective rate for a resource at a given time
+// @Description Returns the rate whose `[start_time, end_time)` window contains the `at` timestamp. Defaults to now when `at` is omitted.
+// @Tags Compute Allocation Resource Rates
+// @Security CustosUserHeader
+// @Produce json
+// @Param id path string true "Compute allocation resource ID"
+// @Param at query string false "RFC3339 time; defaults to now"
+// @Success 200 {object} models.ComputeAllocationResourceRate
+// @Failure 400 {object} object{error=string} "Invalid 'at' query parameter"
+// @Failure 404 {object} object{error=string}
+// @Router /compute-allocation-resources/{id}/rates/effective [get]
func (s *Server) getEffectiveRateForResource(w http.ResponseWriter, r *http.Request) {
var at time.Time
if raw := r.URL.Query().Get("at"); raw != "" {
@@ -534,6 +798,17 @@ func (s *Server) getEffectiveRateForResource(w http.ResponseWriter, r *http.Requ
writeJSON(w, http.StatusOK, rate)
}
+// @Summary Create a compute allocation diff
+// @Description Records a discrete change against a compute allocation (e.g. USAGE_UPDATE or ALLOCATION_STATUS_CHANGE).
+// @Tags Compute Allocation Diffs
+// @Security CustosUserHeader
+// @Accept json
+// @Produce json
+// @Param request body models.ComputeAllocationDiff true "Diff payload"
+// @Success 201 {object} models.ComputeAllocationDiff
+// @Failure 400 {object} object{error=string}
+// @Failure 404 {object} object{error=string}
+// @Router /compute-allocation-diffs [post]
func (s *Server) createComputeAllocationDiff(w http.ResponseWriter, r *http.Request) {
var diff models.ComputeAllocationDiff
if err := decodeJSON(r, &diff); err != nil {
@@ -548,6 +823,14 @@ func (s *Server) createComputeAllocationDiff(w http.ResponseWriter, r *http.Requ
writeJSON(w, http.StatusCreated, created)
}
+// @Summary Get a compute allocation diff
+// @Tags Compute Allocation Diffs
+// @Security CustosUserHeader
+// @Produce json
+// @Param id path string true "Diff ID"
+// @Success 200 {object} models.ComputeAllocationDiff
+// @Failure 404 {object} object{error=string}
+// @Router /compute-allocation-diffs/{id} [get]
func (s *Server) getComputeAllocationDiff(w http.ResponseWriter, r *http.Request) {
diff, err := s.svc.GetComputeAllocationDiff(r.Context(), r.PathValue("id"))
if err != nil {
@@ -557,6 +840,13 @@ func (s *Server) getComputeAllocationDiff(w http.ResponseWriter, r *http.Request
writeJSON(w, http.StatusOK, diff)
}
+// @Summary Delete a compute allocation diff
+// @Tags Compute Allocation Diffs
+// @Security CustosUserHeader
+// @Param id path string true "Diff ID"
+// @Success 204 "No Content"
+// @Failure 404 {object} object{error=string}
+// @Router /compute-allocation-diffs/{id} [delete]
func (s *Server) deleteComputeAllocationDiff(w http.ResponseWriter, r *http.Request) {
if err := s.svc.DeleteComputeAllocationDiff(r.Context(), r.PathValue("id")); err != nil {
writeServiceError(w, err)
@@ -565,6 +855,14 @@ func (s *Server) deleteComputeAllocationDiff(w http.ResponseWriter, r *http.Requ
w.WriteHeader(http.StatusNoContent)
}
+// @Summary List diffs for a compute allocation
+// @Tags Compute Allocation Diffs
+// @Security CustosUserHeader
+// @Produce json
+// @Param id path string true "Compute allocation ID"
+// @Success 200 {array} models.ComputeAllocationDiff
+// @Failure 404 {object} object{error=string}
+// @Router /compute-allocations/{id}/diffs [get]
func (s *Server) listDiffsForAllocation(w http.ResponseWriter, r *http.Request) {
diffs, err := s.svc.ListDiffsForAllocation(r.Context(), r.PathValue("id"))
if err != nil {
@@ -574,6 +872,14 @@ func (s *Server) listDiffsForAllocation(w http.ResponseWriter, r *http.Request)
writeJSON(w, http.StatusOK, diffs)
}
+// @Summary Get the most recent diff for a compute allocation
+// @Tags Compute Allocation Diffs
+// @Security CustosUserHeader
+// @Produce json
+// @Param id path string true "Compute allocation ID"
+// @Success 200 {object} models.ComputeAllocationDiff
+// @Failure 404 {object} object{error=string}
+// @Router /compute-allocations/{id}/diffs/latest [get]
func (s *Server) getLatestDiffForAllocation(w http.ResponseWriter, r *http.Request) {
diff, err := s.svc.GetLatestDiffForAllocation(r.Context(), r.PathValue("id"))
if err != nil {
@@ -583,6 +889,15 @@ func (s *Server) getLatestDiffForAllocation(w http.ResponseWriter, r *http.Reque
writeJSON(w, http.StatusOK, diff)
}
+// @Summary Create a compute allocation change request
+// @Tags Compute Allocation Change Requests
+// @Security CustosUserHeader
+// @Accept json
+// @Produce json
+// @Param request body models.ComputeAllocationChangeRequest true "Change request payload"
+// @Success 201 {object} models.ComputeAllocationChangeRequest
+// @Failure 400 {object} object{error=string}
+// @Router /compute-allocation-change-requests [post]
func (s *Server) createComputeAllocationChangeRequest(w http.ResponseWriter, r *http.Request) {
var req models.ComputeAllocationChangeRequest
if err := decodeJSON(r, &req); err != nil {
@@ -597,6 +912,14 @@ func (s *Server) createComputeAllocationChangeRequest(w http.ResponseWriter, r *
writeJSON(w, http.StatusCreated, created)
}
+// @Summary Get a compute allocation change request
+// @Tags Compute Allocation Change Requests
+// @Security CustosUserHeader
+// @Produce json
+// @Param id path string true "Change request ID"
+// @Success 200 {object} models.ComputeAllocationChangeRequest
+// @Failure 404 {object} object{error=string}
+// @Router /compute-allocation-change-requests/{id} [get]
func (s *Server) getComputeAllocationChangeRequest(w http.ResponseWriter, r *http.Request) {
req, err := s.svc.GetComputeAllocationChangeRequest(r.Context(), r.PathValue("id"))
if err != nil {
@@ -606,6 +929,17 @@ func (s *Server) getComputeAllocationChangeRequest(w http.ResponseWriter, r *htt
writeJSON(w, http.StatusOK, req)
}
+// @Summary Update a compute allocation change request
+// @Tags Compute Allocation Change Requests
+// @Security CustosUserHeader
+// @Accept json
+// @Produce json
+// @Param id path string true "Change request ID"
+// @Param request body models.ComputeAllocationChangeRequest true "Change request payload"
+// @Success 200 {object} models.ComputeAllocationChangeRequest
+// @Failure 400 {object} object{error=string}
+// @Failure 404 {object} object{error=string}
+// @Router /compute-allocation-change-requests/{id} [put]
func (s *Server) updateComputeAllocationChangeRequest(w http.ResponseWriter, r *http.Request) {
var req models.ComputeAllocationChangeRequest
if err := decodeJSON(r, &req); err != nil {
@@ -621,6 +955,13 @@ func (s *Server) updateComputeAllocationChangeRequest(w http.ResponseWriter, r *
writeJSON(w, http.StatusOK, updated)
}
+// @Summary Delete a compute allocation change request
+// @Tags Compute Allocation Change Requests
+// @Security CustosUserHeader
+// @Param id path string true "Change request ID"
+// @Success 204 "No Content"
+// @Failure 404 {object} object{error=string}
+// @Router /compute-allocation-change-requests/{id} [delete]
func (s *Server) deleteComputeAllocationChangeRequest(w http.ResponseWriter, r *http.Request) {
if err := s.svc.DeleteComputeAllocationChangeRequest(r.Context(), r.PathValue("id")); err != nil {
writeServiceError(w, err)
@@ -629,6 +970,14 @@ func (s *Server) deleteComputeAllocationChangeRequest(w http.ResponseWriter, r *
w.WriteHeader(http.StatusNoContent)
}
+// @Summary List change requests for a compute allocation
+// @Tags Compute Allocation Change Requests
+// @Security CustosUserHeader
+// @Produce json
+// @Param id path string true "Compute allocation ID"
+// @Success 200 {array} models.ComputeAllocationChangeRequest
+// @Failure 404 {object} object{error=string}
+// @Router /compute-allocations/{id}/change-requests [get]
func (s *Server) listChangeRequestsForAllocation(w http.ResponseWriter, r *http.Request) {
rows, err := s.svc.ListChangeRequestsForAllocation(r.Context(), r.PathValue("id"))
if err != nil {
@@ -638,6 +987,14 @@ func (s *Server) listChangeRequestsForAllocation(w http.ResponseWriter, r *http.
writeJSON(w, http.StatusOK, rows)
}
+// @Summary List change requests submitted by a user
+// @Tags Compute Allocation Change Requests
+// @Security CustosUserHeader
+// @Produce json
+// @Param id path string true "User ID"
+// @Success 200 {array} models.ComputeAllocationChangeRequest
+// @Failure 404 {object} object{error=string}
+// @Router /users/{id}/change-requests [get]
func (s *Server) listChangeRequestsByRequester(w http.ResponseWriter, r *http.Request) {
rows, err := s.svc.ListChangeRequestsByRequester(r.Context(), r.PathValue("id"))
if err != nil {
@@ -647,6 +1004,15 @@ func (s *Server) listChangeRequestsByRequester(w http.ResponseWriter, r *http.Re
writeJSON(w, http.StatusOK, rows)
}
+// @Summary Create a change request event
+// @Tags Change Request Events
+// @Security CustosUserHeader
+// @Accept json
+// @Produce json
+// @Param request body models.ComputeAllocationChangeRequestEvent true "Event payload"
+// @Success 201 {object} models.ComputeAllocationChangeRequestEvent
+// @Failure 400 {object} object{error=string}
+// @Router /compute-allocation-change-request-events [post]
func (s *Server) createComputeAllocationChangeRequestEvent(w http.ResponseWriter, r *http.Request) {
var evt models.ComputeAllocationChangeRequestEvent
if err := decodeJSON(r, &evt); err != nil {
@@ -661,6 +1027,14 @@ func (s *Server) createComputeAllocationChangeRequestEvent(w http.ResponseWriter
writeJSON(w, http.StatusCreated, created)
}
+// @Summary Get a change request event
+// @Tags Change Request Events
+// @Security CustosUserHeader
+// @Produce json
+// @Param id path string true "Event ID"
+// @Success 200 {object} models.ComputeAllocationChangeRequestEvent
+// @Failure 404 {object} object{error=string}
+// @Router /compute-allocation-change-request-events/{id} [get]
func (s *Server) getComputeAllocationChangeRequestEvent(w http.ResponseWriter, r *http.Request) {
evt, err := s.svc.GetComputeAllocationChangeRequestEvent(r.Context(), r.PathValue("id"))
if err != nil {
@@ -670,6 +1044,13 @@ func (s *Server) getComputeAllocationChangeRequestEvent(w http.ResponseWriter, r
writeJSON(w, http.StatusOK, evt)
}
+// @Summary Delete a change request event
+// @Tags Change Request Events
+// @Security CustosUserHeader
+// @Param id path string true "Event ID"
+// @Success 204 "No Content"
+// @Failure 404 {object} object{error=string}
+// @Router /compute-allocation-change-request-events/{id} [delete]
func (s *Server) deleteComputeAllocationChangeRequestEvent(w http.ResponseWriter, r *http.Request) {
if err := s.svc.DeleteComputeAllocationChangeRequestEvent(r.Context(), r.PathValue("id")); err != nil {
writeServiceError(w, err)
@@ -678,6 +1059,14 @@ func (s *Server) deleteComputeAllocationChangeRequestEvent(w http.ResponseWriter
w.WriteHeader(http.StatusNoContent)
}
+// @Summary List events for a change request
+// @Tags Change Request Events
+// @Security CustosUserHeader
+// @Produce json
+// @Param id path string true "Change request ID"
+// @Success 200 {array} models.ComputeAllocationChangeRequestEvent
+// @Failure 404 {object} object{error=string}
+// @Router /compute-allocation-change-requests/{id}/events [get]
func (s *Server) listEventsForChangeRequest(w http.ResponseWriter, r *http.Request) {
rows, err := s.svc.ListEventsForChangeRequest(r.Context(), r.PathValue("id"))
if err != nil {
@@ -687,6 +1076,14 @@ func (s *Server) listEventsForChangeRequest(w http.ResponseWriter, r *http.Reque
writeJSON(w, http.StatusOK, rows)
}
+// @Summary Get the most recent event for a change request
+// @Tags Change Request Events
+// @Security CustosUserHeader
+// @Produce json
+// @Param id path string true "Change request ID"
+// @Success 200 {object} models.ComputeAllocationChangeRequestEvent
+// @Failure 404 {object} object{error=string}
+// @Router /compute-allocation-change-requests/{id}/events/latest [get]
func (s *Server) getLatestEventForChangeRequest(w http.ResponseWriter, r *http.Request) {
evt, err := s.svc.GetLatestEventForChangeRequest(r.Context(), r.PathValue("id"))
if err != nil {
@@ -696,6 +1093,15 @@ func (s *Server) getLatestEventForChangeRequest(w http.ResponseWriter, r *http.R
writeJSON(w, http.StatusOK, evt)
}
+// @Summary Create a compute allocation membership
+// @Tags Compute Allocation Memberships
+// @Security CustosUserHeader
+// @Accept json
+// @Produce json
+// @Param request body models.ComputeAllocationMembership true "Membership payload"
+// @Success 201 {object} models.ComputeAllocationMembership
+// @Failure 400 {object} object{error=string}
+// @Router /compute-allocation-memberships [post]
func (s *Server) createComputeAllocationMembership(w http.ResponseWriter, r *http.Request) {
var m models.ComputeAllocationMembership
if err := decodeJSON(r, &m); err != nil {
@@ -710,6 +1116,14 @@ func (s *Server) createComputeAllocationMembership(w http.ResponseWriter, r *htt
writeJSON(w, http.StatusCreated, created)
}
+// @Summary Get a compute allocation membership
+// @Tags Compute Allocation Memberships
+// @Security CustosUserHeader
+// @Produce json
+// @Param id path string true "Membership ID"
+// @Success 200 {object} models.ComputeAllocationMembership
+// @Failure 404 {object} object{error=string}
+// @Router /compute-allocation-memberships/{id} [get]
func (s *Server) getComputeAllocationMembership(w http.ResponseWriter, r *http.Request) {
m, err := s.svc.GetComputeAllocationMembership(r.Context(), r.PathValue("id"))
if err != nil {
@@ -719,6 +1133,17 @@ func (s *Server) getComputeAllocationMembership(w http.ResponseWriter, r *http.R
writeJSON(w, http.StatusOK, m)
}
+// @Summary Update a compute allocation membership
+// @Tags Compute Allocation Memberships
+// @Security CustosUserHeader
+// @Accept json
+// @Produce json
+// @Param id path string true "Membership ID"
+// @Param request body models.ComputeAllocationMembership true "Membership payload"
+// @Success 200 {object} models.ComputeAllocationMembership
+// @Failure 400 {object} object{error=string}
+// @Failure 404 {object} object{error=string}
+// @Router /compute-allocation-memberships/{id} [put]
func (s *Server) updateComputeAllocationMembership(w http.ResponseWriter, r *http.Request) {
var m models.ComputeAllocationMembership
if err := decodeJSON(r, &m); err != nil {
@@ -734,6 +1159,17 @@ func (s *Server) updateComputeAllocationMembership(w http.ResponseWriter, r *htt
writeJSON(w, http.StatusOK, updated)
}
+// @Summary Update a membership's status
+// @Tags Compute Allocation Memberships
+// @Security CustosUserHeader
+// @Accept json
+// @Produce json
+// @Param id path string true "Membership ID"
+// @Param request body object{membership_status=models.AllocationStatus} true "Status patch"
+// @Success 200 {object} models.ComputeAllocationMembership
+// @Failure 400 {object} object{error=string}
+// @Failure 404 {object} object{error=string}
+// @Router /compute-allocation-memberships/{id}/status [put]
func (s *Server) updateMembershipStatus(w http.ResponseWriter, r *http.Request) {
var body struct {
MembershipStatus models.AllocationStatus `json:"membership_status"`
@@ -750,6 +1186,13 @@ func (s *Server) updateMembershipStatus(w http.ResponseWriter, r *http.Request)
writeJSON(w, http.StatusOK, updated)
}
+// @Summary Delete a compute allocation membership
+// @Tags Compute Allocation Memberships
+// @Security CustosUserHeader
+// @Param id path string true "Membership ID"
+// @Success 204 "No Content"
+// @Failure 404 {object} object{error=string}
+// @Router /compute-allocation-memberships/{id} [delete]
func (s *Server) deleteComputeAllocationMembership(w http.ResponseWriter, r *http.Request) {
if err := s.svc.DeleteComputeAllocationMembership(r.Context(), r.PathValue("id")); err != nil {
writeServiceError(w, err)
@@ -758,6 +1201,14 @@ func (s *Server) deleteComputeAllocationMembership(w http.ResponseWriter, r *htt
w.WriteHeader(http.StatusNoContent)
}
+// @Summary List members of a compute allocation
+// @Tags Compute Allocation Memberships
+// @Security CustosUserHeader
+// @Produce json
+// @Param id path string true "Compute allocation ID"
+// @Success 200 {array} models.ComputeAllocationMembership
+// @Failure 404 {object} object{error=string}
+// @Router /compute-allocations/{id}/memberships [get]
func (s *Server) listMembersForAllocation(w http.ResponseWriter, r *http.Request) {
rows, err := s.svc.ListMembersForAllocation(r.Context(), r.PathValue("id"))
if err != nil {
@@ -767,6 +1218,14 @@ func (s *Server) listMembersForAllocation(w http.ResponseWriter, r *http.Request
writeJSON(w, http.StatusOK, rows)
}
+// @Summary List a user's compute allocation memberships
+// @Tags Compute Allocation Memberships
+// @Security CustosUserHeader
+// @Produce json
+// @Param id path string true "User ID"
+// @Success 200 {array} models.ComputeAllocationMembership
+// @Failure 404 {object} object{error=string}
+// @Router /users/{id}/compute-allocation-memberships [get]
func (s *Server) listAllocationsForUser(w http.ResponseWriter, r *http.Request) {
rows, err := s.svc.ListAllocationsForUser(r.Context(), r.PathValue("id"))
if err != nil {
@@ -776,6 +1235,15 @@ func (s *Server) listAllocationsForUser(w http.ResponseWriter, r *http.Request)
writeJSON(w, http.StatusOK, rows)
}
+// @Summary Create a compute allocation usage record
+// @Tags Compute Allocation Usages
+// @Security CustosUserHeader
+// @Accept json
+// @Produce json
+// @Param request body models.ComputeAllocationUsage true "Usage payload"
+// @Success 201 {object} models.ComputeAllocationUsage
+// @Failure 400 {object} object{error=string}
+// @Router /compute-allocation-usages [post]
func (s *Server) createComputeAllocationUsage(w http.ResponseWriter, r *http.Request) {
var u models.ComputeAllocationUsage
if err := decodeJSON(r, &u); err != nil {
@@ -790,6 +1258,14 @@ func (s *Server) createComputeAllocationUsage(w http.ResponseWriter, r *http.Req
writeJSON(w, http.StatusCreated, created)
}
+// @Summary Get a compute allocation usage record
+// @Tags Compute Allocation Usages
+// @Security CustosUserHeader
+// @Produce json
+// @Param id path string true "Usage ID"
+// @Success 200 {object} models.ComputeAllocationUsage
+// @Failure 404 {object} object{error=string}
+// @Router /compute-allocation-usages/{id} [get]
func (s *Server) getComputeAllocationUsage(w http.ResponseWriter, r *http.Request) {
u, err := s.svc.GetComputeAllocationUsage(r.Context(), r.PathValue("id"))
if err != nil {
@@ -799,6 +1275,13 @@ func (s *Server) getComputeAllocationUsage(w http.ResponseWriter, r *http.Reques
writeJSON(w, http.StatusOK, u)
}
+// @Summary Delete a compute allocation usage record
+// @Tags Compute Allocation Usages
+// @Security CustosUserHeader
+// @Param id path string true "Usage ID"
+// @Success 204 "No Content"
+// @Failure 404 {object} object{error=string}
+// @Router /compute-allocation-usages/{id} [delete]
func (s *Server) deleteComputeAllocationUsage(w http.ResponseWriter, r *http.Request) {
if err := s.svc.DeleteComputeAllocationUsage(r.Context(), r.PathValue("id")); err != nil {
writeServiceError(w, err)
@@ -807,6 +1290,14 @@ func (s *Server) deleteComputeAllocationUsage(w http.ResponseWriter, r *http.Req
w.WriteHeader(http.StatusNoContent)
}
+// @Summary List usages for a compute allocation
+// @Tags Compute Allocation Usages
+// @Security CustosUserHeader
+// @Produce json
+// @Param id path string true "Compute allocation ID"
+// @Success 200 {array} models.ComputeAllocationUsage
+// @Failure 404 {object} object{error=string}
+// @Router /compute-allocations/{id}/usages [get]
func (s *Server) listUsagesForAllocation(w http.ResponseWriter, r *http.Request) {
rows, err := s.svc.ListUsagesForAllocation(r.Context(), r.PathValue("id"))
if err != nil {
@@ -816,6 +1307,14 @@ func (s *Server) listUsagesForAllocation(w http.ResponseWriter, r *http.Request)
writeJSON(w, http.StatusOK, rows)
}
+// @Summary List usages submitted by a user
+// @Tags Compute Allocation Usages
+// @Security CustosUserHeader
+// @Produce json
+// @Param id path string true "User ID"
+// @Success 200 {array} models.ComputeAllocationUsage
+// @Failure 404 {object} object{error=string}
+// @Router /users/{id}/compute-allocation-usages [get]
func (s *Server) listUsagesByUser(w http.ResponseWriter, r *http.Request) {
rows, err := s.svc.ListUsagesByUser(r.Context(), r.PathValue("id"))
if err != nil {
@@ -825,6 +1324,15 @@ func (s *Server) listUsagesByUser(w http.ResponseWriter, r *http.Request) {
writeJSON(w, http.StatusOK, rows)
}
+// @Summary Create a membership resource override
+// @Tags Membership Resource Overrides
+// @Security CustosUserHeader
+// @Accept json
+// @Produce json
+// @Param request body models.ComputeAllocationMembershipResourceOverride true "Override payload"
+// @Success 201 {object} models.ComputeAllocationMembershipResourceOverride
+// @Failure 400 {object} object{error=string}
+// @Router /compute-allocation-membership-resource-overrides [post]
func (s *Server) createComputeAllocationMembershipResourceOverride(w http.ResponseWriter, r *http.Request) {
var o models.ComputeAllocationMembershipResourceOverride
if err := decodeJSON(r, &o); err != nil {
@@ -839,6 +1347,14 @@ func (s *Server) createComputeAllocationMembershipResourceOverride(w http.Respon
writeJSON(w, http.StatusCreated, created)
}
+// @Summary Get a membership resource override
+// @Tags Membership Resource Overrides
+// @Security CustosUserHeader
+// @Produce json
+// @Param id path string true "Override ID"
+// @Success 200 {object} models.ComputeAllocationMembershipResourceOverride
+// @Failure 404 {object} object{error=string}
+// @Router /compute-allocation-membership-resource-overrides/{id} [get]
func (s *Server) getComputeAllocationMembershipResourceOverride(w http.ResponseWriter, r *http.Request) {
o, err := s.svc.GetComputeAllocationMembershipResourceOverride(r.Context(), r.PathValue("id"))
if err != nil {
@@ -848,6 +1364,17 @@ func (s *Server) getComputeAllocationMembershipResourceOverride(w http.ResponseW
writeJSON(w, http.StatusOK, o)
}
+// @Summary Update a membership resource override
+// @Tags Membership Resource Overrides
+// @Security CustosUserHeader
+// @Accept json
+// @Produce json
+// @Param id path string true "Override ID"
+// @Param request body models.ComputeAllocationMembershipResourceOverride true "Override payload"
+// @Success 200 {object} models.ComputeAllocationMembershipResourceOverride
+// @Failure 400 {object} object{error=string}
+// @Failure 404 {object} object{error=string}
+// @Router /compute-allocation-membership-resource-overrides/{id} [put]
func (s *Server) updateComputeAllocationMembershipResourceOverride(w http.ResponseWriter, r *http.Request) {
var o models.ComputeAllocationMembershipResourceOverride
if err := decodeJSON(r, &o); err != nil {
@@ -863,6 +1390,13 @@ func (s *Server) updateComputeAllocationMembershipResourceOverride(w http.Respon
writeJSON(w, http.StatusOK, updated)
}
+// @Summary Delete a membership resource override
+// @Tags Membership Resource Overrides
+// @Security CustosUserHeader
+// @Param id path string true "Override ID"
+// @Success 204 "No Content"
+// @Failure 404 {object} object{error=string}
+// @Router /compute-allocation-membership-resource-overrides/{id} [delete]
func (s *Server) deleteComputeAllocationMembershipResourceOverride(w http.ResponseWriter, r *http.Request) {
if err := s.svc.DeleteComputeAllocationMembershipResourceOverride(r.Context(), r.PathValue("id")); err != nil {
writeServiceError(w, err)
@@ -871,6 +1405,14 @@ func (s *Server) deleteComputeAllocationMembershipResourceOverride(w http.Respon
w.WriteHeader(http.StatusNoContent)
}
+// @Summary List resource overrides for a membership
+// @Tags Membership Resource Overrides
+// @Security CustosUserHeader
+// @Produce json
+// @Param id path string true "Membership ID"
+// @Success 200 {array} models.ComputeAllocationMembershipResourceOverride
+// @Failure 404 {object} object{error=string}
+// @Router /compute-allocation-memberships/{id}/resource-overrides [get]
func (s *Server) listOverridesForMembership(w http.ResponseWriter, r *http.Request) {
rows, err := s.svc.ListOverridesForMembership(r.Context(), r.PathValue("id"))
if err != nil {
@@ -880,6 +1422,14 @@ func (s *Server) listOverridesForMembership(w http.ResponseWriter, r *http.Reque
writeJSON(w, http.StatusOK, rows)
}
+// @Summary List membership overrides referencing a resource
+// @Tags Membership Resource Overrides
+// @Security CustosUserHeader
+// @Produce json
+// @Param id path string true "Compute allocation resource ID"
+// @Success 200 {array} models.ComputeAllocationMembershipResourceOverride
+// @Failure 404 {object} object{error=string}
+// @Router /compute-allocation-resources/{id}/membership-overrides [get]
func (s *Server) listOverridesForResource(w http.ResponseWriter, r *http.Request) {
rows, err := s.svc.ListOverridesForResource(r.Context(), r.PathValue("id"))
if err != nil {
@@ -889,6 +1439,14 @@ func (s *Server) listOverridesForResource(w http.ResponseWriter, r *http.Request
writeJSON(w, http.StatusOK, rows)
}
+// @Summary Get total SU usage for a compute allocation
+// @Tags Compute Allocation Usages
+// @Security CustosUserHeader
+// @Produce json
+// @Param id path string true "Compute allocation ID"
+// @Success 200 {object} object{compute_allocation_id=string,total_su_amount=integer}
+// @Failure 404 {object} object{error=string}
+// @Router /compute-allocations/{id}/usages/total [get]
func (s *Server) getTotalSUUsageForAllocation(w http.ResponseWriter, r *http.Request) {
total, err := s.svc.GetTotalSUUsageForAllocation(r.Context(), r.PathValue("id"))
if err != nil {
@@ -901,6 +1459,15 @@ func (s *Server) getTotalSUUsageForAllocation(w http.ResponseWriter, r *http.Req
})
}
+// @Summary Get total SU usage for a user within a compute allocation
+// @Tags Compute Allocation Usages
+// @Security CustosUserHeader
+// @Produce json
+// @Param id path string true "Compute allocation ID"
+// @Param userId path string true "User ID"
+// @Success 200 {object} object{compute_allocation_id=string,user_id=string,total_su_amount=integer}
+// @Failure 404 {object} object{error=string}
+// @Router /compute-allocations/{id}/users/{userId}/usages/total [get]
func (s *Server) getTotalSUUsageForUserInAllocation(w http.ResponseWriter, r *http.Request) {
allocationID := r.PathValue("id")
userID := r.PathValue("userId")
@@ -920,6 +1487,17 @@ type statusUpdateRequest struct {
Status string `json:"status"`
}
+// @Summary Update a user's status
+// @Tags Users
+// @Security CustosUserHeader
+// @Accept json
+// @Produce json
+// @Param id path string true "User ID"
+// @Param request body object{status=models.UserStatus} true "Status patch"
+// @Success 200 {object} models.User
+// @Failure 400 {object} object{error=string}
+// @Failure 404 {object} object{error=string}
+// @Router /users/{id}/status [put]
func (s *Server) updateUserStatus(w http.ResponseWriter, r *http.Request) {
var req statusUpdateRequest
if err := decodeJSON(r, &req); err != nil {
@@ -934,6 +1512,17 @@ func (s *Server) updateUserStatus(w http.ResponseWriter, r *http.Request) {
writeJSON(w, http.StatusOK, u)
}
+// @Summary Update a project's status
+// @Tags Projects
+// @Security CustosUserHeader
+// @Accept json
+// @Produce json
+// @Param id path string true "Project ID"
+// @Param request body object{status=models.ProjectStatus} true "Status patch"
+// @Success 200 {object} models.Project
+// @Failure 400 {object} object{error=string}
+// @Failure 404 {object} object{error=string}
+// @Router /projects/{id}/status [put]
func (s *Server) updateProjectStatus(w http.ResponseWriter, r *http.Request) {
var req statusUpdateRequest
if err := decodeJSON(r, &req); err != nil {
@@ -948,6 +1537,15 @@ func (s *Server) updateProjectStatus(w http.ResponseWriter, r *http.Request) {
writeJSON(w, http.StatusOK, p)
}
+// @Summary Create a user identity
+// @Tags User Identities
+// @Security CustosUserHeader
+// @Accept json
+// @Produce json
+// @Param request body models.UserIdentity true "Identity payload"
+// @Success 201 {object} models.UserIdentity
+// @Failure 400 {object} object{error=string}
+// @Router /user-identities [post]
func (s *Server) createUserIdentity(w http.ResponseWriter, r *http.Request) {
var e models.UserIdentity
if err := decodeJSON(r, &e); err != nil {
@@ -962,6 +1560,14 @@ func (s *Server) createUserIdentity(w http.ResponseWriter, r *http.Request) {
writeJSON(w, http.StatusCreated, created)
}
+// @Summary Get a user identity
+// @Tags User Identities
+// @Security CustosUserHeader
+// @Produce json
+// @Param id path string true "Identity ID"
+// @Success 200 {object} models.UserIdentity
+// @Failure 404 {object} object{error=string}
+// @Router /user-identities/{id} [get]
func (s *Server) getUserIdentity(w http.ResponseWriter, r *http.Request) {
e, err := s.svc.GetUserIdentity(r.Context(), r.PathValue("id"))
if err != nil {
@@ -971,6 +1577,15 @@ func (s *Server) getUserIdentity(w http.ResponseWriter, r *http.Request) {
writeJSON(w, http.StatusOK, e)
}
+// @Summary Get a user identity by source and external ID
+// @Tags User Identities
+// @Security CustosUserHeader
+// @Produce json
+// @Param source path string true "Identity source"
+// @Param externalId path string true "Identity's external ID at that source"
+// @Success 200 {object} models.UserIdentity
+// @Failure 404 {object} object{error=string}
+// @Router /user-identities/sources/{source}/external/{externalId} [get]
func (s *Server) getUserIdentityBySourceAndExternalID(w http.ResponseWriter, r *http.Request) {
e, err := s.svc.GetUserIdentityBySourceAndExternalID(r.Context(), r.PathValue("source"), r.PathValue("externalId"))
if err != nil {
@@ -980,6 +1595,14 @@ func (s *Server) getUserIdentityBySourceAndExternalID(w http.ResponseWriter, r *
writeJSON(w, http.StatusOK, e)
}
+// @Summary Get a user identity by its OIDC subject claim
+// @Tags User Identities
+// @Security CustosUserHeader
+// @Produce json
+// @Param oidcSub path string true "OIDC subject claim"
+// @Success 200 {object} models.UserIdentity
+// @Failure 404 {object} object{error=string}
+// @Router /user-identities/oidc-subjects/{oidcSub} [get]
func (s *Server) getUserIdentityByOIDCSub(w http.ResponseWriter, r *http.Request) {
e, err := s.svc.GetUserIdentityByOIDCSub(r.Context(), r.PathValue("oidcSub"))
if err != nil {
@@ -989,6 +1612,14 @@ func (s *Server) getUserIdentityByOIDCSub(w http.ResponseWriter, r *http.Request
writeJSON(w, http.StatusOK, e)
}
+// @Summary List a user's identities
+// @Tags User Identities
+// @Security CustosUserHeader
+// @Produce json
+// @Param id path string true "User ID"
+// @Success 200 {array} models.UserIdentity
+// @Failure 404 {object} object{error=string}
+// @Router /users/{id}/user-identities [get]
func (s *Server) listUserIdentitiesForUser(w http.ResponseWriter, r *http.Request) {
out, err := s.svc.ListUserIdentitiesForUser(r.Context(), r.PathValue("id"))
if err != nil {
@@ -998,6 +1629,17 @@ func (s *Server) listUserIdentitiesForUser(w http.ResponseWriter, r *http.Reques
writeJSON(w, http.StatusOK, out)
}
+// @Summary Update a user identity
+// @Tags User Identities
+// @Security CustosUserHeader
+// @Accept json
+// @Produce json
+// @Param id path string true "Identity ID"
+// @Param request body models.UserIdentity true "Identity payload"
+// @Success 200 {object} models.UserIdentity
+// @Failure 400 {object} object{error=string}
+// @Failure 404 {object} object{error=string}
+// @Router /user-identities/{id} [put]
func (s *Server) updateUserIdentity(w http.ResponseWriter, r *http.Request) {
var e models.UserIdentity
if err := decodeJSON(r, &e); err != nil {
@@ -1012,6 +1654,13 @@ func (s *Server) updateUserIdentity(w http.ResponseWriter, r *http.Request) {
writeJSON(w, http.StatusOK, &e)
}
+// @Summary Delete a user identity
+// @Tags User Identities
+// @Security CustosUserHeader
+// @Param id path string true "Identity ID"
+// @Success 204 "No Content"
+// @Failure 404 {object} object{error=string}
+// @Router /user-identities/{id} [delete]
func (s *Server) deleteUserIdentity(w http.ResponseWriter, r *http.Request) {
if err := s.svc.DeleteUserIdentity(r.Context(), r.PathValue("id")); err != nil {
writeServiceError(w, err)
@@ -1025,6 +1674,17 @@ type mergeUsersRequest struct {
RetiringUserID string `json:"retiring_user_id"`
}
+// @Summary Merge two users
+// @Description Merges the retiring user into the surviving user; the surviving record is returned.
+// @Tags Users
+// @Security CustosUserHeader
+// @Accept json
+// @Produce json
+// @Param request body mergeUsersRequest true "Merge payload"
+// @Success 200 {object} models.User
+// @Failure 400 {object} object{error=string}
+// @Failure 404 {object} object{error=string}
+// @Router /users/merge [post]
func (s *Server) mergeUsers(w http.ResponseWriter, r *http.Request) {
var req mergeUsersRequest
if err := decodeJSON(r, &req); err != nil {