Skip to content

Commit 191a3d7

Browse files
committed
HYPERFLEET-1083 feat: add generic Resource type and Channel/Version spec schemas
1 parent aeb51fc commit 191a3d7

18 files changed

Lines changed: 3692 additions & 401 deletions

CHANGELOG.md

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,24 @@
22

33
All notable changes to the HyperFleet API specification will be documented in this file.
44

5-
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
5+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.16/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

88
## [Unreleased]
99

10+
## [1.0.16] - 2026-05-19
11+
12+
### Added
13+
14+
- Generic `Resource` type with `kind` discriminator and JSONB `spec` field, replacing per-entity model hierarchies for new resource types (HYPERFLEET-1083)
15+
- `ResourceCreateRequest`, `ResourcePatchRequest`, `ResourceList`, `ResourceStatus` types in core contract
16+
- Generic `/resources` CRUD routes in core contract (GET list, GET by ID, POST, PATCH, DELETE) per design doc Section 3.2
17+
- `GET /resources/{id}/statuses` route for adapter status listing
18+
- `references` field on Resource for non-ownership associations between entities (Section 9)
19+
- `ChannelSpec` validation schema in GCP contract (`is_default`, `enabled_regex`)
20+
- `VersionSpec` validation schema in GCP contract (`raw_version`, `enabled`, `is_default`, `release_image`, `end_of_life_time`)
21+
- `KindChannel` and `KindVersion` kind aliases in GCP contract
22+
1023
## [1.0.15] - 2026-05-18
1124

1225
### Added
@@ -146,7 +159,8 @@ First official stable release of the HyperFleet API specification.
146159
- Interactive API documentation
147160

148161
<!-- Links -->
149-
[Unreleased]: https://github.com/openshift-hyperfleet/hyperfleet-api-spec/compare/v1.0.15...HEAD
162+
[Unreleased]: https://github.com/openshift-hyperfleet/hyperfleet-api-spec/compare/v1.0.16...HEAD
163+
[1.0.16]: https://github.com/openshift-hyperfleet/hyperfleet-api-spec/compare/v1.0.15...v1.0.16
150164
[1.0.15]: https://github.com/openshift-hyperfleet/hyperfleet-api-spec/compare/v1.0.14...v1.0.15
151165
[1.0.14]: https://github.com/openshift-hyperfleet/hyperfleet-api-spec/compare/v1.0.13...v1.0.14
152166
[1.0.13]: https://github.com/openshift-hyperfleet/hyperfleet-api-spec/compare/v1.0.12...v1.0.13

aliases-gcp.tsp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,7 @@ import "./models-gcp/nodepool/model.tsp";
66
import "./models-gcp/nodepool/example_nodepool.tsp";
77
import "./models-gcp/nodepool/example_post.tsp";
88
import "./models-gcp/nodepool/example_patch.tsp";
9+
import "./models-gcp/channel/model.tsp";
10+
import "./models-gcp/version/model.tsp";
11+
import "./services/channels.tsp";
12+
import "./services/versions.tsp";

aliases.tsp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
aliases-gcp.tsp
1+
aliases-core.tsp

main.tsp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import "@typespec/openapi3";
55
import "./services/clusters.tsp";
66
import "./services/statuses.tsp";
77
import "./services/nodepools.tsp";
8+
import "./services/resources.tsp";
89
// Provider-specific security is imported via aliases.tsp
910
import "./aliases.tsp";
1011

