Skip to content
Draft
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
75 changes: 61 additions & 14 deletions api/catalog/v1alpha1/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ paths:
type: string
description: |
Filter catalog items by service type.
Only returns items where spec.service_type matches this value.
Returns items where any resource's service_type matches.
example: vm

responses:
Expand Down Expand Up @@ -301,7 +301,8 @@ paths:
description: |
Updates specific fields of a catalog item using JSON Merge Patch (RFC 7396).

Note that api_version and spec.service_type are immutable after creation.
Note that api_version and resource structure (resource names,
service types, requires_resources) are immutable after creation.
parameters:
- $ref: '#/components/parameters/CatalogItemIdPath'

Expand Down Expand Up @@ -775,24 +776,62 @@ components:
CatalogItemSpec:
type: object
description: |
Specification for a catalog item, defining the service type reference
and field configurations.
Specification for a catalog item. Every catalog item declares
one or more named resources (`resources`, min 1). A single-resource
offering uses `resources` with one entry.
required:
- resources
properties:
resources:
type: array
minItems: 1
description: |
Named resources. Each entry declares a service type, optional
dependency ordering, and field configurations.
items:
$ref: '#/components/schemas/CatalogResource'

CatalogResource:
type: object
description: |
A named resource within a catalog item.
required:
- name
- service_type
properties:
name:
type: string
minLength: 1
description: |
Stable identifier for this resource within the catalog item
(e.g., ordersDb, app). Used in user_values.resource and CEL
references such as ${ordersDb.connectionString}.
example: ordersDb

service_type:
type: string
minLength: 1
description: |
The Service type this catalog item references.
Immutable after creation.
example: vm
Service type for this resource
(vm, container, database, cluster).
example: database

requires_resources:
type: array
description: |
Names of other catalog resources that must reach Ready state
before this resource is provisioned.
items:
type: string
minLength: 1
example:
- ordersDb

fields:
type: array
minItems: 1
description: |
Array of field configurations for this catalog item.
Each configuration defines constraints and defaults for fields
in the service type specification.
Defaults and validation for this resource. Paths are relative
to the service type spec (e.g., engine, image.reference).
items:
$ref: '#/components/schemas/FieldConfiguration'

Expand Down Expand Up @@ -982,15 +1021,23 @@ components:
UserValue:
type: object
required:
- resource
- path
- value
properties:
resource:
type: string
minLength: 1
description: |
Resource name this value targets.
example: ordersDb

path:
type: string
description: |
JSON path to the user value in the CatalogItem spec using dot notation.
Examples: "spec.vcpu.count", "spec.memory.size_gb", "metadata.labels.tier"
example: spec.vcpu.count
JSON path to the user value relative to the resource's service type
spec using dot notation.
example: version

value:
description: |
Expand Down
197 changes: 102 additions & 95 deletions api/catalog/v1alpha1/spec.gen.go

Large diffs are not rendered by default.

