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 k8s DSL for declaring attributes with Kubernetes types

  • TO_JSON / to_json serialization

  • Type 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:

  • metadata attribute (IO::K8s::Apimachinery::Pkg::Apis::Meta::V1::ObjectMeta)

  • api_version() - derived from class name for built-in types, or set via import parameter for CRDs

  • kind() - derived from the last segment of the class name

  • resource_plural() - returns undef (auto-pluralize) by default, override for CRDs

  • to_yaml() - serialize to YAML suitable for kubectl apply -f

  • save($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::Pod gives v1), but CRDs must specify it explicitly since their class names don't follow the IO::K8s::Api::* convention.

resource_plural (recommended for CRDs)

The plural resource name for URL building, e.g. 'staticwebsites'. Must match the CRD's spec.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) where objects contains successfully parsed resources and errors is 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 *List classes (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