diff --git a/.github/workflows/openapi.yml b/.github/workflows/openapi.yml new file mode 100644 index 0000000..ca85437 --- /dev/null +++ b/.github/workflows/openapi.yml @@ -0,0 +1,18 @@ +--- +name: OpenAPI +on: # yamllint disable-line rule:truthy + push: + pull_request: + +jobs: + check-aep-compliance: + runs-on: ubuntu-latest + steps: + - name: Check out code + uses: actions/checkout@v4 + - name: Set up Go + uses: actions/setup-go@v5 + - name: Install Spectral + run: npm i @stoplight/spectral-cli -g + - name: Check that OpenAPI conforms to AEP guidelines + run: spectral lint -r api/.spectral.yaml api/*.yaml diff --git a/api/.spectral.yaml b/api/.spectral.yaml new file mode 100644 index 0000000..799442c --- /dev/null +++ b/api/.spectral.yaml @@ -0,0 +1,2 @@ +extends: + - https://raw.githubusercontent.com/aep-dev/aep-openapi-linter/main/spectral.yaml diff --git a/api/interoperabilityAPI.yaml b/api/interoperabilityAPI.yaml new file mode 100644 index 0000000..002dba0 --- /dev/null +++ b/api/interoperabilityAPI.yaml @@ -0,0 +1,509 @@ +openapi: 3.0.4 +info: + title: DCM Interoperability API + description: |- + This API is used to define and manage the resources provided by the Resources Providers. + version: 0.0.1 +paths: + /resource: + get: + tags: + - resource + summary: List all resources. + description: Returns a list of resources. + operationId: ListResource + parameters: + - in: query + name: max_page_size + schema: + type: integer + - in: query + name: page_token + schema: + type: string + - in: query + name: skip + schema: + type: integer + - in: query + name: filter + schema: + type: string + responses: + '200': + description: successful operation + content: + application/yaml: + schema: + properties: + next_page_token: + type: string + results: + items: + $ref: '#/components/schemas/Resource' + type: array + type: object + default: + description: Unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + security: + - api_key: [] + - resourcestore_auth: + - read:resources + post: + tags: + - resource + summary: Add a new resource. + description: Add a new resource to the catalog. + operationId: CreateResource + requestBody: + description: Create a new resource in the catalog + content: + application/yaml: + schema: + $ref: '#/components/schemas/Resource' + required: true + responses: + '200': + description: Successful operation + content: + application/yaml: + schema: + $ref: '#/components/schemas/Resource' + '400': + description: Invalid input + '422': + description: Validation exception + default: + description: Unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + security: + - resourcestore_auth: + - write:resources + /resource/{resourceKind}: + get: + tags: + - resource + summary: Find resource by Kind. + description: Returns a single resource. + operationId: GetResource + parameters: + - name: resourceKind + in: path + description: Kind of resource to return + required: true + schema: + type: string + format: uri + responses: + '200': + description: successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/Resource' + application/xml: + schema: + $ref: '#/components/schemas/Resource' + '400': + description: Invalid Kind supplied + '404': + description: Resource not found + default: + description: Unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + security: + - api_key: [] + - resourcestore_auth: + - read:resources + put: + tags: + - resource + summary: Updates a resource in the store with form data. + description: Updates a resource resource based on the form data. + operationId: ApplyResourceUpdate + requestBody: + content: + 'application/json': + schema: + type: object + 'x-aep-resource': + singular: Resource + plural: Resources + parameters: + - name: resourceKind + in: path + description: Kind of resource that needs to be updated + required: true + schema: + type: string + format: uri + responses: + '200': + description: successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/Resource' + application/xml: + schema: + $ref: '#/components/schemas/Resource' + '400': + description: Invalid input + default: + description: Unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + security: + - resourcestore_auth: + - write:resources + - read:resources + delete: + tags: + - resource + summary: Deletes a resource. + description: Delete a resource. + operationId: DeleteResource + parameters: + - name: resourceKind + in: path + description: Kind of the resource to be deleted + required: true + schema: + type: string + format: uri + responses: + '200': + description: Resource deleted + '400': + description: Invalid resource value + default: + description: Unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + security: + - resourcestore_auth: + - write:resources + - read:resources + /resource/{resourceKind}/provider: + get: + tags: + - resource + summary: List all providers for a specific resource type. + description: Returns a list of providers. + operationId: ListProvider + parameters: + - name: resourceKind + in: path + description: Kind of resource to consider + required: true + schema: + type: string + format: uri + - in: query + name: max_page_size + schema: + type: integer + - in: query + name: page_token + schema: + type: string + - in: query + name: skip + schema: + type: integer + - in: query + name: filter + schema: + type: string + responses: + '200': + description: successful operation + content: + application/yaml: + schema: + properties: + next_page_token: + type: string + results: + items: + $ref: '#/components/schemas/Provider' + type: array + type: object + default: + description: Unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + security: + - api_key: [] + - resourcestore_auth: + - read:providers + post: + tags: + - resource + summary: Add a new provider to a resource kind. + description: Add a new provider to a resource kind. + operationId: CreateProvider + parameters: + - name: resourceKind + in: path + description: Kind of resource to consider + required: true + schema: + type: string + format: uri + requestBody: + description: Create a new resource in the catalog + content: + application/yaml: + schema: + $ref: '#/components/schemas/Provider' + required: true + responses: + '200': + description: Successful operation + content: + application/yaml: + schema: + $ref: '#/components/schemas/Resource' + '400': + description: Invalid input + '422': + description: Validation exception + default: + description: Unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + security: + - resourcestore_auth: + - write:providers + /resource/{resourceKind}/provider/{providerId}: + get: + tags: + - resource + summary: Find partner by ID from by Kind. + description: Returns a single resource. + operationId: GetResourceProvider + parameters: + - name: resourceKind + in: path + description: Kind of resource to consider + required: true + schema: + type: string + format: uri + - name: providerId + in: path + description: ID of the provider to consider + required: true + schema: + type: string + format: uurl + responses: + '200': + description: successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/Provider' + application/xml: + schema: + $ref: '#/components/schemas/Provider' + '400': + description: Invalid Kind supplied + '404': + description: Resource not found + default: + description: Unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + security: + - api_key: [] + - resourcestore_auth: + - read:resources + put: + tags: + - resource + summary: Updates a provider for a resource in the store with form data. + description: Updates a provider for a resource in the store with form data. + operationId: ApplyResourceProviderUpdate + requestBody: + content: + 'application/json': + schema: + type: object + 'x-aep-resource': + singular: Resource + plural: Resources + parameters: + - name: resourceKind + in: path + description: Kind of resource to consider + required: true + schema: + type: string + format: uri + - name: providerId + in: path + description: ID of the provider to update + required: true + schema: + type: string + format: uurl + responses: + '200': + description: successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/Resource' + application/xml: + schema: + $ref: '#/components/schemas/Resource' + '400': + description: Invalid input + default: + description: Unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + security: + - resourcestore_auth: + - write:resources + - read:resources + delete: + tags: + - resource + summary: Deletes a resource provider. + description: Delete a resource provider. + operationId: DeleteResourceProvider + parameters: + - name: resourceKind + in: path + description: Kind of the resource to be deleted + required: true + schema: + type: string + format: uri + - name: providerId + in: path + description: ID of the provider to be deleted + required: true + schema: + type: string + format: uurl + responses: + '200': + description: Resource deleted + '400': + description: Invalid resource value + default: + description: Unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + security: + - resourcestore_auth: + - write:resources + - read:resources +components: + schemas: + Resource: + required: + - apiVersion + - kind + - spec + type: object + x-aep-resource: true + properties: + apiVersion: + type: string + description: version of the resource definition + kind: + type: string + format: uri + description: resource kind name (PascalCase)) + spec: + type: object + description: openAPI object of the resource specs object + status: + type: object + description: openAPI object of the resource status object + Provider: + required: + - name + - id + type: object + x-aep-resource: true + properties: + name: + type: string + description: name of the provider + id: + type: string + format: uuid + description: provider's ID + Error: + type: object + properties: + type: + type: string + description: The error code identifying the type of error + title: + type: string + description: A human-readable description of the generic problem + detail: + type: string + description: A human-readable explanation specific to this occurrence of the problem + instance: + type: string + format: uuid + description: A unique identifier for the specific occurrence of the problem + required: + - type + - title + - instance + requestBodies: + Resource: + description: Resource object that needs to be added to the store + content: + application/json: + schema: + $ref: '#/components/schemas/Resource' + application/xml: + schema: + $ref: '#/components/schemas/Resource' + securitySchemes: + resourcestore_auth: + type: oauth2 + flows: + implicit: + authorizationUrl: https://resourcestore3.swagger.io/oauth/authorize + scopes: + "write:resources": modify resources in your account + "read:resources": read your resources + api_key: + type: apiKey + name: api_key + in: header diff --git a/object_design.md b/object_design.md new file mode 100644 index 0000000..c1bb511 --- /dev/null +++ b/object_design.md @@ -0,0 +1,89 @@ +# RFC: DCM Service Spec Design Guidelines + +**Feature Name:** DCM Service Spec Design Guidelines + +**Type:** feature + +**Start Date:** 2025-08-01 + +**Status:** Draft + +## Abstract +This document defines the conventions and principles for designing DCM Service Specs, both built-in and custom resources. + +The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in [RFC 2119](https://datatracker.ietf.org/doc/rfc2119/). + +## Motivation +DCM Service Spec evolve over time and must remain backward compatible while accommodating new features. A consistent design approach ensures: + +* Predictability for users +* Stability of contracts across versions +* Extensibility for new use cases without breaking existing ones + +## Terminology +* **Built-in Resource Definition**: A top-level DCM Service Spec that is defined within the DCM project. +* **Custom Resource Definition**: A Service Spec that is a user-defined extension of the DCM Service Specs. +* **Spec**: The desired state of the object. +* **Status**: The observed state of the object. +* **Metadata**: Common fields such as `name`, `namespace`, `labels`, `annotations`. + +## Service Spec Structure +### Top-Level Structure +All DCM Service Specs MUST include: + +* `apiVersion`: API group and version. +* `kind`: Resource kind name (PascalCase). +* `metadata`: Object metadata (name, namespace, labels, annotations). +* `spec`: Desired state (optional for read-only resources). +* `status`: Observed state (read-only, updated by controllers). + +### API version +* API versions MUST follow the DCM versioning convention: `v1alpha1`, `v1beta1`, `v1`. +* Breaking changes MUST only occur between major version bumps (e.g., `v1` → `v2`). +* Alpha versions MAY introduce breaking changes; Beta and GA versions MUST preserve compatibility. +* Deprecation MUST be announced at least two releases before removal. + +## Kind +* The `kind` MUST be a valid URI resolvable to the documentation of the oject itself. +* Resource names MUST be lowercase, plural, and in English. +* Field names MUST be in `camelCase`. +* Enumerated values MUST be in `PascalCase`. + +### Spec vs Status +* **Spec** MUST be declarative and idempotent. +* **Status** MUST be system-populated and reflect actual runtime state. +* **Spec** changes SHALL NOT directly mutate **Status**; controllers MUST mediate changes. + +## Validation and Defaulting +* All fields MUST be validated using OpenAPI v3 schema. +* Default values SHOULD be applied using defaulting mechanisms in the controller. +* Validation errors MUST be explicit and descriptive. + +## Extensibility +* Optional fields SHOULD be added in a backward-compatible way. +* New capabilities SHOULD be introduced as optional fields, feature gates, or separate resources. +* Labels and annotations MAY be used for opaque, non-critical extensions. This approach SHOULD be avoided, preferring the creation of new fields within the Resource Definition itself. +* Implementations MUST NOT embed arbitrary JSON in fields; structured schema SHALL be preferred. + +## Field Design Principles +* Fields MUST be consistent in naming and type across Specs. +* Boolean fields MUST be prefixed with verbs like `enable`, `allow`, `require`. +* Duration values MUST use standard time units (e.g., `30s`, `5m`). +* Lists SHALL be treated as sets, thus disregarding the order of the items. + +## Abstraction Level +* API objects MUST operate at an appropriate level of abstraction for their intended audience. +* User-facing APIs SHOULD abstract implementation details and expose only necessary configuration parameters. +* Low-level APIs MAY expose more granular configuration but MUST remain portable across different environments. +* APIs MUST avoid exposing internal controller logic or cluster-specific implementation details that could break portability. +* Resource Definitions SHOULD define clear boundaries of abstraction to avoid overloading users with unnecessary complexity. + +## API Evolution +* Fields MUST NOT be removed from GA APIs; they MAY be deprecated and ignored. +* Renamed fields MUST preserve compatibility via aliasing or conversion. +* Changes MUST be additive to preserve backward compatibility. + +## Security and Multi-Tenancy +* Sensitive fields MUST NOT appear in plain text in `status`. +* API access MUST be controlled via RBAC. +* Multi-tenant Resource Definition MUST isolate data per tenant.