diff --git a/.github/workflows/openapi-publish.yml b/.github/workflows/openapi-publish.yml index 8d4b95e..3b96096 100644 --- a/.github/workflows/openapi-publish.yml +++ b/.github/workflows/openapi-publish.yml @@ -30,12 +30,19 @@ jobs: run: | mkdir -p dist redocly bundle openapi/files/v1/openapi.yaml -o dist/files-v1.yaml + - name: Bundle spec (secrets v1) + run: | + mkdir -p dist + redocly bundle openapi/secrets/v1/openapi.yaml -o dist/secrets-v1.yaml - name: Lint bundled spec with Spectral (team v1) run: | spectral lint dist/team-v1.yaml - name: Lint bundled spec with Spectral (files v1) run: | spectral lint dist/files-v1.yaml + - name: Lint bundled spec with Spectral (secrets v1) + run: | + spectral lint dist/secrets-v1.yaml - name: Upload bundled artifact (team v1) uses: actions/upload-artifact@v4 with: @@ -46,6 +53,11 @@ jobs: with: name: files-v1-openapi path: dist/files-v1.yaml + - name: Upload bundled artifact (secrets v1) + uses: actions/upload-artifact@v4 + with: + name: secrets-v1-openapi + path: dist/secrets-v1.yaml publish: if: github.event_name == 'push' @@ -71,6 +83,10 @@ jobs: run: | mkdir -p dist redocly bundle openapi/files/v1/openapi.yaml -o dist/files-v1.yaml + - name: Bundle spec (secrets v1) + run: | + mkdir -p dist + redocly bundle openapi/secrets/v1/openapi.yaml -o dist/secrets-v1.yaml - name: Install ORAS env: ORAS_VERSION: '1.1.0' @@ -92,3 +108,7 @@ jobs: run: | oras push ghcr.io/agynio/openapi/files:1 \ dist/files-v1.yaml:application/vnd.oai.openapi+yaml + - name: Push OCI artifact to GHCR (secrets v1) + run: | + oras push ghcr.io/agynio/openapi/secrets:1 \ + dist/secrets-v1.yaml:application/vnd.oai.openapi+yaml diff --git a/openapi/secrets/v1/components/parameters/IdPath.yaml b/openapi/secrets/v1/components/parameters/IdPath.yaml new file mode 100644 index 0000000..26f2028 --- /dev/null +++ b/openapi/secrets/v1/components/parameters/IdPath.yaml @@ -0,0 +1,6 @@ +name: id +in: path +required: true +schema: + type: string + format: uuid diff --git a/openapi/secrets/v1/components/responses/ProblemResponse.yaml b/openapi/secrets/v1/components/responses/ProblemResponse.yaml new file mode 100644 index 0000000..0a98c5a --- /dev/null +++ b/openapi/secrets/v1/components/responses/ProblemResponse.yaml @@ -0,0 +1,5 @@ +description: RFC 7807 problem response +content: + application/problem+json: + schema: + $ref: '../schemas/Problem.yaml' diff --git a/openapi/secrets/v1/components/schemas/EntityMeta.yaml b/openapi/secrets/v1/components/schemas/EntityMeta.yaml new file mode 100644 index 0000000..7769ea1 --- /dev/null +++ b/openapi/secrets/v1/components/schemas/EntityMeta.yaml @@ -0,0 +1,6 @@ +type: object +properties: + id: { type: string, format: uuid } + createdAt: { type: string, format: date-time } + updatedAt: { type: string, format: date-time } +required: [id, createdAt] diff --git a/openapi/secrets/v1/components/schemas/PaginatedSecretProviders.yaml b/openapi/secrets/v1/components/schemas/PaginatedSecretProviders.yaml new file mode 100644 index 0000000..208f1c4 --- /dev/null +++ b/openapi/secrets/v1/components/schemas/PaginatedSecretProviders.yaml @@ -0,0 +1,9 @@ +type: object +properties: + items: + type: array + items: { $ref: './SecretProvider.yaml' } + page: { type: integer } + perPage: { type: integer } + total: { type: integer } +required: [items, page, perPage, total] diff --git a/openapi/secrets/v1/components/schemas/PaginatedSecrets.yaml b/openapi/secrets/v1/components/schemas/PaginatedSecrets.yaml new file mode 100644 index 0000000..d30f1cc --- /dev/null +++ b/openapi/secrets/v1/components/schemas/PaginatedSecrets.yaml @@ -0,0 +1,9 @@ +type: object +properties: + items: + type: array + items: { $ref: './Secret.yaml' } + page: { type: integer } + perPage: { type: integer } + total: { type: integer } +required: [items, page, perPage, total] diff --git a/openapi/secrets/v1/components/schemas/Pagination.yaml b/openapi/secrets/v1/components/schemas/Pagination.yaml new file mode 100644 index 0000000..82421f7 --- /dev/null +++ b/openapi/secrets/v1/components/schemas/Pagination.yaml @@ -0,0 +1,6 @@ +type: object +properties: + page: { type: integer, minimum: 1 } + perPage: { type: integer, minimum: 1 } + total: { type: integer, minimum: 0 } +required: [page, perPage, total] diff --git a/openapi/secrets/v1/components/schemas/Problem.yaml b/openapi/secrets/v1/components/schemas/Problem.yaml new file mode 100644 index 0000000..ef475b1 --- /dev/null +++ b/openapi/secrets/v1/components/schemas/Problem.yaml @@ -0,0 +1,8 @@ +type: object +properties: + type: { type: string, format: uri } + title: { type: string } + status: { type: integer } + detail: { type: string } + instance: { type: string, format: uri } +required: [title, status] diff --git a/openapi/secrets/v1/components/schemas/ResolvedSecretValue.yaml b/openapi/secrets/v1/components/schemas/ResolvedSecretValue.yaml new file mode 100644 index 0000000..ed564a3 --- /dev/null +++ b/openapi/secrets/v1/components/schemas/ResolvedSecretValue.yaml @@ -0,0 +1,4 @@ +type: object +properties: + value: { type: string } +required: [value] diff --git a/openapi/secrets/v1/components/schemas/Secret.yaml b/openapi/secrets/v1/components/schemas/Secret.yaml new file mode 100644 index 0000000..b599c5a --- /dev/null +++ b/openapi/secrets/v1/components/schemas/Secret.yaml @@ -0,0 +1,9 @@ +allOf: + - $ref: './EntityMeta.yaml' + - type: object + properties: + title: { type: string } + description: { type: string } + secretProviderId: { type: string, format: uuid } + remoteName: { type: string } + required: [secretProviderId, remoteName] diff --git a/openapi/secrets/v1/components/schemas/SecretCreateRequest.yaml b/openapi/secrets/v1/components/schemas/SecretCreateRequest.yaml new file mode 100644 index 0000000..08296b5 --- /dev/null +++ b/openapi/secrets/v1/components/schemas/SecretCreateRequest.yaml @@ -0,0 +1,7 @@ +type: object +properties: + title: { type: string } + description: { type: string } + secretProviderId: { type: string, format: uuid } + remoteName: { type: string } +required: [secretProviderId, remoteName] diff --git a/openapi/secrets/v1/components/schemas/SecretProvider.yaml b/openapi/secrets/v1/components/schemas/SecretProvider.yaml new file mode 100644 index 0000000..d429e6c --- /dev/null +++ b/openapi/secrets/v1/components/schemas/SecretProvider.yaml @@ -0,0 +1,9 @@ +allOf: + - $ref: './EntityMeta.yaml' + - type: object + properties: + title: { type: string } + description: { type: string } + type: { $ref: './SecretProviderType.yaml' } + config: { $ref: './SecretProviderConfig.yaml' } + required: [type, config] diff --git a/openapi/secrets/v1/components/schemas/SecretProviderConfig.yaml b/openapi/secrets/v1/components/schemas/SecretProviderConfig.yaml new file mode 100644 index 0000000..8cc621d --- /dev/null +++ b/openapi/secrets/v1/components/schemas/SecretProviderConfig.yaml @@ -0,0 +1,4 @@ +type: object +properties: + vault: { $ref: './VaultConfig.yaml' } +additionalProperties: false diff --git a/openapi/secrets/v1/components/schemas/SecretProviderCreateRequest.yaml b/openapi/secrets/v1/components/schemas/SecretProviderCreateRequest.yaml new file mode 100644 index 0000000..196fc99 --- /dev/null +++ b/openapi/secrets/v1/components/schemas/SecretProviderCreateRequest.yaml @@ -0,0 +1,7 @@ +type: object +properties: + title: { type: string } + description: { type: string } + type: { $ref: './SecretProviderType.yaml' } + config: { $ref: './SecretProviderConfig.yaml' } +required: [type, config] diff --git a/openapi/secrets/v1/components/schemas/SecretProviderType.yaml b/openapi/secrets/v1/components/schemas/SecretProviderType.yaml new file mode 100644 index 0000000..2f536d3 --- /dev/null +++ b/openapi/secrets/v1/components/schemas/SecretProviderType.yaml @@ -0,0 +1,3 @@ +type: string +enum: + - vault diff --git a/openapi/secrets/v1/components/schemas/SecretProviderUpdateRequest.yaml b/openapi/secrets/v1/components/schemas/SecretProviderUpdateRequest.yaml new file mode 100644 index 0000000..fa75ac4 --- /dev/null +++ b/openapi/secrets/v1/components/schemas/SecretProviderUpdateRequest.yaml @@ -0,0 +1,6 @@ +type: object +properties: + title: { type: string } + description: { type: string } + config: { $ref: './SecretProviderConfig.yaml' } +additionalProperties: false diff --git a/openapi/secrets/v1/components/schemas/SecretUpdateRequest.yaml b/openapi/secrets/v1/components/schemas/SecretUpdateRequest.yaml new file mode 100644 index 0000000..a433939 --- /dev/null +++ b/openapi/secrets/v1/components/schemas/SecretUpdateRequest.yaml @@ -0,0 +1,7 @@ +type: object +properties: + title: { type: string } + description: { type: string } + secretProviderId: { type: string, format: uuid } + remoteName: { type: string } +additionalProperties: false diff --git a/openapi/secrets/v1/components/schemas/VaultConfig.yaml b/openapi/secrets/v1/components/schemas/VaultConfig.yaml new file mode 100644 index 0000000..924fde8 --- /dev/null +++ b/openapi/secrets/v1/components/schemas/VaultConfig.yaml @@ -0,0 +1,5 @@ +type: object +properties: + address: { type: string } + token: { type: string } +required: [address, token] diff --git a/openapi/secrets/v1/openapi.yaml b/openapi/secrets/v1/openapi.yaml new file mode 100644 index 0000000..345fafa --- /dev/null +++ b/openapi/secrets/v1/openapi.yaml @@ -0,0 +1,58 @@ +openapi: 3.0.3 +info: + title: Secrets API + version: 0.1.0 +servers: + - url: https://api.example.com +tags: + - name: SecretProviders + - name: Secrets +paths: + /secret-providers: + $ref: './paths/secret-providers.yaml' + /secret-providers/{id}: + $ref: './paths/secret-provider-by-id.yaml' + /secrets: + $ref: './paths/secrets.yaml' + /secrets/{id}: + $ref: './paths/secret-by-id.yaml' + /secrets/{id}/resolve: + $ref: './paths/secret-resolve.yaml' +components: + parameters: + IdPath: + $ref: './components/parameters/IdPath.yaml' + responses: + ProblemResponse: + $ref: './components/responses/ProblemResponse.yaml' + schemas: + Problem: + $ref: './components/schemas/Problem.yaml' + Pagination: + $ref: './components/schemas/Pagination.yaml' + EntityMeta: + $ref: './components/schemas/EntityMeta.yaml' + SecretProviderType: + $ref: './components/schemas/SecretProviderType.yaml' + VaultConfig: + $ref: './components/schemas/VaultConfig.yaml' + SecretProviderConfig: + $ref: './components/schemas/SecretProviderConfig.yaml' + SecretProvider: + $ref: './components/schemas/SecretProvider.yaml' + SecretProviderCreateRequest: + $ref: './components/schemas/SecretProviderCreateRequest.yaml' + SecretProviderUpdateRequest: + $ref: './components/schemas/SecretProviderUpdateRequest.yaml' + PaginatedSecretProviders: + $ref: './components/schemas/PaginatedSecretProviders.yaml' + Secret: + $ref: './components/schemas/Secret.yaml' + SecretCreateRequest: + $ref: './components/schemas/SecretCreateRequest.yaml' + SecretUpdateRequest: + $ref: './components/schemas/SecretUpdateRequest.yaml' + PaginatedSecrets: + $ref: './components/schemas/PaginatedSecrets.yaml' + ResolvedSecretValue: + $ref: './components/schemas/ResolvedSecretValue.yaml' diff --git a/openapi/secrets/v1/paths/secret-by-id.yaml b/openapi/secrets/v1/paths/secret-by-id.yaml new file mode 100644 index 0000000..3734623 --- /dev/null +++ b/openapi/secrets/v1/paths/secret-by-id.yaml @@ -0,0 +1,44 @@ +get: + tags: [Secrets] + summary: Get secret by ID + parameters: + - $ref: '../components/parameters/IdPath.yaml' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '../components/schemas/Secret.yaml' + default: + $ref: '../components/responses/ProblemResponse.yaml' +patch: + tags: [Secrets] + summary: Update secret (partial) + parameters: + - $ref: '../components/parameters/IdPath.yaml' + requestBody: + required: true + content: + application/json: + schema: + $ref: '../components/schemas/SecretUpdateRequest.yaml' + responses: + '200': + description: Updated + content: + application/json: + schema: + $ref: '../components/schemas/Secret.yaml' + default: + $ref: '../components/responses/ProblemResponse.yaml' +delete: + tags: [Secrets] + summary: Delete secret + parameters: + - $ref: '../components/parameters/IdPath.yaml' + responses: + '204': + description: No Content + default: + $ref: '../components/responses/ProblemResponse.yaml' diff --git a/openapi/secrets/v1/paths/secret-provider-by-id.yaml b/openapi/secrets/v1/paths/secret-provider-by-id.yaml new file mode 100644 index 0000000..27ce088 --- /dev/null +++ b/openapi/secrets/v1/paths/secret-provider-by-id.yaml @@ -0,0 +1,44 @@ +get: + tags: [SecretProviders] + summary: Get secret provider by ID + parameters: + - $ref: '../components/parameters/IdPath.yaml' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '../components/schemas/SecretProvider.yaml' + default: + $ref: '../components/responses/ProblemResponse.yaml' +patch: + tags: [SecretProviders] + summary: Update secret provider (partial) + parameters: + - $ref: '../components/parameters/IdPath.yaml' + requestBody: + required: true + content: + application/json: + schema: + $ref: '../components/schemas/SecretProviderUpdateRequest.yaml' + responses: + '200': + description: Updated + content: + application/json: + schema: + $ref: '../components/schemas/SecretProvider.yaml' + default: + $ref: '../components/responses/ProblemResponse.yaml' +delete: + tags: [SecretProviders] + summary: Delete secret provider + parameters: + - $ref: '../components/parameters/IdPath.yaml' + responses: + '204': + description: No Content + default: + $ref: '../components/responses/ProblemResponse.yaml' diff --git a/openapi/secrets/v1/paths/secret-providers.yaml b/openapi/secrets/v1/paths/secret-providers.yaml new file mode 100644 index 0000000..acb50d8 --- /dev/null +++ b/openapi/secrets/v1/paths/secret-providers.yaml @@ -0,0 +1,44 @@ +get: + tags: [SecretProviders] + summary: List secret providers + parameters: + - in: query + name: page + schema: + type: integer + minimum: 1 + default: 1 + - in: query + name: perPage + schema: + type: integer + minimum: 1 + maximum: 100 + default: 20 + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '../components/schemas/PaginatedSecretProviders.yaml' + default: + $ref: '../components/responses/ProblemResponse.yaml' +post: + tags: [SecretProviders] + summary: Create secret provider + requestBody: + required: true + content: + application/json: + schema: + $ref: '../components/schemas/SecretProviderCreateRequest.yaml' + responses: + '201': + description: Created + content: + application/json: + schema: + $ref: '../components/schemas/SecretProvider.yaml' + default: + $ref: '../components/responses/ProblemResponse.yaml' diff --git a/openapi/secrets/v1/paths/secret-resolve.yaml b/openapi/secrets/v1/paths/secret-resolve.yaml new file mode 100644 index 0000000..90b04b0 --- /dev/null +++ b/openapi/secrets/v1/paths/secret-resolve.yaml @@ -0,0 +1,14 @@ +post: + tags: [Secrets] + summary: Resolve secret value + parameters: + - $ref: '../components/parameters/IdPath.yaml' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '../components/schemas/ResolvedSecretValue.yaml' + default: + $ref: '../components/responses/ProblemResponse.yaml' diff --git a/openapi/secrets/v1/paths/secrets.yaml b/openapi/secrets/v1/paths/secrets.yaml new file mode 100644 index 0000000..0cd2da8 --- /dev/null +++ b/openapi/secrets/v1/paths/secrets.yaml @@ -0,0 +1,49 @@ +get: + tags: [Secrets] + summary: List secrets + parameters: + - in: query + name: secretProviderId + schema: + type: string + format: uuid + - in: query + name: page + schema: + type: integer + minimum: 1 + default: 1 + - in: query + name: perPage + schema: + type: integer + minimum: 1 + maximum: 100 + default: 20 + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '../components/schemas/PaginatedSecrets.yaml' + default: + $ref: '../components/responses/ProblemResponse.yaml' +post: + tags: [Secrets] + summary: Create secret + requestBody: + required: true + content: + application/json: + schema: + $ref: '../components/schemas/SecretCreateRequest.yaml' + responses: + '201': + description: Created + content: + application/json: + schema: + $ref: '../components/schemas/Secret.yaml' + default: + $ref: '../components/responses/ProblemResponse.yaml'