NAME
IO::K8s::Resource - Base class for all Kubernetes resources
VERSION
version 1.108
SYNOPSIS
package IO::K8s::Api::Core::V1::Pod;
use IO::K8s::Resource;
k8s apiVersion => 'Str';
k8s kind => 'Str';
k8s metadata => 'Meta::V1::ObjectMeta';
k8s spec => 'Core::V1::PodSpec';
1;
DESCRIPTION
Base class that sets up Moo, inheritance, and provides the k8s DSL. Just use IO::K8s::Resource; - no need for use Moo or extends.
NAME
IO::K8s::Resource - Base class for Kubernetes resources
EXPORTED FUNCTIONS
k8s
k8s name => 'Str';
k8s replicas => 'Int';
k8s ratio => 'Num'; # JSON number, unquoted on the wire
k8s suspend => 'Bool';
k8s spec => 'Core::V1::PodSpec'; # Short class name
k8s containers => ['Core::V1::Container']; # Array of objects
k8s labels => { Str => 1 }; # Opaque hash of strings
k8s limits => { Quantity => 1 }; # Typed value map (also Int/Num/Bool/Time/IntOrStr)
k8s rows => [ {} ]; # Array of opaque hashes
k8s matrix => [ [] ]; # Array of opaque arrays
k8s spec => { # Inline struct
replicas => Int,
selector => Str,
template => { Str => 1 },
};
The { Str => 1 } form is a deliberately opaque hash: any value is accepted, for genuinely free-form maps such as labels, annotations and fieldsV1. { Quantity => 1 } (and Int, Num, Bool, Time, IntOrStr) instead validates every value against that scalar type, so a map upstream declares as map[X]Quantity rejects a bad value at construction rather than at the API server.
Inline structs auto-generate an inner class (e.g. MyClass::_Spec) with the declared fields. Hashrefs are auto-coerced to the inner class on construction, through the very same coercion a named nested class gets -- so a plain container inside the hashref is copied one level rather than stored by reference (k116). Before that, ->new was the one route into an inline-struct field that aliased the caller's structure while inflate, struct_to_object and FROM_HASH already copied it; the four now agree.
A named nested class coerces the same way (since 1.108): a plain hashref passed to ->new or to the setter of an is_object, is_array_of_objects or is_hash_of_objects field is built into that class -- element-wise for an array, value-wise for a map -- so
IO::K8s::Traefik::V1alpha1::Middleware->new(
spec => { rateLimit => { average => 100 } });
no longer has to pre-build MiddlewareSpec and RateLimit by hand. It goes through the same inflation "FROM_HASH" in IO::K8s::Role::Resource uses, so boolean spellings and the unknown-field policy behave identically on both routes. A value that is already an object is passed through untouched, and anything that is neither a hashref nor an object is left to the type constraint to reject.
Short class names are auto-expanded:
Core::V1::Pod -> IO::K8s::Api::Core::V1::Pod
Meta::V1::ObjectMeta -> IO::K8s::Apimachinery::Pkg::Apis::Meta::V1::ObjectMeta
Field names that are not valid Perl identifiers are automatically sanitized: $ref becomes _ref, $schema becomes _schema, and hyphens are replaced with underscores (x-kubernetes-foo becomes x_kubernetes_foo). The original JSON key is preserved via init_arg so constructors and FROM_HASH still accept the original names, and TO_JSON outputs the original keys.
k8s '$ref' => Str; # Moo attr: _ref
k8s 'x-kubernetes-list-type' => Str; # Moo attr: x_kubernetes_list_type
Field options
k8s replicas => Int, { minimum => 0, maximum => 10, default => 1 };
k8s policy => Str, { enum => [qw(Retain Delete)], required => 1 };
k8s name => Str, { pattern => qr/\A[a-z0-9-]+\z/, description => '...' };
k8s spec => {
mode => [ Str, { enum => [qw(fast safe)] } ], # inside an inline struct
hosts => [ [Str], { pattern => '^[a-z.]+$' } ],
};
A field declaration takes an optional third argument: a hashref of options, directly after the type spec (k8s name = Type, { ... }>), or as the second element of a two-element arrayref in place of the type spec (name = [ Type, { ... } ]>) for a field inside an inline struct, which has no third-argument slot of its own. The legacy 'required' string marker and the Type! suffix (k8s x = 'Str!'>) still work and are equivalent to { required = 1 }>.
The nine recognised option keys are required, default, enum, minimum, maximum, pattern, description, nullable and preserve_unknown. All nine are recorded in the attribute registry for the CRD schema a to_crd emitter builds from it.
required itself takes two meaningful values. required = 1> (like the legacy marker and the ! suffix) both makes the field a Moo-required constructor argument and records required = 1> in the registry. required = 'schema'> records the same registry fact without the Moo enforcement, leaving the field optional at construction -- this is what IO::K8s::AutoGen uses for an OpenAPI required list, since a document a real cluster returns can still omit such a field (a server-side default, or a status object not yet populated), and inflate must not fail on data the cluster actually sent. Any other true value is treated the same as 1.
enum, minimum, maximum and pattern are additionally enforced as Type::Tiny constraints at construction, the same way { Quantity => 1 } validates a typed value map -- a bad value fails here instead of at the API server. They apply to a scalar field, to each element of an array of scalars (k8s tags => [Str], { enum => [...] }), and to each value of a typed value map (k8s weights => { Int => 1 }, { maximum => 100 }). Declaring one of them on an object, inline-struct or opaque-container field ({ Str = 1 }>, [ {} ], [ [] ], a nested class) is a class-load error, since there is no scalar value to check. A failing value dies with one of:
Value "x" is not one of: a, b
Value "-1" is below the minimum 0
Value "11" is above the maximum 10
Value "x" does not match the pattern ...
On an optional field the message is prefixed with Type::Tiny's own generic "did not pass type constraint" line; the rule text above follows in the explanation.
default, description, nullable and preserve_unknown are schema-only: they are recorded for to_crd and never change anything at construction or serialization. In particular, default is not applied client-side -- defaulting is the API server's job, and a client-side default would change the wire output, so a field with no value given still serializes as absent. nullable likewise does not make TO_JSON emit null: an unset field is still omitted from the JSON either way.
Class load fails, naming the class and field, on: an unrecognised option key (k8s: unknown field option '<key' for field '<name>' of <class> (known: ...)>); any option given an explicit undef value, since that is a declaration error rather than "no option" (k8s: field option '<key' for ... must not be undef>); a third argument that is neither 'required' nor a hashref; an empty or duplicate enum, or enum on a Bool field; minimum/maximum on a non-numeric field, a non-numeric bound, or a minimum exceeding maximum; a pattern on a non-string field or one that does not compile as a Perl regex; and a default that fails the field's own type (k8s: 'default' for ... fails the field's own type: <message>), checked once at class-load time rather than discovered later when to_crd emits it. An object-bearing field -- a referenced class, an inline struct, or an array or map of objects -- is exempt from that last check: no plain hash or array default can ever satisfy an InstanceOf constraint, so there is nothing useful to check, and the default is recorded as given.
The registry (_k8s_attr_info) keeps required as a plain 1 (absent when not required, matching the pre-D3 shape) and every other given option, one level deep, under options.
SUPPORT
Issues
Please report bugs and feature requests on GitHub at https://github.com/pplu/io-k8s-p5/issues.
CONTRIBUTING
Contributions are welcome! Please fork the repository and submit a pull request.
AUTHORS
Torsten Raudssus <getty@cpan.org>
Jose Luis Martinez Torres <jlmartin@cpan.org>
COPYRIGHT AND LICENSE
This software is Copyright (c) 2018-2026 by Jose Luis Martinez Torres <jlmartin@cpan.org>.
This is free software, licensed under:
The Apache License, Version 2.0, January 2004