46 changes: 33 additions & 13 deletions api/catalog/v1alpha1/types.gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 6 additions & 4 deletions internal/catalog/handlers/v1alpha1/catalog_item.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,11 +92,13 @@ func validateAndBuildCreateCatalogItemRequest(request server.CreateCatalogItemRe
if request.Body.Spec == nil {
return nil, ErrEmptySpec
}
if request.Body.Spec.ServiceType == nil {
return nil, ErrInvalidServiceType
if len(request.Body.Spec.Resources) == 0 {
return nil, ErrEmptyResources
}
if request.Body.Spec.Fields == nil {
return nil, ErrEmptyFields
for _, r := range request.Body.Spec.Resources {
if r.ServiceType == "" {
return nil, ErrInvalidResourceServiceType
}
}
return &service.CreateCatalogItemRequest{
ID: request.Params.Id,
Expand Down
21 changes: 16 additions & 5 deletions internal/catalog/handlers/v1alpha1/catalog_item_errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,11 @@ var (
// ErrEmptySpec indicates the spec is empty (must have at least one field)
ErrEmptySpec = errors.New("spec cannot be empty: must have at least one field")

// ErrEmptyFields indicates the spec.fields array is empty (must have at least 1 field)
ErrEmptyFields = errors.New("spec.fields cannot be empty: must have at least one field")
// ErrEmptyResources indicates the spec.resources array is empty (must have at least 1 resource)
ErrEmptyResources = errors.New("spec.resources cannot be empty: must have at least one resource")

// ErrInvalidResourceServiceType indicates a resource has an empty service_type
ErrInvalidResourceServiceType = errors.New("spec.resources[].service_type cannot be empty")
)

// mapCreateCatalogItemErrorToHTTP converts service domain errors to CreateCatalogItem HTTP responses
Expand All @@ -40,8 +43,12 @@ func mapCreateCatalogItemErrorToHTTP(err error) server.CreateCatalogItemResponse
},
}
case errors.Is(err, service.ErrServiceTypeNotFound),
errors.Is(err, service.ErrCatalogItemSpecConflict),
errors.Is(err, service.ErrDependsOnCycleDetected),
errors.Is(err, service.ErrDependsOnPathNotFound):
errors.Is(err, service.ErrDependsOnPathNotFound),
errors.Is(err, service.ErrCatalogItemResourceNameTaken),
errors.Is(err, service.ErrCatalogItemRequiresResourceNotFound),
errors.Is(err, service.ErrCatalogItemRequiresCycle):
// Validation errors -> 400 Bad Request
return server.CreateCatalogItem400JSONResponse(v1alpha1.Error{
Type: v1alpha1.INVALIDARGUMENT,
Expand Down Expand Up @@ -91,9 +98,13 @@ func mapGetCatalogItemErrorToHTTP(err error) server.GetCatalogItemResponseObject
// mapUpdateCatalogItemErrorToHTTP converts service domain errors to UpdateCatalogItem HTTP responses
func mapUpdateCatalogItemErrorToHTTP(err error) server.UpdateCatalogItemResponseObject {
switch {
case errors.Is(err, service.ErrImmutableFieldUpdate),
case errors.Is(err, service.ErrImmutableSpecStructureUpdate),
errors.Is(err, service.ErrCatalogItemSpecConflict),
errors.Is(err, service.ErrDependsOnCycleDetected),
errors.Is(err, service.ErrDependsOnPathNotFound):
errors.Is(err, service.ErrDependsOnPathNotFound),
errors.Is(err, service.ErrCatalogItemResourceNameTaken),
errors.Is(err, service.ErrCatalogItemRequiresResourceNotFound),
errors.Is(err, service.ErrCatalogItemRequiresCycle):
// Validation errors -> 400 Bad Request
return server.UpdateCatalogItem400JSONResponse(v1alpha1.Error{
Type: v1alpha1.INVALIDARGUMENT,
Expand Down
6 changes: 6 additions & 0 deletions internal/catalog/handlers/v1alpha1/catalog_item_instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,12 @@ func validateAndBuildCreateCatalogItemInstanceRequest(request server.CreateCatal
if request.Body.ApiVersion != supportedAPIVersion {
return nil, ErrInvalidCatalogItemInstanceAPIVersion
}
if request.Body.DisplayName == "" {
return nil, ErrInvalidCatalogItemInstanceDisplayName
}
if request.Body.Spec.CatalogItemId == "" {
return nil, ErrInvalidCatalogItemId
}
return &service.CreateCatalogItemInstanceRequest{
ID: request.Params.Id,
ApiVersion: request.Body.ApiVersion,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,18 @@ func mapCreateCatalogItemInstanceErrorToHTTP(err error) server.CreateCatalogItem
},
}
case errors.Is(err, service.ErrCatalogItemNotFoundForInstance),
errors.Is(err, service.ErrCatalogItemSpecConflict),
errors.Is(err, service.ErrUserValuePathNotFound),
errors.Is(err, service.ErrUserValueNotEditable),
errors.Is(err, service.ErrUserValueValidationFailed),
errors.Is(err, service.ErrUserValueDependsOnViolation):
errors.Is(err, service.ErrUserValueDependsOnViolation),
errors.Is(err, service.ErrUserValueResourceRequired),
errors.Is(err, service.ErrUserValueResourceNotFound),
errors.Is(err, service.ErrInvalidCELExpression),
errors.Is(err, service.ErrCELResourceNotFound),
errors.Is(err, service.ErrCELSelfReference),
errors.Is(err, service.ErrCELServiceTypeOutputNotFound),
errors.Is(err, service.ErrUserValueCELNotAllowed):
return server.CreateCatalogItemInstance400JSONResponse(v1alpha1.Error{
Type: v1alpha1.INVALIDARGUMENT,
Status: 400,
Expand Down Expand Up @@ -112,6 +120,15 @@ func mapRehydrateCatalogItemInstanceErrorToHTTP(err error) server.RehydrateCatal
Detail: stringPtr("this instance was modified by another request; please retry"),
},
}
case errors.Is(err, service.ErrMultiResourceRehydrateNotSupported):
return server.RehydrateCatalogItemInstance500JSONResponse{
InternalServerErrorJSONResponse: server.InternalServerErrorJSONResponse{
Type: v1alpha1.UNIMPLEMENTED,
Status: 500,
Title: "Not Supported",
Detail: stringPtr(err.Error()),
},
}
case errors.Is(err, service.ErrPlacementManagerPolicyRejected):
return server.RehydrateCatalogItemInstance406JSONResponse{
PolicyRejectedJSONResponse: server.PolicyRejectedJSONResponse{
Expand Down
Loading