NAME
IO::K8s - Objects representing things found in the Kubernetes API
VERSION
version 1.000
SYNOPSIS
use IO::K8s;
my $k8s = IO::K8s->new;
# Load .pk8s manifest files (Perl DSL)
my $resources = $k8s->load('myapp.pk8s');
# Load and validate YAML manifests
my $resources = $k8s->load_yaml('deployment.yaml');
# Validate with error collection
my ($objs, $errors) = $k8s->load_yaml($yaml, collect_errors => 1);
# Create objects programmatically
my $pod = $k8s->new_object('Pod',
metadata => { name => 'my-pod', namespace => 'default' },
spec => { containers => [{ name => 'app', image => 'nginx' }] }
);
# Export to YAML and save
print $pod->to_yaml;
$pod->save('pod.yaml');
# Inflate JSON/struct into typed objects
my $svc = $k8s->json_to_object('Service', '{"kind":"Service",...}');
my $obj = $k8s->inflate($json_with_kind); # Auto-detect class from 'kind'
# Serialize back
my $json = $k8s->object_to_json($svc);
my $struct = $k8s->object_to_struct($pod);
# With OpenAPI spec for Custom Resources (CRDs)
my $k8s = IO::K8s->new(openapi_spec => $spec_from_cluster);
my $helmchart = $k8s->inflate($helmchart_json); # Auto-generates class!
DESCRIPTION
This module provides objects and serialization / deserialization methods that represent the structures found in the Kubernetes API https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.31/
Kubernetes API is strict about input types. When a value is expected to be an integer, sending it as a string will cause rejection. This module ensures correct value types in JSON that can be sent to Kubernetes.
It also inflates JSON returned by Kubernetes into typed Perl objects.
NAME
IO::K8s - Objects representing things found in the Kubernetes API
CLASS ARCHITECTURE
IO::K8s uses a layered architecture. Understanding these layers helps when working with built-in resources or writing your own CRD classes.
IO::K8s::Resource (base layer)
All Kubernetes objects inherit from IO::K8s::Resource. It provides:
Moo class setup
The
k8sDSL for declaring attributes with Kubernetes typesTO_JSON/to_jsonserializationType registry for inflation (JSON -> objects)
The k8s DSL supports these type specifications:
k8s name => 'Str'; # string attribute
k8s replicas => 'Int'; # integer attribute
k8s ready => 'Bool'; # boolean attribute
k8s spec => 'Core::V1::PodSpec'; # nested IO::K8s object
k8s ports => ['Core::V1::ServicePort']; # array of objects
k8s labels => { Str => 1 }; # hash of strings
k8s items => ['+Full::Class::Name']; # array with full class (+ prefix)
IO::K8s::APIObject (top-level resources)
IO::K8s::APIObject extends IO::K8s::Resource for top-level API objects (Pod, Deployment, Service, etc.) by applying IO::K8s::Role::APIObject. This adds:
metadataattribute (IO::K8s::Apimachinery::Pkg::Apis::Meta::V1::ObjectMeta)api_version()- derived from class name for built-in types, or set via import parameter for CRDskind()- derived from the last segment of the class nameresource_plural()- returnsundef(auto-pluralize) by default, override for CRDsto_yaml()- serialize to YAML suitable forkubectl apply -fsave($file)- write YAML to file
IO::K8s::Role::Namespaced (marker role)
IO::K8s::Role::Namespaced is a marker role for namespace-scoped resources. Kubernetes::REST checks this to build the correct URL path (with or without /namespaces/{ns}/).
WRITING CRD CLASSES
To use Custom Resource Definitions with Kubernetes::REST, write a Perl class using IO::K8s::APIObject. This is the same base used by all built-in Kubernetes types like Pod, Deployment, and Service.
Minimal CRD class
package My::StaticWebSite;
use IO::K8s::APIObject
api_version => 'homelab.example.com/v1',
resource_plural => 'staticwebsites';
with 'IO::K8s::Role::Namespaced';
k8s spec => { Str => 1 };
k8s status => { Str => 1 };
1;
That's it - 6 lines of actual code. This class now supports:
my $site = My::StaticWebSite->new(
metadata => $meta_object,
spec => { domain => 'blog.example.com', image => 'nginx' },
);
$site->kind; # "StaticWebSite"
$site->api_version; # "homelab.example.com/v1"
$site->to_yaml; # full YAML output
$site->TO_JSON; # hashref for JSON encoding
Import parameters
use IO::K8s::APIObject accepts these parameters:
api_version(required for CRDs)-
The CRD's
group/version, e.g.'homelab.example.com/v1'. For built-in types this is derived from the class name (IO::K8s::Api::Core::V1::Podgivesv1), but CRDs must specify it explicitly since their class names don't follow theIO::K8s::Api::*convention. resource_plural(recommended for CRDs)-
The plural resource name for URL building, e.g.
'staticwebsites'. Must match the CRD'sspec.names.plural. If omitted, Kubernetes::REST auto-pluralizes the kind name (StaticWebSite->staticwebsites), but this heuristic doesn't work for all names.
Namespaced vs cluster-scoped
Apply IO::K8s::Role::Namespaced for namespace-scoped CRDs (the common case). Omit it for cluster-scoped CRDs:
# Namespaced CRD (most CRDs):
package My::StaticWebSite;
use IO::K8s::APIObject api_version => 'homelab.example.com/v1', ...;
with 'IO::K8s::Role::Namespaced';
# Cluster-scoped CRD (rare):
package My::ClusterBackupPolicy;
use IO::K8s::APIObject api_version => 'backup.example.com/v1', ...;
# No 'with Namespaced' - this is cluster-wide
Registering with Kubernetes::REST
Register your CRD class in the resource map using the + prefix (which means "use this full class name as-is"):
use Kubernetes::REST::Kubeconfig;
use My::StaticWebSite;
my $api = Kubernetes::REST::Kubeconfig->new->api;
$api->resource_map->{StaticWebSite} = '+My::StaticWebSite';
# Now use it like any built-in resource
my $site = $api->create($api->new_object(StaticWebSite =>
metadata => { name => 'my-blog', namespace => 'default' },
spec => { domain => 'blog.example.com', image => 'nginx' },
));
See Kubernetes::REST::Example for complete CRUD examples with CRDs.
AUTO-GENERATION
IO::K8s can automatically generate classes for Custom Resources and other types not included in the built-in classes. This is an alternative to writing CRD classes by hand.
From cluster OpenAPI spec
Provide the cluster's OpenAPI spec and IO::K8s will auto-generate classes on demand:
use IO::K8s;
use Kubernetes::REST::Kubeconfig;
# Get OpenAPI spec from cluster
my $api = Kubernetes::REST::Kubeconfig->new->api;
my $resp = $api->_request('GET', '/openapi/v2');
my $spec = JSON::MaybeXS->new->decode($resp->content);
# Create IO::K8s with auto-generation enabled
my $k8s = IO::K8s->new(openapi_spec => $spec);
# Now inflate works for ANY type in the cluster
my $addon = $k8s->inflate($k3s_addon_json); # k3s.cattle.io/v1 Addon
my $chart = $k8s->inflate($helmchart_json); # helm.cattle.io/v1 HelmChart
Auto-generated classes are placed in a unique namespace per IO::K8s instance (e.g., IO::K8s::_AUTOGEN_abc123::...) to avoid collisions.
Explicit generation with IO::K8s::AutoGen
For more control, use IO::K8s::AutoGen directly:
use IO::K8s::AutoGen;
my $class = IO::K8s::AutoGen::get_or_generate(
'com.example.homelab.v1.StaticWebSite', # definition name
$schema, # OpenAPI schema
{}, # all definitions
'MyApp::K8s', # namespace
api_version => 'homelab.example.com/v1',
kind => 'StaticWebSite',
resource_plural => 'staticwebsites',
is_namespaced => 1,
);
# Register with Kubernetes::REST
$api->resource_map->{StaticWebSite} = "+$class";
Custom Class Namespaces
You can provide your own pre-built classes that take precedence over both built-in and auto-generated classes:
my $k8s = IO::K8s->new(
class_namespaces => ['MyApp::K8s'],
openapi_spec => $spec,
);
With this configuration, the class lookup order is:
1. MyApp::K8s::... (your classes)
2. IO::K8s::... (built-in classes)
3. IO::K8s::_AUTOGEN_... (auto-generated)
This lets you create optimized or customized classes for specific resources while falling back to auto-generation for everything else.
ATTRIBUTES
openapi_spec
Optional. The OpenAPI v2 specification from a Kubernetes cluster. When provided, enables auto-generation of classes for types not found in the built-in classes.
class_namespaces
Optional. ArrayRef of namespace prefixes to search for classes before checking IO::K8s built-ins. Useful for providing your own implementations.
resource_map
HashRef mapping short names (like 'Pod') to class paths. Defaults to built-in mappings for standard Kubernetes resources.
METHODS
load
my $resources = $k8s->load('myapp.pk8s');
Load a .pk8s manifest file and return an ArrayRef of IO::K8s objects.
The .pk8s file format is Perl code with a DSL for defining Kubernetes resources:
# myapp.pk8s
ConfigMap {
name => 'my-config',
namespace => 'default',
data => { key => 'value' }
};
Deployment {
name => 'my-app',
namespace => 'default',
spec => {
replicas => 3,
selector => { matchLabels => { app => 'my-app' } },
template => {
metadata => { labels => { app => 'my-app' } },
spec => {
containers => [{
name => 'app',
image => 'my-app:latest',
}],
},
},
}
};
Inside {} blocks, name, namespace, labels, and annotations are automatically moved to metadata.
With CRDs (requires openapi_spec):
my $k8s = IO::K8s->new(openapi_spec => $spec);
my $resources = $k8s->load('helmchart.pk8s');
load_yaml
my $resources = $k8s->load_yaml('manifest.yaml');
my $resources = $k8s->load_yaml($yaml_string);
Load a YAML manifest file (or YAML string) and return an ArrayRef of IO::K8s objects. Supports multi-document YAML (separated by ---).
This method validates the YAML against the Kubernetes types. If a field has the wrong type or an unknown field is used, an error is thrown. This is useful for validating manifests before applying them to a cluster.
# Validate a manifest file
eval {
my $objs = $k8s->load_yaml('deployment.yaml');
say "Valid! Contains " . scalar(@$objs) . " resources";
};
if ($@) {
say "Invalid manifest: $@";
}
Options:
- collect_errors => 1
-
Collect all validation errors instead of stopping at the first one. Returns a list of
(objects, errors)whereobjectscontains successfully parsed resources anderrorsis an ArrayRef of error messages.my ($objs, $errors) = $k8s->load_yaml($yaml, collect_errors => 1); if (@$errors) { say "Found " . scalar(@$errors) . " errors:"; say " - $_" for @$errors; }
new_object
my $pod = $k8s->new_object('Pod', %args);
my $pod = $k8s->new_object('Pod', \%args);
Create a new Kubernetes object of the given type. The type can be a short name (like Pod) or a full class path.
inflate
my $obj = $k8s->inflate($json_string);
my $obj = $k8s->inflate(\%hashref);
Inflate a JSON string or hashref into a typed IO::K8s object. The class is auto-detected from the kind field in the data.
json_to_object
my $obj = $k8s->json_to_object($json_with_kind);
my $obj = $k8s->json_to_object('Pod', $json_string);
Convert JSON to an IO::K8s object. With one argument, auto-detects the class from kind. With two arguments, uses the specified class.
struct_to_object
my $obj = $k8s->struct_to_object(\%hashref_with_kind);
my $obj = $k8s->struct_to_object('Pod', \%hashref);
Convert a Perl hashref to an IO::K8s object. With one argument, auto-detects the class from kind. With two arguments, uses the specified class.
object_to_json
my $json = $k8s->object_to_json($obj);
Serialize an IO::K8s object to JSON.
object_to_struct
my $hashref = $k8s->object_to_struct($obj);
Convert an IO::K8s object to a plain Perl hashref.
UPGRADING FROM PREVIOUS VERSIONS
WARNING: Version 1.00 contains breaking changes!
This version has been completely rewritten. Key changes that may affect your code:
Moose to Moo migration
All classes now use Moo instead of Moose. This means faster startup and lighter dependencies, but Moose-specific features (meta introspection, etc.) are no longer available.
List classes removed
Individual
*Listclasses (e.g.,IO::K8s::Api::Core::V1::PodList) have been replaced with the unified IO::K8s::List class. The old class names still exist as deprecation stubs that emit warnings.Updated to Kubernetes v1.31 API
API objects have been updated from v1.14 to v1.31. Some fields may have changed, been added, or removed according to upstream Kubernetes API changes.
New Role for namespaced resources
Resources that are namespaced now consume IO::K8s::Role::Namespaced. Use
$class->does('IO::K8s::Role::Namespaced')to check if a resource is namespace-scoped.
SEE ALSO
Kubernetes::REST - REST client for the Kubernetes API, uses IO::K8s for typed request/response objects
Kubernetes::REST::Example - Comprehensive examples for using Kubernetes::REST with IO::K8s against a real cluster (Minikube, K3s, etc.)
https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.31/
BUGS and SOURCE
The source code is located here: https://github.com/pplu/io-k8s-p5
Please report bugs to: https://github.com/pplu/io-k8s-p5/issues
COPYRIGHT and LICENSE
Copyright (c) 2018 by CAPSiDE Copyright (c) 2026 by Torsten Raudssus
This code is distributed under the Apache 2 License. The full text of the license can be found in the LICENSE file included with this module.
AUTHORS
Torsten Raudssus <torsten@raudssus.de> (current maintainer)
Jose Luis Martinez <jlmartinez@capside.com> (original author)
SUPPORT
Issues
Please report bugs and feature requests on GitHub at https://github.com/pplu/io-k8s-p5/issues.
IRC
Join #kubernetes on irc.perl.org or message Getty directly.
CONTRIBUTING
Contributions are welcome! Please fork the repository and submit a pull request.
AUTHORS
Torsten Raudssus <torsten@raudssus.de>
Jose Luis Martinez <jlmartinez@capside.com> (original author, inactive)
COPYRIGHT AND LICENSE
This software is Copyright (c) 2018 by CAPSiDE.
This is free software, licensed under:
The Apache License, Version 2.0, January 2004