Repository: innabox/fulfillment-api
Pull request #19: Add endpoint to retrieve admin Kubeconfig
Author: login: jhernand
Created: 2025-03-14T16:52:53Z | Merged: 2025-03-19T09:07:03Z

This patch adds two new methods to retrieve the admin Kubeconfig of a cluster: `GetKubeconfig` and `GetKubeconfigViaHttp`. The first is intended for use with gRPC only, and it isn't mapped to HTTP. The second is intended for use with HTTP only, and can be used like this:

    GET /api/fulfillment/v1/clusters/123/kubeconfig HTTP/1.1

    HTTP/1.1 200 OK
    Content-Type: application/yaml
    Content-Length: 21484

    apiVersion: v1
    clusters:
    - cluster:
    ...

References: https://github.com/innabox/fulfillment-api/issues/12

Note that this pull request also includes the patch from #18, will need to be rebased once that is merged.

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **New Features**
	- Introduced a new API endpoint that lets users retrieve the administrative Kubeconfig in YAML format for a specific cluster.
	- Enabled support for both HTTP and gRPC protocols to enhance integration.
	- Requires the submission of a valid cluster identifier to access the configuration details.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->


Diff:
diff --git a/openapi/v2/openapi.json b/openapi/v2/openapi.json
index 6b01c7b..9a4ce79 100644
--- a/openapi/v2/openapi.json
+++ b/openapi/v2/openapi.json
@@ -354,6 +354,38 @@
         ]
       }
     },
+    "/api/fulfillment/v1/clusters/{cluster_id}/kubeconfig": {
+      "get": {
+        "summary": "Returns the admin Kubeconfig of the cluster.",
+        "description": "This is intended for use with HTTP and returns the YAML text of the Kubeconfig directly using the content type\n`application/yaml`.\n\nbuf:lint:ignore RPC_RESPONSE_STANDARD_NAME",
+        "operationId": "Clusters_GetKubeconfigViaHttp",
+        "responses": {
+          "200": {
+            "description": "A successful response.",
+            "schema": {
+              "$ref": "#/definitions/apiHttpBody"
+            }
+          },
+          "default": {
+            "description": "An unexpected error response.",
+            "schema": {
+              "$ref": "#/definitions/rpcStatus"
+            }
+          }
+        },
+        "parameters": [
+          {
+            "name": "cluster_id",
+            "in": "path",
+            "required": true,
+            "type": "string"
+          }
+        ],
+        "tags": [
+          "Clusters"
+        ]
+      }
+    },
     "/api/fulfillment/v1/events": {
       "get": {
         "summary": "Watches events.",
@@ -404,6 +436,29 @@
     }
   },
   "definitions": {
+    "apiHttpBody": {
+      "type": "object",
+      "properties": {
+        "content_type": {
+          "type": "string",
+          "description": "The HTTP Content-Type header value specifying the content type of the body."
+        },
+        "data": {
+          "type": "string",
+          "format": "byte",
+          "description": "The HTTP request/response body as raw binary."
+        },
+        "extensions": {
+          "type": "array",
+          "items": {
+            "type": "object",
+            "$ref": "#/definitions/protobufAny"
+          },
+          "description": "Application specific response metadata. Must be set in the first response\nfor streaming APIs."
+        }
+      },
+      "description": "Message that represents an arbitrary HTTP body. It should only be used for\npayload formats that can't be represented as JSON, such as raw binary or\nan HTML page.\n\n\nThis message can be used both in streaming and non-streaming API methods in\nthe request as well as the response.\n\nIt can be used as a top-level request field, which is convenient if one\nwants to extract parameters from either the URL or HTTP template into the\nrequest fields and also want access to the raw HTTP body.\n\nExample:\n\n    message GetResourceRequest {\n      // A unique request id.\n      string request_id = 1;\n\n      // The raw HTTP body is bound to this field.\n      google.api.HttpBody http_body = 2;\n\n    }\n\n    service ResourceService {\n      rpc GetResource(GetResourceRequest)\n        returns (google.api.HttpBody);\n      rpc UpdateResource(google.api.HttpBody)\n        returns (google.protobuf.Empty);\n\n    }\n\nExample with streaming methods:\n\n    service CaldavService {\n      rpc GetCalendar(stream google.api.HttpBody)\n        returns (stream google.api.HttpBody);\n      rpc UpdateCalendar(stream google.api.HttpBody)\n        returns (stream google.api.HttpBody);\n\n    }\n\nUse of this type only changes how the request and response bodies are\nhandled, all other features will continue to work unchanged."
+    },
     "protobufAny": {
       "type": "object",
       "properties": {
@@ -620,6 +675,14 @@
         }
       }
     },
