Skip to content
Merged
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
21 changes: 20 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,24 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [1.0.17] - 2026-05-21

### Added

- Generic `Resource` type with `kind` discriminator and JSONB `spec` field, replacing per-entity model hierarchies for new resource types (HYPERFLEET-1083)
- `ResourceCreateRequest`, `ResourcePatchRequest`, `ResourceList`, `ResourceStatus` types in core contract
- Generic `/resources` CRUD routes in core contract (GET list, GET by ID, POST, PATCH, DELETE) per design doc Section 3.2
- `/channels` and `/channels/{channel_id}/versions` CRUD routes in GCP contract
- `references` field on Resource for non-ownership associations between entities (Section 9)
- `ChannelSpec` validation schema in GCP contract (`is_default`, `enabled_regex`)
- `VersionSpec` validation schema in GCP contract (`raw_version`, `enabled`, `is_default`, `release_image`, `end_of_life_time`)
- `KindChannel` and `KindVersion` kind aliases in GCP contract
Comment thread
coderabbitai[bot] marked this conversation as resolved.

### Changed

- `GET /resources/{id}/statuses` and `POST /resources/{id}/force-delete` moved to core contract
- `GET /clusters/{id}/statuses` and `GET /nodepools/{id}/statuses` moved to core contract

## [1.0.16] - 2026-05-20

### Added
Expand Down Expand Up @@ -152,7 +170,8 @@ First official stable release of the HyperFleet API specification.
- Interactive API documentation

<!-- Links -->
[Unreleased]: https://github.com/openshift-hyperfleet/hyperfleet-api-spec/compare/v1.0.16...HEAD
[Unreleased]: https://github.com/openshift-hyperfleet/hyperfleet-api-spec/compare/v1.0.17...HEAD
[1.0.17]: https://github.com/openshift-hyperfleet/hyperfleet-api-spec/compare/v1.0.16...v1.0.17
[1.0.16]: https://github.com/openshift-hyperfleet/hyperfleet-api-spec/compare/v1.0.15...v1.0.16
[1.0.15]: https://github.com/openshift-hyperfleet/hyperfleet-api-spec/compare/v1.0.14...v1.0.15
[1.0.14]: https://github.com/openshift-hyperfleet/hyperfleet-api-spec/compare/v1.0.13...v1.0.14
Expand Down
1 change: 1 addition & 0 deletions aliases-core.tsp
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@ import "./models-core/nodepool/model.tsp";
import "./models-core/nodepool/example_nodepool.tsp";
import "./models-core/nodepool/example_post.tsp";
import "./models-core/nodepool/example_patch.tsp";
import "./services/statuses.tsp";
import "./services/statuses-internal.tsp";
import "./services/force-delete-internal.tsp";
10 changes: 10 additions & 0 deletions aliases-gcp.tsp
Comment thread
kuudori marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,13 @@ import "./models-gcp/nodepool/model.tsp";
import "./models-gcp/nodepool/example_nodepool.tsp";
import "./models-gcp/nodepool/example_post.tsp";
import "./models-gcp/nodepool/example_patch.tsp";
import "./models-gcp/channel/model.tsp";
import "./models-gcp/channel/example_channel.tsp";
import "./models-gcp/channel/example_post.tsp";
import "./models-gcp/channel/example_patch.tsp";
import "./models-gcp/version/model.tsp";
import "./models-gcp/version/example_version.tsp";
import "./models-gcp/version/example_post.tsp";
import "./models-gcp/version/example_patch.tsp";
import "./services/channels.tsp";
import "./services/versions.tsp";
4 changes: 2 additions & 2 deletions main.tsp
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import "@typespec/openapi";
import "@typespec/openapi3";

import "./services/clusters.tsp";
import "./services/statuses.tsp";
import "./services/nodepools.tsp";
import "./services/resources.tsp";
// Provider-specific security is imported via aliases.tsp
import "./aliases.tsp";

