Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions .github/ISSUE_TEMPLATE/bug_report.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ name: Bug Report
about: Report a bug to help us improve
title: "[BUG] "
labels: bug
assignees: ''
assignees: ""
---

## Describe the Bug
Expand All @@ -12,9 +12,9 @@ assignees: ''

## Steps to Reproduce

1.
2.
3.
1.
2.
3.

## Expected Behavior

Expand All @@ -26,9 +26,9 @@ assignees: ''

## Environment

- OS:
- Version:
- Go version (if applicable):
- OS:
- Version:
- Go version (if applicable):

## Additional Context

Expand Down
2 changes: 1 addition & 1 deletion .github/ISSUE_TEMPLATE/feature_request.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ name: Feature Request
about: Suggest an idea for this project
title: "[FEATURE] "
labels: enhancement
assignees: ''
assignees: ""
---

## Problem Statement
Expand Down
49 changes: 32 additions & 17 deletions clientset/docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,11 +102,11 @@ func Install(scheme *runtime.Scheme) {

The platform API differs from a standard Kubernetes API in three ways that require hand-written code:

| Difference | Solution |
|---|---|
| Requests are signed with AWS SigV4 | `transport/sigv4.go` — custom RoundTripper |
| Resources are account-scoped, not namespace-scoped | SigV4 transport extracts the Kubernetes namespace from the URL, maps it to `X-Amz-Account-Id`, and strips the `/namespaces/{ns}/` segment |
| Wire format is flat JSON, not Kubernetes nested metadata | `transport/bridge.go` — request/response adapter |
| Difference | Solution |
| -------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
| Requests are signed with AWS SigV4 | `transport/sigv4.go` — custom RoundTripper |
| Resources are account-scoped, not namespace-scoped | SigV4 transport extracts the Kubernetes namespace from the URL, maps it to `X-Amz-Account-Id`, and strips the `/namespaces/{ns}/` segment |
| Wire format is flat JSON, not Kubernetes nested metadata | `transport/bridge.go` — request/response adapter |

### `rest/config.go` — SDK configuration

Expand Down Expand Up @@ -137,18 +137,24 @@ The `Adapter` RoundTripper handles four transformations:
**Response rewriting** — platform API returns flat JSON objects:

```json
{"id": "abc-123", "name": "my-cluster", "resource_version": "1", "spec": {}, "status": {}}
{
"id": "abc-123",
"name": "my-cluster",
"resource_version": "1",
"spec": {},
"status": {}
}
```

The Kubernetes decoder populates `v1alpha1.Cluster` from `metadata.*` fields. The adapter rewrites each response before the decoder sees it:

| Wire field | → | Kubernetes field |
|---|---|---|
| `name` | → | `metadata.name` |
| `id` | → | `metadata.uid` |
| `resource_version` | → | `metadata.resourceVersion` |
| `generation` | → | `metadata.generation` |
| `spec`, `status`, … | → | unchanged |
| Wire field | → | Kubernetes field |
| ------------------- | --- | -------------------------- |
| `name` | → | `metadata.name` |
| `id` | → | `metadata.uid` |
| `resource_version` | → | `metadata.resourceVersion` |
| `generation` | → | `metadata.generation` |
| `spec`, `status`, … | → | unchanged |

Both single-object and list (`{"items": [...]}`) responses are handled.

Expand All @@ -159,15 +165,24 @@ Both single-object and list (`{"items": [...]}`) responses are handled.
**Error response translation** — platform API errors use a different envelope from `metav1.Status`:

```json
{"kind": "Error", "code": "CLUSTERS-MGMT-001", "reason": "account not authorized"}
{
"kind": "Error",
"code": "CLUSTERS-MGMT-001",
"reason": "account not authorized"
}
```

client-go's `transformResponse` cannot parse this format and falls back to `StatusReasonUnknown`, silently discarding the server's error message. The adapter intercepts any non-2xx response that matches the platform envelope and rewrites it to a minimal `metav1.Status` JSON body before client-go sees it:

```json
{"apiVersion": "v1", "kind": "Status", "status": "Failure",
"message": "CLUSTERS-MGMT-001: account not authorized",
"reason": "Forbidden", "code": 403}
{
"apiVersion": "v1",
"kind": "Status",
"status": "Failure",
"message": "CLUSTERS-MGMT-001: account not authorized",
"reason": "Forbidden",
"code": 403
}
```

This ensures `k8s.io/apimachinery/pkg/api/errors` helpers (`IsNotFound`, `IsForbidden`, etc.) classify errors correctly and that callers receive the full server message rather than a generic unknown error.
Expand Down
10 changes: 5 additions & 5 deletions docs/api/api-management.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,11 +115,11 @@ type ClusterSpec struct {
}
```

| Write Mode | On Create (POST) | On Update (PUT/PATCH) |
| ---------- | ----------------- | --------------------- |
| **mutable** | Customer can set | Customer can change |
| **immutable** | Customer can set | Rejected if changed |
| **service-set** | Platform fills it in | Rejected if present |
| Write Mode | On Create (POST) | On Update (PUT/PATCH) |
| --------------- | -------------------- | --------------------- |
| **mutable** | Customer can set | Customer can change |
| **immutable** | Customer can set | Rejected if changed |
| **service-set** | Platform fills it in | Rejected if present |

### Marker 3: Feature Gate (`+openshift:enable:FeatureGate=X`)

Expand Down
26 changes: 13 additions & 13 deletions docs/api/passthrough-design.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,12 @@ The passthrough codegen pipeline generates Go struct types that mirror upstream

## File roles

| File | Role |
|------|------|
| `api/v1alpha1/zz_generated.passthrough.go` | The committed passthrough types. Human-curated markers live here. This is the source of truth for field policy. Despite the `zz_generated` prefix, this file is intentionally hand-edited to curate markers, then regenerated to pick up upstream struct changes. |
| `api/v1alpha1/configuration.go` | Local mirror of `ClusterConfiguration` with granular markers on nested fields (kubelet, machineConfig). Used by the scanner for nested field marker extraction. |
| `hack/api-codegen/pkg/registry/field_metadata.json` | Generated field registry (JSON). Produced by `marker-scanner` from the passthrough file. Consumed by `passthrough-gen`, `conversion-gen`, and `openapi-gen`. |
| `hack/api-codegen/pkg/registry/field_metadata.go` | Generated field registry (Go). Same data as the JSON, importable by Go code. |
| File | Role |
| --------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `api/v1alpha1/zz_generated.passthrough.go` | The committed passthrough types. Human-curated markers live here. This is the source of truth for field policy. Despite the `zz_generated` prefix, this file is intentionally hand-edited to curate markers, then regenerated to pick up upstream struct changes. |
| `api/v1alpha1/configuration.go` | Local mirror of `ClusterConfiguration` with granular markers on nested fields (kubelet, machineConfig). Used by the scanner for nested field marker extraction. |
| `hack/api-codegen/pkg/registry/field_metadata.json` | Generated field registry (JSON). Produced by `marker-scanner` from the passthrough file. Consumed by `passthrough-gen`, `conversion-gen`, and `openapi-gen`. |
| `hack/api-codegen/pkg/registry/field_metadata.go` | Generated field registry (Go). Same data as the JSON, importable by Go code. |

## Pipeline

Expand Down Expand Up @@ -84,12 +84,12 @@ If HyperShift removes a field from `HostedClusterSpec` or `NodePoolSpec`, `make

The registry captures the following marker categories from the passthrough file:

| Marker | Registry field | Purpose |
|--------|---------------|---------|
| `+k8s:openapi-gen=false` | `hidden: true` | Field excluded from public OpenAPI and REST types |
| `+hyperfleet:write-mode=mutable\|immutable\|service-set` | `writeMode` | Controls customer mutability |
| `+openshift:enable:FeatureGate=X` | `featureGate` | Field gated behind a feature flag |
| `+hyperfleet:validation:FeatureGateAwareWriteMode:...` | `featureGateAwareWriteModes` | Write-mode varies by active feature gates |
| Marker | Registry field | Purpose |
| -------------------------------------------------------- | ---------------------------- | ------------------------------------------------- |
| `+k8s:openapi-gen=false` | `hidden: true` | Field excluded from public OpenAPI and REST types |
| `+hyperfleet:write-mode=mutable\|immutable\|service-set` | `writeMode` | Controls customer mutability |
| `+openshift:enable:FeatureGate=X` | `featureGate` | Field gated behind a feature flag |
| `+hyperfleet:validation:FeatureGateAwareWriteMode:...` | `featureGateAwareWriteModes` | Write-mode varies by active feature gates |

Upstream markers like `+optional` and `+required` are propagated directly from HyperShift source by `passthrough-gen` via `isForwardedMarker()` — they do not go through the registry.

Expand All @@ -104,7 +104,7 @@ This ensures new upstream fields don't accidentally become visible or mutable.

## Resolved gaps

The following issues existed in main and were fixed on this branch:
The following issues existed in main and were fixed:

1. **Stale embedded registry** (fixed): `passthrough-gen` embedded a copy of `field_metadata.json` via `//go:embed`. Removed the embedded copy and made `-registry` mandatory.

Expand Down
Loading