+    "v1ClustersGetKubeconfigResponse": {
+      "type": "object",
+      "properties": {
+        "kubeconfig": {
+          "type": "string"
+        }
+      }
+    },
     "v1ClustersGetResponse": {
       "type": "object",
       "properties": {
diff --git a/openapi/v3/openapi.yaml b/openapi/v3/openapi.yaml
index fb9c10a..71764b8 100644
--- a/openapi/v3/openapi.yaml
+++ b/openapi/v3/openapi.yaml
@@ -379,6 +379,38 @@ paths:
             application/json:
               schema:
                 $ref: "#/components/schemas/rpcStatus"
+  /api/fulfillment/v1/clusters/{cluster_id}/kubeconfig:
+    get:
+      tags:
+      - Clusters
+      summary: Returns the admin Kubeconfig of the cluster.
+      description: |-
+        This is intended for use with HTTP and returns the YAML text of the Kubeconfig directly using the content type
+        `application/yaml`.
+
+        buf:lint:ignore RPC_RESPONSE_STANDARD_NAME
+      operationId: Clusters_GetKubeconfigViaHttp
+      parameters:
+      - name: cluster_id
+        in: path
+        required: true
+        style: simple
+        explode: false
+        schema:
+          type: string
+      responses:
+        "200":
+          description: A successful response.
+          content:
+            application/json:
+              schema:
+                $ref: "#/components/schemas/apiHttpBody"
+        default:
+          description: An unexpected error response.
+          content:
+            application/json:
+              schema:
+                $ref: "#/components/schemas/rpcStatus"
   /api/fulfillment/v1/events:
     get:
       tags:
@@ -432,6 +464,69 @@ paths:
                 $ref: "#/components/schemas/rpcStatus"
 components:
   schemas:
+    apiHttpBody:
+      type: object
+      properties:
+        content_type:
+          type: string
+          description: The HTTP Content-Type header value specifying the content type
+            of the body.
+        data:
+          pattern: "^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$"
+          type: string
+          description: The HTTP request/response body as raw binary.
+          format: byte
+        extensions:
+          type: array
+          description: |-
+            Application specific response metadata. Must be set in the first response
+            for streaming APIs.
+          items:
+            $ref: "#/components/schemas/protobufAny"
+      description: |-
+        Message that represents an arbitrary HTTP body. It should only be used for
+        payload formats that can't be represented as JSON, such as raw binary or
+        an HTML page.
+
+
+        This message can be used both in streaming and non-streaming API methods in
+        the request as well as the response.
+
+        It can be used as a top-level request field, which is convenient if one
+        wants to extract parameters from either the URL or HTTP template into the
+        request fields and also want access to the raw HTTP body.
+
+        Example:
+
+            message GetResourceRequest {
+              // A unique request id.
+              string request_id = 1;
+
+              // The raw HTTP body is bound to this field.
+              google.api.HttpBody http_body = 2;
+
+            }
+
+            service ResourceService {
+              rpc GetResource(GetResourceRequest)
+                returns (google.api.HttpBody);
+              rpc UpdateResource(google.api.HttpBody)
+                returns (google.protobuf.Empty);
+
+            }
+
+        Example with streaming methods:
+
+            service CaldavService {
+              rpc GetCalendar(stream google.api.HttpBody)
+                returns (stream google.api.HttpBody);
+              rpc UpdateCalendar(stream google.api.HttpBody)
+                returns (stream google.api.HttpBody);
+
+            }
+
+        Use of this type only changes how the request and response bodies are
+        handled, all other features will continue to work unchanged.
     protobufAny:
       type: object
       properties:
@@ -733,6 +828,11 @@ components:
           description: List of results.
           items:
             $ref: "#/components/schemas/v1ClusterTemplate"
+    v1ClustersGetKubeconfigResponse:
+      type: object
+      properties:
+        kubeconfig:
+          type: string
     v1ClustersGetResponse:
       type: object
       properties:
diff --git a/proto/fulfillment/v1/clusters_service.proto b/proto/fulfillment/v1/clusters_service.proto
index ad639ea..f5832ef 100644
--- a/proto/fulfillment/v1/clusters_service.proto
+++ b/proto/fulfillment/v1/clusters_service.proto
@@ -17,6 +17,7 @@ package fulfillment.v1;
 
 import "fulfillment/v1/cluster_type.proto";
 import "google/api/annotations.proto";
+import "google/api/httpbody.proto";
 
 message ClustersListRequest {
   // Index of the first result. If not specified the default value will be zero.
@@ -73,6 +74,18 @@ message ClustersGetResponse {
   Cluster cluster = 1;
 }
 
+message ClustersGetKubeconfigRequest {
+  string cluster_id = 1;
+}
+
+message ClustersGetKubeconfigResponse {
+  string kubeconfig = 1;
+}
+
+message ClustersGetKubeconfigViaHttpRequest {
+  string cluster_id = 1;
+}
+
 service Clusters {
   // Retrieves the list of clusters.
   rpc List(ClustersListRequest) returns (ClustersListResponse) {
@@ -83,4 +96,20 @@ service Clusters {
   rpc Get(ClustersGetRequest) returns (ClustersGetResponse) {
     option (google.api.http) = {get: "/api/fulfillment/v1/clusters/{cluster_id}"};
   }
+
+  // Returns the admin Kubeconfig of the cluster.
+  //
+  // This intended for use with the gRPC protocol, and it isn't mapped to an HTTP endpoint. To retrieve the Kubeconfig
+  // via HTTP see the `ClustersGetKubeconfigViaHttp` method below.
+  rpc GetKubeconfig(ClustersGetKubeconfigRequest) returns (ClustersGetKubeconfigResponse) {}
+
+  // Returns the admin Kubeconfig of the cluster.
+  //
+  // This is intended for use with HTTP and returns the YAML text of the Kubeconfig directly using the content type
+  // `application/yaml`.
+  //
+  // buf:lint:ignore RPC_RESPONSE_STANDARD_NAME
+  rpc GetKubeconfigViaHttp(ClustersGetKubeconfigViaHttpRequest) returns (google.api.HttpBody) {
+    option (google.api.http) = {get: "/api/fulfillment/v1/clusters/{cluster_id}/kubeconfig"};
+  }
 }


Reviews:
user: login: coderabbitai[bot]
body: **Actionable comments posted: 1**

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 3358968b4d1184eeda057b03d3ffc8110da90a0c and 30a4361fde96c3b6243cb7513aa540ea1e20e41e.

</details>

<details>
<summary>📒 Files selected for processing (3)</summary>

* `openapi/v2/openapi.json` (3 hunks)
* `openapi/v3/openapi.yaml` (3 hunks)
* `proto/fulfillment/v1/clusters_service.proto` (3 hunks)

</details>

<details>
<summary>🔇 Additional comments (3)</summary><blockquote>

<details>
<summary>openapi/v2/openapi.json (1)</summary>

`357-387`: <details>
<summary>❓ Verification inconclusive</summary>

**New Kubeconfig Endpoint in Swagger v2 – Confirm Response Schema and Content-Type**  
The new endpoint is added correctly in the OpenAPI v2 JSON specification. As with the v3 document, the description indicates that the response should be YAML (`application/yaml`), but the response schema is under the default media type. Ensure that the actual endpoint response and definition align with this expectation or adjust the media type accordingly.

---



</details>

**Kubeconfig Endpoint Media Format Alignment Verification**  
The new endpoint in `openapi/v2/openapi.json` properly documents that the response should be in YAML (via the description noting `application/yaml`), but the 200 response is defined only through a generic schema reference (`#/definitions/apiHttpBody`). Please verify that:

- The actual service implementation returns YAML as described.
- The schema for `apiHttpBody` accurately represents the YAML output structure.
- If needed, consider explicitly setting the response media type (for example, using a global or per-operation `produces` field) to avoid ambiguity.

</details>
<details>
<summary>proto/fulfillment/v1/clusters_service.proto (2)</summary>

`77-88`: **New Message Types for Kubeconfig Retrieval – Clarity and Consistency**  
The new message types (`ClustersGetKubeconfigRequest`, `ClustersGetKubeconfigResponse`, and `ClustersGetKubeconfigViaHttpRequest`) are straightforward and consistent with the design goals. Please ensure that corresponding documentation mentions the intended usage (gRPC vs. HTTP) to avoid any ambiguity for API consumers.

---

`100-114`: **New RPC Methods – Verify HTTP Mapping and Lint Ignore Usage**  
The new RPC methods, `GetKubeconfig` (for gRPC) and `GetKubeconfigViaHttp` (for HTTP), are added as specified. The HTTP method is mapped with an appropriate option (`google.api.http`) and includes the lint ignore directive for RPC response standard naming. Ensure that the import of `google/api/httpbody.proto` is correctly resolved in your build environment, and that both methods behave as intended in their respective contexts.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-15T10:40:05Z
user: login: jhernand
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-17T11:06:08Z
user: login: coderabbitai[bot]
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-17T11:06:35Z
user: login: jhernand
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-17T11:08:35Z
user: login: coderabbitai[bot]
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-17T11:09:13Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 0**

<details>
<summary>🧹 Nitpick comments (1)</summary><blockquote>

<details>
<summary>openapi/v2/openapi.json (1)</summary><blockquote>

`678-685`: **New Schema Definition for Kubeconfig Response**  
The definition for `v1ClustersGetKubeconfigResponse` correctly introduces a schema with a single property, `kubeconfig`, of type string. For enhanced clarity, consider adding a descriptive message (for example, "The admin Kubeconfig in YAML format") to the `kubeconfig` property. This will help future readers understand the expected format of this field.  


*Proposed Diff:*
```diff
 "v1ClustersGetKubeconfigResponse": {
   "type": "object",
   "properties": {
     "kubeconfig": {
       "type": "string",
+      "description": "The admin Kubeconfig in YAML format."
     }
   }
 }
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 30a4361fde96c3b6243cb7513aa540ea1e20e41e and da24cfc9ca6e39739b3bf7d63eb7dff75d8415ba.

</details>

<details>
<summary>📒 Files selected for processing (3)</summary>

* `openapi/v2/openapi.json` (3 hunks)
* `openapi/v3/openapi.yaml` (3 hunks)
* `proto/fulfillment/v1/clusters_service.proto` (3 hunks)

</details>

<details>
<summary>🚧 Files skipped from review as they are similar to previous changes (1)</summary>

* proto/fulfillment/v1/clusters_service.proto

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🧠 Learnings (1)</summary>

<details>
<summary>openapi/v3/openapi.yaml (1)</summary>

```
Learnt from: jhernand
PR: innabox/fulfillment-api#19
File: openapi/v3/openapi.yaml:382-414
Timestamp: 2025-03-17T11:06:34.483Z
Learning: When using the HttpBody mechanism for gRPC to REST mapping in the fulfillment API, the server can send responses with any content type (like application/yaml) and will set the Content-Type header accordingly at runtime, even though the OpenAPI specification generated by protoc-gen-openapiv2 may show application/json in the documentation. The team plans to post-process the generated OpenAPI to fix these inconsistencies.
```

</details>

</details>

</details>

<details>
<summary>🔇 Additional comments (3)</summary><blockquote>

<details>
<summary>openapi/v2/openapi.json (1)</summary>

`357-388`: **Content-Type Specification Review**  
The new endpoint `/api/fulfillment/v1/clusters/{cluster_id}/kubeconfig` is defined to return the admin Kubeconfig. Note that its description clearly states that the endpoint returns YAML content (`application/yaml`), yet the response schema reference used is `apiHttpBody` (typically rendered under `application/json` by the generator). This discrepancy is expected because of the gRPC-to-REST mapping limitations. Please ensure that the runtime behavior sets the appropriate content type, and if possible, consider a post-processing step or note in the documentation for clarity.

</details>
<details>
<summary>openapi/v3/openapi.yaml (2)</summary>

`382-414`: **Endpoint Response Media Type Consistency**  
The newly introduced endpoint `/api/fulfillment/v1/clusters/{cluster_id}/kubeconfig` in the OpenAPI v3 document is intended for HTTP usage, returning YAML text as described. However, the response section documents the response under the media type `application/json` via the `apiHttpBody` schema. This is a known limitation of the protoc-gen-openapiv2 tool and is addressed by post-processing the generated spec. Please verify that the post-processing step or runtime behavior ultimately sets the `Content-Type` header to `application/yaml` as intended.

---

`467-490`: **`apiHttpBody` Schema Review**  
The `apiHttpBody` schema is defined with the properties `content_type`, `data`, and `extensions`. Its design—including the base64-encoded data pattern—is appropriate for representing arbitrary HTTP bodies (especially non-JSON payloads). No changes are required here, but please ensure that this schema meets all downstream needs for representing dynamic response types.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-18T17:56:36Z
user: login: larsks
state: APPROVED
author_association: MEMBER
submitted_at: 2025-03-18T18:14:16Z
user: login: larsks
body: Does OCM have a similar API? Or, perhaps a better question, what does the OCM API for retrieving cluster credentials  look like?
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-18T18:15:26Z

Review comments:
diff_hunk: @@ -379,6 +379,38 @@ paths:
             application/json:
               schema:
                 $ref: "#/components/schemas/rpcStatus"
+  /api/fulfillment/v1/clusters/{cluster_id}/kubeconfig:
+    get:
+      tags:
+      - Clusters
+      summary: Returns the admin Kubeconfig of the cluster.
+      description: |-
+        This is intended for use with HTTP and returns the YAML text of the Kubeconfig directly using the content type
+        `application/yaml`.
+
+        buf:lint:ignore RPC_RESPONSE_STANDARD_NAME
+      operationId: Clusters_GetKubeconfigViaHttp
+      parameters:
+      - name: cluster_id
+        in: path
+        required: true
+        style: simple
+        explode: false
+        schema:
+          type: string
+      responses:
+        "200":
+          description: A successful response.
+          content:
+            application/json:
+              schema:
+                $ref: "#/components/schemas/apiHttpBody"
+        default:
+          description: An unexpected error response.
+          content:
+            application/json:
+              schema:
+                $ref: "#/components/schemas/rpcStatus"
   /api/fulfillment/v1/events:
path: openapi/v3/openapi.yaml
user: login: coderabbitai[bot]
body: _💡 Verification agent_

<details>
<summary>❓ Verification inconclusive</summary>

**New Kubeconfig Endpoint – Verify Response Content-Type Consistency**  
The new GET endpoint for retrieving the admin Kubeconfig is clearly defined. However, note that the description emphasizes returning YAML text with a content type of `application/yaml`, while the response content is currently defined under `application/json`. Please verify that the intended response format is preserved in both the documentation and implementation.

---



</details>

**Content-Type Mismatch for Kubeconfig Retrieval Endpoint**

The description for the `/api/fulfillment/v1/clusters/{cluster_id}/kubeconfig` endpoint indicates that the GET call returns a YAML payload with a content type of `application/yaml`. However, the 200 response is currently documented to produce an `application/json` payload via the `#/components/schemas/apiHttpBody` reference. Please verify whether the endpoint is intended to return YAML or JSON, and update either the response schema or the description to ensure consistency.

<!-- This is an auto-generated reply by CodeRabbit -->
created_at: 2025-03-15T10:40:04Z
author_association: NONE
diff_hunk: @@ -379,6 +379,38 @@ paths:
             application/json:
               schema:
                 $ref: "#/components/schemas/rpcStatus"
+  /api/fulfillment/v1/clusters/{cluster_id}/kubeconfig:
+    get:
+      tags:
+      - Clusters
+      summary: Returns the admin Kubeconfig of the cluster.
+      description: |-
+        This is intended for use with HTTP and returns the YAML text of the Kubeconfig directly using the content type
+        `application/yaml`.
+
+        buf:lint:ignore RPC_RESPONSE_STANDARD_NAME
+      operationId: Clusters_GetKubeconfigViaHttp
+      parameters:
+      - name: cluster_id
+        in: path
+        required: true
+        style: simple
+        explode: false
+        schema:
+          type: string
+      responses:
+        "200":
+          description: A successful response.
+          content:
+            application/json:
+              schema:
+                $ref: "#/components/schemas/apiHttpBody"
+        default:
+          description: An unexpected error response.
+          content:
+            application/json:
+              schema:
+                $ref: "#/components/schemas/rpcStatus"
   /api/fulfillment/v1/events:
path: openapi/v3/openapi.yaml
user: login: jhernand
body: This is a side effect of the gRPC to REST mapping. When using the "HttpBody" mechanism the server can send any kind of response content, and will set the "Content-Type" header accordingly, but the "protoc-gen-openapiv2" tool that we use to generate the OpenAPI specification can't know that. We will probably need to post-process the generated OpenAPI to fix this and other inconsistencies.
created_at: 2025-03-17T11:06:08Z
author_association: CONTRIBUTOR
diff_hunk: @@ -379,6 +379,38 @@ paths:
             application/json:
               schema:
                 $ref: "#/components/schemas/rpcStatus"
+  /api/fulfillment/v1/clusters/{cluster_id}/kubeconfig:
+    get:
+      tags:
+      - Clusters
+      summary: Returns the admin Kubeconfig of the cluster.
+      description: |-
+        This is intended for use with HTTP and returns the YAML text of the Kubeconfig directly using the content type
+        `application/yaml`.
+
+        buf:lint:ignore RPC_RESPONSE_STANDARD_NAME
+      operationId: Clusters_GetKubeconfigViaHttp
+      parameters:
+      - name: cluster_id
+        in: path
+        required: true
+        style: simple
+        explode: false
+        schema:
+          type: string
+      responses:
+        "200":
+          description: A successful response.
+          content:
+            application/json:
+              schema:
+                $ref: "#/components/schemas/apiHttpBody"
+        default:
+          description: An unexpected error response.
+          content:
+            application/json:
+              schema:
+                $ref: "#/components/schemas/rpcStatus"
   /api/fulfillment/v1/events:
path: openapi/v3/openapi.yaml
user: login: coderabbitai[bot]
body: @jhernand Thank you for the clarification. I understand now - this is a limitation of the `protoc-gen-openapiv2` tool when handling the HttpBody mechanism, which allows dynamic content types at runtime but can't be properly reflected in the generated OpenAPI specification. Your approach to post-process the generated OpenAPI to correct these inconsistencies makes sense.

---

<details>
<summary>✏️ Learnings added</summary>

```
Learnt from: jhernand
PR: innabox/fulfillment-api#19
File: openapi/v3/openapi.yaml:382-414
Timestamp: 2025-03-17T11:06:34.381Z
Learning: When using the HttpBody mechanism for gRPC to REST mapping in the fulfillment API, the server can send responses with any content type (like application/yaml) and will set the Content-Type header accordingly at runtime, even though the OpenAPI specification generated by protoc-gen-openapiv2 may show application/json in the documentation. The team plans to post-process the generated OpenAPI to fix these inconsistencies.
```

> Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure [review instructions](https://docs.coderabbit.ai/guides/review-instructions). You can manage existing learnings in the [app](https://app.coderabbit.ai/learnings).

</details>

<!-- This is an auto-generated reply by CodeRabbit -->
created_at: 2025-03-17T11:06:35Z
author_association: NONE
diff_hunk: @@ -379,6 +379,38 @@ paths:
             application/json:
               schema:
                 $ref: "#/components/schemas/rpcStatus"
+  /api/fulfillment/v1/clusters/{cluster_id}/kubeconfig:
+    get:
+      tags:
+      - Clusters
+      summary: Returns the admin Kubeconfig of the cluster.
+      description: |-
+        This is intended for use with HTTP and returns the YAML text of the Kubeconfig directly using the content type
+        `application/yaml`.
+
+        buf:lint:ignore RPC_RESPONSE_STANDARD_NAME
+      operationId: Clusters_GetKubeconfigViaHttp
+      parameters:
+      - name: cluster_id
+        in: path
+        required: true
+        style: simple
+        explode: false
+        schema:
+          type: string
+      responses:
+        "200":
+          description: A successful response.
+          content:
+            application/json:
+              schema:
+                $ref: "#/components/schemas/apiHttpBody"
+        default:
+          description: An unexpected error response.
+          content:
+            application/json:
+              schema:
+                $ref: "#/components/schemas/rpcStatus"
   /api/fulfillment/v1/events:
path: openapi/v3/openapi.yaml
user: login: jhernand
body: @coderabbitai open a ticket in this project to explain that need to post-process the generated OpenAPI.
created_at: 2025-03-17T11:08:35Z
author_association: CONTRIBUTOR
diff_hunk: @@ -379,6 +379,38 @@ paths:
             application/json:
               schema:
                 $ref: "#/components/schemas/rpcStatus"
+  /api/fulfillment/v1/clusters/{cluster_id}/kubeconfig:
+    get:
+      tags:
+      - Clusters
+      summary: Returns the admin Kubeconfig of the cluster.
+      description: |-
+        This is intended for use with HTTP and returns the YAML text of the Kubeconfig directly using the content type
+        `application/yaml`.
+
+        buf:lint:ignore RPC_RESPONSE_STANDARD_NAME
+      operationId: Clusters_GetKubeconfigViaHttp
+      parameters:
+      - name: cluster_id
+        in: path
+        required: true
+        style: simple
+        explode: false
+        schema:
+          type: string
+      responses:
+        "200":
+          description: A successful response.
+          content:
+            application/json:
+              schema:
+                $ref: "#/components/schemas/apiHttpBody"
+        default:
+          description: An unexpected error response.
+          content:
+            application/json:
+              schema:
+                $ref: "#/components/schemas/rpcStatus"
   /api/fulfillment/v1/events:
path: openapi/v3/openapi.yaml
user: login: coderabbitai[bot]
body: <details>
<summary>🧩 Analysis chain</summary>

@jhernand I've created a new issue in the repository to track the need for post-processing the generated OpenAPI documentation. The issue details the background of the problem with the HttpBody mechanism in gRPC to REST mapping, provides the example from PR #19 with the Kubeconfig endpoint's content type inconsistency, and suggests implementing a post-processing step to fix these documentation issues.



---

🏁 Script executed:
```
#!/bin/bash
# Create a GitHub issue about the need to post-process OpenAPI documentation

gh issue create \
  --title "Post-process generated OpenAPI documentation to fix content type inconsistencies" \
  --body "## Background

When using the HttpBody mechanism for gRPC to REST mapping in the API, the server can send responses with any content type and will set the Content-Type header accordingly at runtime. However, the OpenAPI specification generated by protoc-gen-openapiv2 cannot correctly represent this dynamic behavior, always showing application/json in the documentation.

## Example

In PR #19, a new endpoint was added: \`/api/fulfillment/v1/clusters/{cluster_id}/kubeconfig\`

This endpoint returns YAML content with content type \`application/yaml\`, but the generated OpenAPI documentation shows it as returning \`application/json\`.

## Proposed Solution

We need to implement a post-processing step for the generated OpenAPI documentation to correct these inconsistencies, ensuring that the documentation accurately reflects the API's actual behavior.

This issue was identified in the following discussion: https://github.com/innabox/fulfillment-api/pull/19#discussion_r1996755246" \
  --assignee "jhernand"
```

Length of output: 1176

---



</details>

@jhernand I've created a new issue (#20) to track the need for post-processing the generated OpenAPI documentation. You can view it here: https://github.com/innabox/fulfillment-api/issues/20

The issue explains the background of the problem with HttpBody mechanism in gRPC to REST mapping, highlights the example from PR #19 with the Kubeconfig endpoint's content type inconsistency, and suggests implementing a post-processing step to fix these documentation issues.

---

<details>
<summary>✏️ Learnings added</summary>

```
Learnt from: jhernand
PR: innabox/fulfillment-api#19
File: openapi/v3/openapi.yaml:382-414
Timestamp: 2025-03-17T11:09:12.628Z
Learning: The team needs to post-process the OpenAPI documentation generated by protoc-gen-openapiv2 to fix content type inconsistencies, particularly for endpoints using HttpBody mechanism that return non-JSON content types (like application/yaml for the Kubeconfig endpoint).
```

---

```
Learnt from: jhernand
PR: innabox/fulfillment-api#19
File: openapi/v3/openapi.yaml:382-414
Timestamp: 2025-03-17T11:09:12.628Z
Learning: The team needs to post-process the OpenAPI documentation generated by protoc-gen-openapiv2 to fix content type inconsistencies, particularly for endpoints using HttpBody mechanism that return non-JSON content types (like application/yaml for the Kubeconfig endpoint).
```

> Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure [review instructions](https://docs.coderabbit.ai/guides/review-instructions). You can manage existing learnings in the [app](https://app.coderabbit.ai/learnings).

</details>

<!-- This is an auto-generated reply by CodeRabbit -->
created_at: 2025-03-17T11:09:13Z
author_association: NONE

PR comments:
user: login: coderabbitai[bot]
created_at: 2025-03-14T16:53:02Z
body: <!-- This is an auto-generated comment: summarize by coderabbit.ai -->
<!-- walkthrough_start -->

## Walkthrough

The changes introduce a new API endpoint designed to retrieve the admin Kubeconfig for a specified cluster. This endpoint is accessible via a GET request and returns the Kubeconfig in YAML format with a designated content type. Corresponding updates were made in both OpenAPI v2 and v3 specifications, adding schemas that detail the structure of the HTTP response, including properties like content type, data, and extensions. Additionally, a new response definition specifically encapsulates the Kubeconfig string. Parallel changes were implemented in the gRPC interface by introducing new message types and RPC methods. The gRPC methods provide both standard and HTTP-accessible ways to fetch the Kubeconfig, utilizing newly defined request and response messages, and include the necessary import for handling HTTP body responses.

<!-- walkthrough_end -->

<!-- announcements_start -->

> [!TIP]
> <details>
> <summary>⚡🧪 Multi-step agentic review comment chat (experimental)</summary>
> 
> - We're introducing multi-step agentic chat in review comments. This experimental feature enhances review discussions with the CodeRabbit agentic chat by enabling advanced interactions, including the ability to create pull requests directly from comments.
> 						- To enable this feature, set early_access to true under in the settings.
> 
> </details>

<!-- announcements_end -->
<!-- internal state start -->


<!-- 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 -->

<!-- internal state end -->
<!-- tips_start -->

---



<details>
<summary>🪧 Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=innabox/fulfillment-api&utm_content=19):

- Review comments: Directly reply to a review comment made by CodeRabbit. Example:
  - `I pushed a fix in commit <commit_id>, please review it.`
  - `Generate unit testing code for this file.`
  - `Open a follow-up GitHub issue for this discussion.`
- Files and specific lines of code (under the "Files changed" tab): Tag `@coderabbitai` in a new review comment at the desired location with your query. Examples:
  - `@coderabbitai generate unit testing code for this file.`
  -	`@coderabbitai modularize this function.`
- PR comments: Tag `@coderabbitai` in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
  - `@coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.`
  - `@coderabbitai read src/utils.ts and generate unit testing code.`
  - `@coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.`
  - `@coderabbitai help me debug CodeRabbit configuration file.`

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

### CodeRabbit Commands (Invoked using PR comments)

- `@coderabbitai pause` to pause the reviews on a PR.
- `@coderabbitai resume` to resume the paused reviews.
- `@coderabbitai review` to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
- `@coderabbitai full review` to do a full review from scratch and review all the files again.
- `@coderabbitai summary` to regenerate the summary of the PR.
- `@coderabbitai generate docstrings` to [generate docstrings](https://docs.coderabbit.ai/finishing-touches/docstrings) for this PR.
- `@coderabbitai resolve` resolve all the CodeRabbit review comments.
- `@coderabbitai configuration` to show the current CodeRabbit configuration for the repository.
- `@coderabbitai help` to get help.

### Other keywords and placeholders

- Add `@coderabbitai ignore` anywhere in the PR description to prevent this PR from being reviewed.
- Add `@coderabbitai summary` to generate the high-level summary at a specific location in the PR description.
- Add `@coderabbitai` anywhere in the PR title to generate the title automatically.

### CodeRabbit Configuration File (`.coderabbit.yaml`)

- You can programmatically configure CodeRabbit by adding a `.coderabbit.yaml` file to the root of your repository.
- Please see the [configuration documentation](https://docs.coderabbit.ai/guides/configure-coderabbit) for more information.
- If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: `# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json`

### Documentation and Community

- Visit our [Documentation](https://docs.coderabbit.ai) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](http://discord.gg/coderabbit) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->
author_association: NONE
user: login: jhernand
created_at: 2025-03-19T09:06:46Z
body: > Does OCM have a similar API? Or, perhaps a better question, what does the OCM API for retrieving cluster credentials look like?

OCM has a similar endpoint for retrieving credentials:

https://api.openshift.com/#/default/get_api_clusters_mgmt_v1_clusters__cluster_id__credentials

That is intended for very privileged users: the owner of the cluster.

For regular users it has the capability to create identity providers via the API (see https://api.openshift.com/#/default/post_api_clusters_mgmt_v1_clusters__cluster_id__identity_providers) so that the owner of the cluster can decide how to authenticate and grant access to users.

Assisted installer also has a similar endpoint: https://github.com/openshift/assisted-service/blob/c498e121260df0f580fadbf37316a977935eb17e/swagger.yaml#L2108-L2165

author_association: CONTRIBUTOR