Expand All @@ -21,7 +21,7 @@ using OpenAPI;
*/
@service(#{ title: "HyperFleet API" })
@info(#{
version: "1.0.16",
version: "1.0.17",
contact: #{
name: "HyperFleet Team",
url: "https://github.com/openshift-hyperfleet",
Expand Down
22 changes: 22 additions & 0 deletions models-gcp/channel/example_channel.tsp
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import "./model.tsp";
import "../../models/common/model.tsp";

const exampleChannel: Channel = #{
kind: "Channel",
id: "019466a2-1234-7abc-9def-0123456789ab",
href: "/api/hyperfleet/v1/channels/019466a2-1234-7abc-9def-0123456789ab",
name: "stable",
labels: #{ tier: "production" },
spec: #{
is_default: true,
enabled_regex: "^4\\.\\d+\\.\\d+$",
},
generation: 1,
status: #{
conditions: #[],
},
created_time: "2025-06-01T00:00:00Z",
updated_time: "2025-06-01T10:02:00Z",
created_by: "user-123@example.com",
updated_by: "user-123@example.com",
};
9 changes: 9 additions & 0 deletions models-gcp/channel/example_patch.tsp
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import "./model.tsp";

const exampleChannelPatchRequest: ChannelPatchRequest = #{
spec: #{
is_default: false,
enabled_regex: "^4\\.17\\.\\d+$",
},
labels: #{ tier: "staging" },
};
11 changes: 11 additions & 0 deletions models-gcp/channel/example_post.tsp
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import "./model.tsp";

const exampleChannelCreateRequest: ChannelCreateRequest = #{
kind: "Channel",
name: "stable",
labels: #{ tier: "production" },
spec: #{
is_default: true,
enabled_regex: "^4\\.\\d+\\.\\d+$",
},
};
55 changes: 55 additions & 0 deletions models-gcp/channel/model.tsp
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import "@typespec/openapi";
import "../../models/common/model.tsp";
import "../../models/statuses/model.tsp";
import "../../models/resource/model.tsp";

using OpenAPI;

alias KindChannel = "Channel";

model ChannelSpec {
/** Whether this is the default channel for new clusters */
is_default: boolean;

/** Regex pattern for matching enabled version strings */
enabled_regex: string;
}

model ChannelBase {
...APIResource;

@minLength(1)
@maxLength(63)
@pattern("^[a-z0-9]([-a-z0-9]*[a-z0-9])?$")
name: string;

spec: ChannelSpec;
}
Comment thread
kuudori marked this conversation as resolved.

@example(exampleChannel)
model Channel {
...ChannelBase;
...APIMetadata;

@minValue(1)
generation: int32;

status: ResourceStatus;
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

@example(exampleChannelCreateRequest)
model ChannelCreateRequest {
...ChannelBase;
}

@extension("minProperties", 1)
@extension("additionalProperties", false)
@example(exampleChannelPatchRequest)
model ChannelPatchRequest {
spec?: ChannelSpec;
labels?: Record<string>;
Comment thread
kuudori marked this conversation as resolved.
}

model ChannelList {
...List<Channel>;
}
11 changes: 11 additions & 0 deletions models-gcp/version/example_patch.tsp
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import "./model.tsp";

const exampleVersionPatchRequest: VersionPatchRequest = #{
spec: #{
raw_version: "4.17.12",
enabled: false,
is_default: false,
release_image: "quay.io/openshift-release-dev/ocp-release:4.17.12-multi",
},
labels: #{ deprecated: "true" },
};
13 changes: 13 additions & 0 deletions models-gcp/version/example_post.tsp
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import "./model.tsp";

const exampleVersionCreateRequest: VersionCreateRequest = #{
kind: "Version",
name: "4.17.12",
labels: #{},
spec: #{
raw_version: "4.17.12",
enabled: true,
is_default: true,
release_image: "quay.io/openshift-release-dev/ocp-release:4.17.12-multi",
},
};
29 changes: 29 additions & 0 deletions models-gcp/version/example_version.tsp
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import "./model.tsp";
import "../../models/common/model.tsp";

