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
10 changes: 7 additions & 3 deletions docs/docs/operator/building-blocks/entities.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -152,8 +152,12 @@ public class EntitySpec
### Special Attributes

- `[Ignore]`: Excludes a property or entity from CRD generation
- `[PreserveUnknownFields]`: Preserves unknown fields in the Kubernetes object
- `[EmbeddedResource]`: Marks a property as an embedded Kubernetes resource
- `[PreserveUnknownFields]`: Allows unknown fields on the annotated object (`x-kubernetes-preserve-unknown-fields: true`). The known fields are still transpiled and validated, so you keep a structural schema for what you model while permitting extra fields. Works the same whether placed on a property or on a class/type: if the type cannot be represented (it contains a circular reference or an otherwise non-transpilable member), it gracefully falls back to an opaque `type: object` with `x-kubernetes-preserve-unknown-fields: true` instead of failing — making this the recommended way to model complex, externally generated, or self-referencing types.
- `[EmbeddedResource]`: Marks a property as an embedded Kubernetes resource. The property type is never traversed; the schema is always an opaque embedded `type: object`.

:::note
The CRD transpiler maps property types recursively. A **circular type reference** that is not opted out via `[PreserveUnknownFields]` or `[Ignore]` cannot be represented as a finite OpenAPI schema and raises a descriptive `TranspilationFailedException` during generation. Annotate the offending property or type with `[PreserveUnknownFields]` or `[Ignore]`, or restructure the type to remove the cycle.
:::

## Example with Multiple Attributes

Expand Down Expand Up @@ -264,4 +268,4 @@ subresources:

:::note
`[ScaleSubresource]` and the status subresource are controlled independently. A `Status` property activates `status: {}` regardless of `[ScaleSubresource]`, and `[ScaleSubresource]` adds `scale:` regardless of whether a `Status` property exists.
:::
:::
294 changes: 182 additions & 112 deletions src/KubeOps.Transpiler/Crds.cs

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the Apache 2.0 License.
// See the LICENSE file in the project root for more information.

namespace KubeOps.Transpiler.Exceptions;

/// <summary>
/// Raised when the CRD transpiler detects a circular type reference that cannot be represented as a
/// finite OpenAPI schema. Derives from <see cref="InvalidOperationException"/> to preserve backwards
/// compatibility for existing catch clauses.
/// </summary>
internal sealed class CircularTypeReferenceException : InvalidOperationException
{
/// <summary>
/// Initializes a new instance of the <see cref="CircularTypeReferenceException"/> class.
/// </summary>
public CircularTypeReferenceException()
{
}

/// <summary>
/// Initializes a new instance of the <see cref="CircularTypeReferenceException"/> class.
/// </summary>
/// <param name="message">The error message.</param>
public CircularTypeReferenceException(string? message)
: base(message)
{
}

/// <summary>
/// Initializes a new instance of the <see cref="CircularTypeReferenceException"/> class.
/// </summary>
/// <param name="message">The error message.</param>
/// <param name="innerException">The exception that caused this exception.</param>
public CircularTypeReferenceException(string? message, Exception? innerException)
: base(message, innerException)
{
}
}
37 changes: 37 additions & 0 deletions src/KubeOps.Transpiler/Exceptions/InvalidTypeException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the Apache 2.0 License.
// See the LICENSE file in the project root for more information.

namespace KubeOps.Transpiler.Exceptions;

/// <summary>
/// Raised when the CRD transpiler encounters a type it cannot map to an OpenAPI schema.
/// </summary>
internal sealed class InvalidTypeException : InvalidOperationException
{
/// <summary>
/// Initializes a new instance of the <see cref="InvalidTypeException"/> class.
/// </summary>
public InvalidTypeException()
{
}

/// <summary>
/// Initializes a new instance of the <see cref="InvalidTypeException"/> class.
/// </summary>
/// <param name="message">The error message.</param>
public InvalidTypeException(string? message)
: base(message)
{
}

/// <summary>
/// Initializes a new instance of the <see cref="InvalidTypeException"/> class.
/// </summary>
/// <param name="message">The error message.</param>
/// <param name="innerException">The exception that caused this exception.</param>
public InvalidTypeException(string? message, Exception? innerException)
: base(message, innerException)
{
}
}
39 changes: 39 additions & 0 deletions src/KubeOps.Transpiler/Exceptions/TranspilationFailedException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the Apache 2.0 License.
// See the LICENSE file in the project root for more information.

namespace KubeOps.Transpiler.Exceptions;

/// <summary>
/// Raised when an entity cannot be transpiled into a CRD. The message is prefixed with the affected
/// entity; the concrete cause (for example a circular type reference or a non-transpilable type) is
/// available via <see cref="Exception.InnerException"/>.
/// </summary>
public sealed class TranspilationFailedException : Exception
{
/// <summary>
/// Initializes a new instance of the <see cref="TranspilationFailedException"/> class.
/// </summary>
public TranspilationFailedException()
{
}

/// <summary>
/// Initializes a new instance of the <see cref="TranspilationFailedException"/> class.
/// </summary>
/// <param name="message">The error message.</param>
public TranspilationFailedException(string? message)
: base(message)
{
}

/// <summary>
/// Initializes a new instance of the <see cref="TranspilationFailedException"/> class.
/// </summary>
/// <param name="message">The error message.</param>
/// <param name="innerException">The exception that caused this exception.</param>
public TranspilationFailedException(string? message, Exception? innerException)
: base(message, innerException)
{
}
}
Loading
Loading