@@ -21,7 +22,7 @@ using OpenAPI;
2122
*/
2223
@service(#{ title: "HyperFleet API" })
2324
@info(#{
24-
version: "1.0.15",
25+
version: "1.0.16",
2526
contact: #{
2627
name: "HyperFleet Team",
2728
url: "https://github.com/openshift-hyperfleet",

models-gcp/channel/model.tsp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
alias KindChannel = "Channel";
2+
3+
/** Spec schema for Channel entities, validated at runtime via EntityDescriptor.SpecSchemaName */
4+
model ChannelSpec {
5+
/** Whether this is the default channel for new clusters */
6+
is_default: boolean;
7+
8+
/** Regex pattern for matching enabled version strings */
9+
enabled_regex: string;
10+
}

models-gcp/version/model.tsp

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import "@typespec/http";
2+
3+
using Http;
4+
5+
alias KindVersion = "Version";
6+
7+
/** Spec schema for Version entities, validated at runtime via EntityDescriptor.SpecSchemaName */
8+
model VersionSpec {
9+
/** Raw version string (e.g., "4.17.12") */
10+
raw_version: string;
11+
12+
/** Whether this version is enabled for provisioning */
13+
enabled: boolean;
14+
15+
/** Whether this is the default version for its channel */
16+
is_default: boolean;
17+
18+
/** Container image reference for the release */
19+
release_image: string;
20+
21+
/** When this version reaches end of life */
22+
@format("date-time") end_of_life_time?: string;
23+
}

models/resource/example_patch.tsp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import "./model.tsp";
2+
3+
const exampleResourcePatchRequest: ResourcePatchRequest = #{
4+
spec: #{},
5+
labels: #{ env: "staging" },
6+
};

models/resource/example_post.tsp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import "./model.tsp";
2+
3+
const exampleResourceCreateRequest: ResourceCreateRequest = #{
4+
kind: "MyResource",
5+
name: "my-resource-1",
6+
spec: #{},
7+
labels: #{ environment: "production", team: "platform" },
8+
};
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import "./model.tsp";
2+
import "../common/model.tsp";
3+
4+
const exampleResource: Resource = #{
5+
kind: "MyResource",
6+
id: "019466a0-8f8e-7abc-9def-0123456789ab",
7+
href: "/api/hyperfleet/v1/myresources/019466a0-8f8e-7abc-9def-0123456789ab",
8+
name: "my-resource-1",
9+
labels: #{ environment: "production", team: "platform" },
10+
spec: #{},
11+
generation: 1,
12+
status: #{
13+
conditions: #[],
14+
},
15+
created_time: "2025-06-01T00:00:00Z",
16+
updated_time: "2025-06-01T10:02:00Z",
17+
created_by: "user-123@example.com",
18+
updated_by: "user-123@example.com",
19+
};

models/resource/model.tsp

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
import "@typespec/openapi";
2+
import "../common/model.tsp";
3+
import "../statuses/model.tsp";
4+
import "./example_resource.tsp";
5+
import "./example_post.tsp";
6+
import "./example_patch.tsp";
7+
8+
using OpenAPI;
9+
10+
/**
11+
* Status computed from adapter condition reports.
12+
*
13+
* For adapter-managed resources (Cluster, NodePool), conditions are populated
14+
* by the status aggregation pipeline. For non-adapter resources (Channel, Version),
15+
* conditions array is empty.
16+
*/
17+
model ResourceStatus {
18+
conditions: ResourceCondition[];
19+
}
20+
21+
@example(exampleResource)
22+
model Resource {
23+
/** Resource identifier (UUID v7) */
24+
id: Identifier;
25+
26+
/** Entity type discriminator (e.g., "Cluster", "Channel", "Version") */
27+
kind: string;
28+
29+
/** Resource name (unique per kind, or per kind+owner for child entities) */
30+
@minLength(1)
31+
@maxLength(253)
32+
name: string;
33+
34+
/** Resource URI (computed at creation time) */
35+
href?: string;
36+
37+
/** Entity-specific payload, validated at runtime against the entity's spec schema */
38+
spec: Record<unknown>;
39+
40+
/** Key-value pairs for filtering and grouping */
41+
labels?: Record<string>;
42+
43+
status: ResourceStatus;
44+
45+
/** Incremented on spec or label changes */
46+
@minValue(1)
47+
generation: int32;
48+
49+
/** Parent resource reference (present only on child entities) */
50+
owner_references?: ObjectReference;
51+
52+
/** Non-ownership associations to other resources, keyed by reference type */
53+
references?: Record<ObjectReference[]>;
54+
55+
...APIMetadata;
56+
}
57+
58+
@example(exampleResourceCreateRequest)
59+
model ResourceCreateRequest {
60+
/** Entity type discriminator */
61+
kind: string;
62+
63+
/** Resource name */
64+
@minLength(1)
65+
@maxLength(253)
66+
name: string;
67+
68+
/** Entity-specific payload */
69+
spec: Record<unknown>;
70+
71+
/** Key-value pairs for filtering and grouping */
72+
labels?: Record<string>;
73+
74+
/** Non-ownership associations to other resources */
75+
references?: Record<ObjectReference[]>;
76+
}
77+
78+
@extension("minProperties", 1)
79+
@extension("additionalProperties", false)
80+
@example(exampleResourcePatchRequest)
81+
model ResourcePatchRequest {
82+
spec?: Record<unknown>;
83+
labels?: Record<string>;
84+
references?: Record<ObjectReference[]>;
85+
}
86+
87+
model ResourceList {
88+
kind: string;
89+
page: int32;
90+
size: int32;
91+
total: int64;
92+
items: Resource[];
93+
}

0 commit comments

Comments
 (0)