const exampleVersion: Version = #{
kind: "Version",
id: "019466a3-5678-7abc-9def-0123456789ab",
href: "/api/hyperfleet/v1/channels/019466a2-1234-7abc-9def-0123456789ab/versions/019466a3-5678-7abc-9def-0123456789ab",
name: "4.17.12",
labels: #{},
spec: #{
raw_version: "4.17.12",
enabled: true,
is_default: true,
release_image: "quay.io/openshift-release-dev/ocp-release:4.17.12-multi",
},
generation: 1,
owner_references: #{
id: "019466a2-1234-7abc-9def-0123456789ab",
kind: "Channel",
href: "/api/hyperfleet/v1/channels/019466a2-1234-7abc-9def-0123456789ab",
},
status: #{
conditions: #[],
},
created_time: "2025-06-01T00:00:00Z",
updated_time: "2025-06-01T10:02:00Z",
created_by: "user-123@example.com",
updated_by: "user-123@example.com",
};
67 changes: 67 additions & 0 deletions models-gcp/version/model.tsp
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import "@typespec/openapi";
import "@typespec/http";
import "../../models/common/model.tsp";
import "../../models/statuses/model.tsp";
import "../../models/resource/model.tsp";

using OpenAPI;
using Http;

alias KindVersion = "Version";

model VersionSpec {
/** Raw version string (e.g., "4.17.12") */
raw_version: string;

/** Whether this version is enabled for provisioning */
enabled: boolean;

/** Whether this is the default version for its channel */
is_default: boolean;

/** Container image reference for the release */
release_image: string;

/** When this version reaches end of life */
@format("date-time") end_of_life_time?: string;
}

model VersionBase {
...APIResource;

@minLength(1)
@maxLength(63)
name: string;

spec: VersionSpec;
}
Comment on lines +10 to +37
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Enforce kind: "Version" in the Version contract.

KindVersion is declared but not applied, so VersionBase/VersionCreateRequest can still carry a non-Version discriminator. That weakens schema-level guarantees for /channels/{channel_id}/versions and can cause client-side contract drift.

As per coding guidelines, Validate changes against HyperFleet architecture standards from the linked architecture repository.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@models-gcp/version/model.tsp` around lines 10 - 37, The model lacks an
enforced discriminator for KindVersion, so add a required, constant `kind:
KindVersion` property to the Version contract (ensure it is present on
VersionBase and any request models like VersionCreateRequest) so the
discriminator is fixed to "Version"; update VersionBase to include a
`@const`/literal `kind` field typed as KindVersion (or equivalent schema
annotation supported by the TSP generator) and ensure VersionCreateRequest and
any other derived/consumer models carry the same required `kind` property to
guarantee schema-level discrimination.


@example(exampleVersion)
model Version {
...VersionBase;
...APIMetadata;

@minValue(1)
generation: int32;

owner_references: ObjectReference;

status: ResourceStatus;
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

@example(exampleVersionCreateRequest)
model VersionCreateRequest {
...VersionBase;
}

@extension("minProperties", 1)
@extension("additionalProperties", false)
@example(exampleVersionPatchRequest)
model VersionPatchRequest {
spec?: VersionSpec;
labels?: Record<string>;
Comment thread
kuudori marked this conversation as resolved.
}

model VersionList {
...List<Version>;
}
6 changes: 6 additions & 0 deletions models/resource/example_patch.tsp
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import "./model.tsp";

const exampleResourcePatchRequest: ResourcePatchRequest = #{
spec: #{},
labels: #{ env: "staging" },
};
8 changes: 8 additions & 0 deletions models/resource/example_post.tsp
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import "./model.tsp";

const exampleResourceCreateRequest: ResourceCreateRequest = #{
kind: "MyResource",
name: "my-resource-1",
spec: #{},
labels: #{ environment: "production", team: "platform" },
};
19 changes: 19 additions & 0 deletions models/resource/example_resource.tsp
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import "./model.tsp";
import "../common/model.tsp";

const exampleResource: Resource = #{
kind: "MyResource",
id: "019466a0-8f8e-7abc-9def-0123456789ab",
href: "/api/hyperfleet/v1/resources/019466a0-8f8e-7abc-9def-0123456789ab",
name: "my-resource-1",
labels: #{ environment: "production", team: "platform" },
spec: #{},
generation: 1,
status: #{
conditions: #[],
},
created_time: "2025-06-01T00:00:00Z",
updated_time: "2025-06-01T10:02:00Z",
created_by: "user-123@example.com",
updated_by: "user-123@example.com",
};
Loading