view release on metacpan or search on metacpan
.claude/skills/perl-kubernetes-rest/SKILL.md view on Meta::CPAN
allowed-tools: Read, Grep, Glob
model: sonnet
---
# Kubernetes::REST â K8s API Client for Perl
## Connect
```perl
my $api = Kubernetes::REST->new(
server => { endpoint => 'https://k8s.local:6443' },
credentials => { token => $token },
);
# With SSL options
my $api = Kubernetes::REST->new(
server => {
endpoint => 'https://k8s.local:6443',
ssl_verify_server => 1,
ssl_ca_file => '/path/to/ca.crt',
},
credentials => { token => $token },
);
```
## CRUD
```perl
lib/Kubernetes/REST.pm view on Meta::CPAN
# 2. io->call - executes the request (pluggable: HTTP::Tiny, async, mock)
# 3. _check_response / _inflate_object / _inflate_list - processes the response
#
# This separation allows different IO backends (sync, async, mock) to slot in
# at step 2 without touching request preparation or response processing.
# ============================================================================
sub _prepare_request {
my ($self, $method, $path, %opts) = @_;
my $url = $self->server->endpoint . $path;
my $content_type = $opts{content_type} // 'application/json';
my $body = $opts{body};
my $parameters = $opts{parameters};
my $extra_headers = $opts{headers} // {};
# Append query parameters to URL
if ($parameters && %$parameters) {
my @pairs;
for my $key (sort keys %$parameters) {
my $val = $parameters->{$key};
lib/Kubernetes/REST.pm view on Meta::CPAN
=head1 VERSION
version 1.104
=head1 SYNOPSIS
use Kubernetes::REST;
my $api = Kubernetes::REST->new(
server => {
endpoint => 'https://kubernetes.local:6443',
ssl_verify_server => 1,
ssl_ca_file => '/path/to/ca.crt',
},
credentials => { token => $token },
);
# List all namespaces
my $namespaces = $api->list('Namespace');
for my $ns (@{ $namespaces->items }) {
say $ns->metadata->name;
lib/Kubernetes/REST.pm view on Meta::CPAN
=head1 DESCRIPTION
This module provides a simple REST client for the Kubernetes API using IO::K8s
resource classes. The IO::K8s classes know their own metadata (API version,
kind, whether they're namespaced), so URL building is automatic.
=head2 server
Required. L<Kubernetes::REST::Server> instance or hashref with server connection configuration.
server => { endpoint => 'https://kubernetes.local:6443' }
Automatically coerces hashrefs to L<Kubernetes::REST::Server> objects.
=head2 credentials
Required. Authentication credentials. Can be a hashref, L<Kubernetes::REST::AuthToken>, or any object with a C<token()> method.
credentials => { token => $bearer_token }
Automatically coerces hashrefs to L<Kubernetes::REST::AuthToken> objects.
lib/Kubernetes/REST.pm view on Meta::CPAN
Provides delegated methods: C<new_object>, C<inflate>, C<json_to_object>, C<struct_to_object>, C<expand_class>.
=head2 resource_map_from_cluster
Boolean. If true, dynamically loads the resource map from the cluster's OpenAPI spec. Defaults to C<1>.
Set to C<0> to use L<IO::K8s> built-in resource map instead (faster startup, but may not match your cluster version).
=head2 cluster_version
Read-only. The Kubernetes cluster version string (e.g., C<v1.31.0>). Fetched automatically from the C</version> endpoint when first accessed.
=head2 resource_map
Hashref mapping short resource names to L<IO::K8s> class paths. By default loads dynamically from the cluster (if C<resource_map_from_cluster> is true) or uses L<IO::K8s> built-in map.
Override for custom resources:
resource_map => {
%{ IO::K8s->default_resource_map },
MyResource => '+My::K8s::V1::MyResource',
}
The C<+> prefix tells L<IO::K8s> that this is a custom class (not in the IO::K8s:: namespace).
=head2 fetch_resource_map
my $map = $api->fetch_resource_map;
Fetch the resource map from the cluster's OpenAPI spec (C</openapi/v2> endpoint). Returns a hashref mapping short resource names (e.g., C<Pod>) to full L<IO::K8s> class paths.
Called automatically if C<resource_map_from_cluster> is enabled.
=head2 schema_for
my $schema = $api->schema_for('Pod');
Get the OpenAPI schema definition for a resource type from the cluster. Accepts short names (C<Pod>), full class names (C<IO::K8s::Api::Core::V1::Pod>), or OpenAPI definition names (C<io.k8s.api.core.v1.Pod>).
Returns a hashref with the OpenAPI v2 schema definition.
lib/Kubernetes/REST.pm view on Meta::CPAN
=back
=head1 ATTRIBUTES
=head2 server
Required. Connection details for the Kubernetes API server. Can be a hashref or
a L<Kubernetes::REST::Server> object.
server => { endpoint => 'https://kubernetes.local:6443' }
=head2 credentials
Required. Authentication credentials. Can be a hashref or a L<Kubernetes::REST::AuthToken>
object.
credentials => { token => $bearer_token }
=head2 io
lib/Kubernetes/REST.pm view on Meta::CPAN
Optional hashref. Maps short resource names to IO::K8s class paths. By default
loads dynamically from the cluster (if C<resource_map_from_cluster> is true) or
uses L<IO::K8s> built-in map. Can be overridden for custom resources.
resource_map => { MyResource => 'Custom::V1::MyResource' }
=head2 cluster_version
Read-only. The Kubernetes cluster version string (e.g., "v1.31.0"). Fetched
automatically from the /version endpoint when first accessed.
=head1 METHODS
=head2 new_object($class, \%attrs) or new_object($class, %attrs)
Create a new IO::K8s object. Accepts short class names (e.g., 'Pod', 'Namespace')
and either a hashref or a hash of attributes.
# With hashref
my $ns = $api->new_object(Namespace => { metadata => { name => 'foo' } });
lib/Kubernetes/REST.pm view on Meta::CPAN
=item on_open, on_frame, on_close, on_error - Duplex transport callbacks passed to IO backend
=back
Requires an IO backend implementing C<call_duplex>. The default sync backends
currently do not provide duplex transport.
=head2 fetch_resource_map()
Fetch the resource map from the cluster's OpenAPI spec (/openapi/v2 endpoint).
Returns a hashref mapping short resource names (e.g., "Pod") to full IO::K8s
class paths. This method is called automatically if C<resource_map_from_cluster>
is enabled.
=head1 BUILDING BLOCKS FOR ASYNC WRAPPERS
Async wrappers like L<Net::Async::Kubernetes> need access to the request/response
pipeline without going through the synchronous convenience methods. The following
public methods provide this:
lib/Kubernetes/REST/Example.pm view on Meta::CPAN
# Verify
kubectl get nodes
B<Key differences from Minikube:>
=over 4
=item * Kubeconfig lives at C</etc/rancher/k3s/k3s.yaml> (not C<~/.kube/config>)
=item * The server endpoint is C<https://127.0.0.1:6443>
=item * K3s includes Traefik as its default ingress controller
=item * K3s uses C<containerd> instead of Docker
=item * NodePort services are accessible directly on the host IP
=back
B<Connecting from Perl with K3s:>
lib/Kubernetes/REST/Example.pm view on Meta::CPAN
=head1 INSTALL PERL DEPENDENCIES
cpanm Kubernetes::REST
=head1 CONNECTING TO THE CLUSTER
=head2 Using the kubeconfig (recommended)
L<Kubernetes::REST::Kubeconfig> parses your kubeconfig and sets up
authentication, SSL certificates, and the server endpoint automatically:
use Kubernetes::REST::Kubeconfig;
my $kc = Kubernetes::REST::Kubeconfig->new;
my $api = $kc->api;
say "Cluster version: " . $api->cluster_version;
If you have multiple contexts:
lib/Kubernetes/REST/Example.pm view on Meta::CPAN
say "Available: @$contexts";
my $api = $kc->api('minikube'); # or 'default', 'kind-test', etc.
=head2 Manual configuration
use Kubernetes::REST;
my $api = Kubernetes::REST->new(
server => {
endpoint => 'https://127.0.0.1:6443',
ssl_verify_server => 0,
},
credentials => { token => $token },
resource_map_from_cluster => 0,
);
=head1 LISTING RESOURCES
=head2 Namespaces
lib/Kubernetes/REST/Example.pm view on Meta::CPAN
=head2 Registering the CRD class
Register your class with the C<+> prefix in the resource map:
my $api = Kubernetes::REST::Kubeconfig->new->api;
$api->resource_map->{StaticWebSite} = '+My::StaticWebSite';
Or pass it at construction time:
my $api = Kubernetes::REST->new(
server => { endpoint => '...' },
credentials => { token => '...' },
resource_map => {
%{ IO::K8s->default_resource_map },
StaticWebSite => '+My::StaticWebSite',
},
);
=head2 CRUD on Custom Resources
Once registered, CRDs work exactly like built-in resources:
lib/Kubernetes/REST/HTTPRequest.pm view on Meta::CPAN
has uri => (is => 'rw', isa => Str);
has method => (is => 'rw', isa => Str);
has url => (is => 'rw', isa => Str, lazy => 1, default => sub {
my $self = shift;
return $self->server->endpoint . $self->uri if $self->server;
return '';
});
has headers => (is => 'rw', isa => HashRef, default => sub { {} });
has parameters => (is => 'rw', isa => HashRef);
lib/Kubernetes/REST/Kubeconfig.pm view on Meta::CPAN
. " and not running in-cluster";
}
$context_name //= $self->current_context_name;
my $ctx = $self->context($context_name);
my $cluster = $self->cluster($ctx->{cluster});
my $user = $self->user($ctx->{user});
# Build server config
my %server = (
endpoint => $cluster->{server},
);
if (my %ca = $self->_resolve_cert($cluster, 'certificate-authority')) {
$server{ $ca{pem} ? 'ssl_ca_pem' : 'ssl_ca_file' } = $ca{pem} // $ca{file};
}
if ($cluster->{'insecure-skip-tls-verify'}) {
$server{ssl_verify_server} = 0;
} else {
$server{ssl_verify_server} = 1;
lib/Kubernetes/REST/Kubeconfig.pm view on Meta::CPAN
return undef unless -f $SA_TOKEN;
my $host = $ENV{KUBERNETES_SERVICE_HOST} // 'kubernetes.default.svc';
my $port = $ENV{KUBERNETES_SERVICE_PORT} // '443';
open my $fh, '<', $SA_TOKEN or croak "Cannot read $SA_TOKEN: $!";
my $token = do { local $/; <$fh> };
chomp $token;
return Kubernetes::REST->new(
server => Kubernetes::REST::Server->new(
endpoint => "https://$host:$port",
ssl_ca_file => $SA_CA,
ssl_verify_server => 1,
),
credentials => Kubernetes::REST::AuthToken->new(token => $token),
);
}
1;
__END__
lib/Kubernetes/REST/Server.pm view on Meta::CPAN
package Kubernetes::REST::Server;
our $VERSION = '1.104';
# ABSTRACT: Kubernetes API server connection configuration
use Moo;
use Types::Standard qw/Str Bool/;
has endpoint => (is => 'ro', isa => Str, required => 1);
has ssl_verify_server => (is => 'ro', isa => Bool, default => 1);
has ssl_cert_file => (is => 'ro');
has ssl_cert_pem => (is => 'ro');
lib/Kubernetes/REST/Server.pm view on Meta::CPAN
=head1 VERSION
version 1.104
=head1 SYNOPSIS
use Kubernetes::REST::Server;
my $server = Kubernetes::REST::Server->new(
endpoint => 'https://kubernetes.local:6443',
ssl_verify_server => 1,
ssl_ca_file => '/path/to/ca.crt',
);
=head1 DESCRIPTION
Configuration object for Kubernetes API server connection details.
=head2 endpoint
Required. The Kubernetes API server endpoint URL (e.g., C<https://kubernetes.local:6443>).
=head2 ssl_verify_server
Boolean. Whether to verify the server's SSL certificate. Defaults to C<1> (true).
Set to C<0> for development clusters with self-signed certificates.
=head2 ssl_cert_file
Optional. Path to client certificate file for mTLS authentication.
t/03_params2request.t view on Meta::CPAN
use warnings;
use Test::More;
use Kubernetes::REST;
use Kubernetes::REST::Server;
use Kubernetes::REST::AuthToken;
# Test _build_path functionality
{
my $api = Kubernetes::REST->new(
server => Kubernetes::REST::Server->new(endpoint => 'http://example.com'),
credentials => Kubernetes::REST::AuthToken->new(token => 'FakeToken'),
resource_map_from_cluster => 0, # Use IO::K8s defaults for testing
);
# Test expand_class with short names
is($api->expand_class('Pod'), 'IO::K8s::Api::Core::V1::Pod', 'Pod expands correctly');
is($api->expand_class('Deployment'), 'IO::K8s::Api::Apps::V1::Deployment', 'Deployment expands correctly');
is($api->expand_class('Node'), 'IO::K8s::Api::Core::V1::Node', 'Node expands correctly');
# Test with relative path
t/03_params2request.t view on Meta::CPAN
}
# Test resource_map customization
{
my $custom_map = {
Pod => 'Api::Core::V1::Pod',
MyCustomResource => 'Api::Custom::V1::MyCustomResource',
};
my $api = Kubernetes::REST->new(
server => Kubernetes::REST::Server->new(endpoint => 'http://example.com'),
credentials => Kubernetes::REST::AuthToken->new(token => 'FakeToken'),
resource_map => $custom_map,
);
is($api->expand_class('MyCustomResource'), 'IO::K8s::Api::Custom::V1::MyCustomResource', 'Custom resource map works');
}
# Test _build_path pluralization rules
{
my $api = Kubernetes::REST->new(
server => Kubernetes::REST::Server->new(endpoint => 'http://example.com'),
credentials => Kubernetes::REST::AuthToken->new(token => 'FakeToken'),
resource_map_from_cluster => 0,
);
# Simple +s: pod -> pods
my $path = $api->_build_path('IO::K8s::Api::Core::V1::Pod');
like($path, qr{/pods$}, 'Pod -> pods (simple +s)');
# Simple +s: node -> nodes
$path = $api->_build_path('IO::K8s::Api::Core::V1::Node');
t/04_response2result.t view on Meta::CPAN
use Test::More;
use Test::Exception;
use Kubernetes::REST;
use Kubernetes::REST::Server;
use Kubernetes::REST::AuthToken;
# Test that error responses are handled correctly
# (actual API calls would require a real cluster, so we just test setup)
{
my $api = Kubernetes::REST->new(
server => Kubernetes::REST::Server->new(endpoint => 'http://example.com'),
credentials => Kubernetes::REST::AuthToken->new(token => 'FakeToken'),
resource_map_from_cluster => 0,
);
# Verify API object is created correctly
ok($api, 'API object created');
is($api->server->endpoint, 'http://example.com', 'Server endpoint set correctly');
is($api->credentials->token, 'FakeToken', 'Credentials set correctly');
}
# Test error handling for missing parameters
{
my $api = Kubernetes::REST->new(
server => Kubernetes::REST::Server->new(endpoint => 'http://example.com'),
credentials => Kubernetes::REST::AuthToken->new(token => 'FakeToken'),
resource_map_from_cluster => 0,
);
throws_ok(
sub { $api->get('Pod') },
qr/name required/,
'get without name throws error'
);
t/06_examples.t view on Meta::CPAN
use Test::More;
use FindBin;
use lib "$FindBin::Bin/../lib";
use Kubernetes::REST;
use MIME::Base64 qw(encode_base64);
# Create a Kubernetes::REST instance for new_object (no server needed)
my $api = Kubernetes::REST->new(
server => {
endpoint => 'https://127.0.0.1:6443',
ssl_verify_server => 0,
},
credentials => { token => 'test' },
resource_map_from_cluster => 0,
);
ok($api, 'API object created');
# === Namespace ===
subtest 'Example: Namespace' => sub {
$api->resource_map->{StaticWebSite} = '+My::StaticWebSite';
} else {
diag "Running with MOCK responses";
# Build mock API with CRD in resource map
require Kubernetes::REST;
require Kubernetes::REST::Server;
require Kubernetes::REST::AuthToken;
my $default_map = IO::K8s->default_resource_map;
$api = Kubernetes::REST->new(
server => Kubernetes::REST::Server->new(endpoint => 'http://mock.local'),
credentials => Kubernetes::REST::AuthToken->new(token => 'MockToken'),
resource_map_from_cluster => 0,
resource_map => {
%$default_map,
StaticWebSite => '+My::StaticWebSite',
},
io => Test::Kubernetes::Mock::IO->new,
);
}
t/09_crd_autogen.t view on Meta::CPAN
}
$api->resource_map->{StaticWebSiteAG} = "+$crd_class";
} else {
diag "Running with MOCK responses";
require Kubernetes::REST;
require Kubernetes::REST::Server;
require Kubernetes::REST::AuthToken;
my $default_map = IO::K8s->default_resource_map;
$api = Kubernetes::REST->new(
server => Kubernetes::REST::Server->new(endpoint => 'http://mock.local'),
credentials => Kubernetes::REST::AuthToken->new(token => 'MockToken'),
resource_map_from_cluster => 0,
resource_map => {
%$default_map,
StaticWebSiteAG => "+$crd_class",
},
io => Test::Kubernetes::Mock::IO->new,
);
}
t/10_watch.t view on Meta::CPAN
use Kubernetes::REST::AuthToken;
use Kubernetes::REST::WatchEvent;
use IO::K8s;
my $container = { name => 'nginx', image => 'nginx:1.27' };
# Build a mock API with watch-capable IO
my $mock_io = Test::Kubernetes::Mock::IO->new;
my $api = Kubernetes::REST->new(
server => Kubernetes::REST::Server->new(endpoint => 'http://mock.local'),
credentials => Kubernetes::REST::AuthToken->new(token => 'MockToken'),
resource_map_from_cluster => 0,
io => $mock_io,
);
# === Test 1: Watch with ADDED/MODIFIED/DELETED events ===
subtest 'watch pod events' => sub {
$mock_io->add_watch_events('/api/v1/namespaces/default/pods', [
{
type => 'ADDED',
t/10_watch.t view on Meta::CPAN
};
# === Test 4: Watch CRD class ===
subtest 'watch CRD class' => sub {
require My::StaticWebSite;
# Build API with CRD registered
my $crd_io = Test::Kubernetes::Mock::IO->new;
my $default_map = IO::K8s->default_resource_map;
my $crd_api = Kubernetes::REST->new(
server => Kubernetes::REST::Server->new(endpoint => 'http://mock.local'),
credentials => Kubernetes::REST::AuthToken->new(token => 'MockToken'),
resource_map_from_cluster => 0,
resource_map => {
%$default_map,
StaticWebSite => '+My::StaticWebSite',
},
io => $crd_io,
);
$crd_io->add_watch_events('/apis/homelab.example.com/v1/namespaces/default/staticwebsites', [
t/12_patch.t view on Meta::CPAN
use Test::Kubernetes::Mock qw(mock_api);
use Kubernetes::REST;
use Kubernetes::REST::Server;
use Kubernetes::REST::AuthToken;
use IO::K8s;
# Build a mock API with programmable responses
my $mock_io = Test::Kubernetes::Mock::IO->new;
my $api = Kubernetes::REST->new(
server => Kubernetes::REST::Server->new(endpoint => 'http://mock.local'),
credentials => Kubernetes::REST::AuthToken->new(token => 'MockToken'),
resource_map_from_cluster => 0,
io => $mock_io,
);
# Simulated patched deployment response
my $deploy_response = {
apiVersion => 'apps/v1',
kind => 'Deployment',
metadata => {
t/13_io_backends.t view on Meta::CPAN
# ============================================================================
subtest 'REST default IO is LWPIO' => sub {
use Test::Kubernetes::Mock qw(mock_api);
# The mock API uses Mock::IO, so test by creating a non-mock one
require Kubernetes::REST;
require Kubernetes::REST::Server;
require Kubernetes::REST::AuthToken;
my $api = Kubernetes::REST->new(
server => Kubernetes::REST::Server->new(endpoint => 'http://test.local'),
credentials => Kubernetes::REST::AuthToken->new(token => 'test'),
resource_map_from_cluster => 0,
);
isa_ok $api->io, 'Kubernetes::REST::LWPIO', 'default IO is LWPIO';
};
subtest 'REST allows HTTPTinyIO override' => sub {
require Kubernetes::REST;
require Kubernetes::REST::Server;
require Kubernetes::REST::AuthToken;
require Kubernetes::REST::HTTPTinyIO;
my $api = Kubernetes::REST->new(
server => Kubernetes::REST::Server->new(endpoint => 'http://test.local'),
credentials => Kubernetes::REST::AuthToken->new(token => 'test'),
resource_map_from_cluster => 0,
io => Kubernetes::REST::HTTPTinyIO->new,
);
isa_ok $api->io, 'Kubernetes::REST::HTTPTinyIO', 'can override with HTTPTinyIO';
};
done_testing;
# ============================================================================
t/14_kubeconfig.t view on Meta::CPAN
subtest 'context - not found' => sub {
my $kc = Kubernetes::REST::Kubeconfig->new(kubeconfig_path => $config_file);
throws_ok { $kc->context('nonexistent') } qr/Context not found: nonexistent/,
'throws for unknown context';
};
subtest 'cluster - lookup by name' => sub {
my $kc = Kubernetes::REST::Kubeconfig->new(kubeconfig_path => $config_file);
my $cluster = $kc->cluster('prod-cluster');
is $cluster->{server}, 'https://prod.k8s.local:6443', 'cluster server endpoint';
is $cluster->{'certificate-authority'}, $ca_file, 'cluster CA file';
};
subtest 'cluster - not found' => sub {
my $kc = Kubernetes::REST::Kubeconfig->new(kubeconfig_path => $config_file);
throws_ok { $kc->cluster('nonexistent') } qr/Cluster not found/,
'throws for unknown cluster';
};
subtest 'user - token user' => sub {
t/14_kubeconfig.t view on Meta::CPAN
subtest 'user - not found' => sub {
my $kc = Kubernetes::REST::Kubeconfig->new(kubeconfig_path => $config_file);
throws_ok { $kc->user('nonexistent') } qr/User not found/,
'throws for unknown user';
};
subtest 'api - token auth with CA file' => sub {
my $kc = Kubernetes::REST::Kubeconfig->new(kubeconfig_path => $config_file);
my $api = $kc->api('production');
isa_ok $api, 'Kubernetes::REST';
is $api->server->endpoint, 'https://prod.k8s.local:6443', 'server endpoint';
is $api->server->ssl_verify_server, 1, 'SSL verification enabled';
is $api->server->ssl_ca_file, $ca_file, 'CA file set';
is $api->credentials->token, 'my-secret-token-12345', 'token set';
};
subtest 'api - cert auth with cert file references' => sub {
my $kc = Kubernetes::REST::Kubeconfig->new(kubeconfig_path => $config_file);
my $api = $kc->api('development');
isa_ok $api, 'Kubernetes::REST';
is $api->server->endpoint, 'https://dev.k8s.local:6443', 'dev server endpoint';
is $api->server->ssl_cert_file, $cert_file, 'client cert file set';
is $api->server->ssl_key_file, $key_file, 'client key file set';
# CA from base64 data should be in-memory PEM, not a file
ok $api->server->ssl_ca_pem, 'CA PEM data set (from base64 data)';
ok !$api->server->ssl_ca_file, 'no CA file (in-memory instead)';
};
subtest 'api - insecure skip TLS verify' => sub {
my $kc = Kubernetes::REST::Kubeconfig->new(kubeconfig_path => $config_file);
my $api = $kc->api('insecure');
is $api->server->ssl_verify_server, 0, 'SSL verification disabled';
# No auth user should get empty token
is $api->credentials->token, '', 'empty token for no-auth user';
};
subtest 'api - default context' => sub {
my $kc = Kubernetes::REST::Kubeconfig->new(kubeconfig_path => $config_file);
my $api = $kc->api;
is $api->server->endpoint, 'https://prod.k8s.local:6443',
'api() without args uses current-context';
};
subtest 'api - with context_name constructor arg' => sub {
my $kc = Kubernetes::REST::Kubeconfig->new(
kubeconfig_path => $config_file,
context_name => 'development',
);
my $api = $kc->api;
is $api->server->endpoint, 'https://dev.k8s.local:6443',
'api() uses context_name from constructor';
};
subtest 'missing kubeconfig file' => sub {
my $kc = Kubernetes::REST::Kubeconfig->new(
kubeconfig_path => '/nonexistent/kubeconfig',
);
throws_ok { $kc->contexts } qr/Kubeconfig not found/,
'throws for missing kubeconfig file';
};
t/15_advanced_api.t view on Meta::CPAN
lives_ok { $req->authenticate } 'authenticate with no credentials does not die';
ok !exists $req->headers->{Authorization}, 'no Authorization header set';
};
subtest 'HTTPRequest - url builder from server + uri' => sub {
require Kubernetes::REST::Server;
my $req = Kubernetes::REST::HTTPRequest->new(
method => 'GET',
uri => '/api/v1/pods',
headers => {},
server => Kubernetes::REST::Server->new(endpoint => 'https://k8s.local:6443'),
);
is $req->url, 'https://k8s.local:6443/api/v1/pods', 'url built from server + uri';
};
# ============================================================================
# REST.pm - fetch_resource_map with mock OpenAPI spec
# ============================================================================
subtest 'fetch_resource_map - parses OpenAPI spec' => sub {
my $api = mock_api();
t/16_cli_watch.t view on Meta::CPAN
}],
users => [{
name => 'test-user',
user => { token => 'test-token' },
}],
});
my $w = make_watcher(kubeconfig => $kc_file);
my $api = $w->api;
isa_ok $api, 'Kubernetes::REST', 'api built from kubeconfig';
is $api->server->endpoint, 'https://test.k8s.local:6443', 'server endpoint from kubeconfig';
is $api->credentials->token, 'test-token', 'token from kubeconfig';
};
subtest 'CLI::Role::Connection - api with context override' => sub {
use File::Temp qw(tempdir);
use YAML::XS ();
my $tmpdir = tempdir(CLEANUP => 1);
my $kc_file = "$tmpdir/kubeconfig";
t/16_cli_watch.t view on Meta::CPAN
],
contexts => [
{ name => 'default', context => { cluster => 'c1', user => 'u1' } },
{ name => 'other', context => { cluster => 'c2', user => 'u1' } },
],
users => [{ name => 'u1', user => { token => 'tok' } }],
});
my $w = make_watcher(kubeconfig => $kc_file, context => 'other');
my $api = $w->api;
is $api->server->endpoint, 'https://c2.local', 'context override selects correct cluster';
};
done_testing;
use Test::Kubernetes::Mock qw(mock_api);
use Kubernetes::REST;
use Kubernetes::REST::Server;
use Kubernetes::REST::AuthToken;
use Kubernetes::REST::LogEvent;
my $mock_io = Test::Kubernetes::Mock::IO->new;
my $api = Kubernetes::REST->new(
server => Kubernetes::REST::Server->new(endpoint => 'http://mock.local'),
credentials => Kubernetes::REST::AuthToken->new(token => 'MockToken'),
resource_map_from_cluster => 0,
io => $mock_io,
);
# === Test 1: One-shot log retrieval ===
subtest 'one-shot log' => sub {
$mock_io->add_log_lines('/api/v1/namespaces/default/pods/nginx-abc/log', [
'2024-01-01T00:00:00Z Starting nginx',
'2024-01-01T00:00:01Z Listening on port 80',
t/21_port_forward.t view on Meta::CPAN
my ($self, $req, %opts) = @_;
$self->last_req($req);
$self->last_opts(\%opts);
return { ok => 1, type => 'duplex-session' };
}
}
sub make_api {
my ($io) = @_;
return Kubernetes::REST->new(
server => Kubernetes::REST::Server->new(endpoint => 'https://mock.local'),
credentials => Kubernetes::REST::AuthToken->new(token => 'MockToken'),
resource_map_from_cluster => 0,
io => $io,
);
}
subtest 'supports_duplex probe' => sub {
my $basic = Test::PF::BasicIO->new;
my $duplex = Test::PF::DuplexIO->new;
ok(!$basic->supports_duplex, 'basic backend reports no duplex support');
t/22_exec.t view on Meta::CPAN
my ($self, $req, %opts) = @_;
$self->last_req($req);
$self->last_opts(\%opts);
return { ok => 1, type => 'duplex-session' };
}
}
sub make_api {
my ($io) = @_;
return Kubernetes::REST->new(
server => Kubernetes::REST::Server->new(endpoint => 'https://mock.local'),
credentials => Kubernetes::REST::AuthToken->new(token => 'MockToken'),
resource_map_from_cluster => 0,
io => $io,
);
}
subtest 'exec requires name and command' => sub {
my $api = make_api(Test::Exec::DuplexIO->new);
throws_ok {
t/23_attach.t view on Meta::CPAN
my ($self, $req, %opts) = @_;
$self->last_req($req);
$self->last_opts(\%opts);
return { ok => 1, type => 'duplex-session' };
}
}
sub make_api {
my ($io) = @_;
return Kubernetes::REST->new(
server => Kubernetes::REST::Server->new(endpoint => 'https://mock.local'),
credentials => Kubernetes::REST::AuthToken->new(token => 'MockToken'),
resource_map_from_cluster => 0,
io => $io,
);
}
subtest 'attach requires name' => sub {
my $api = make_api(Test::Attach::DuplexIO->new);
throws_ok {
t/lib/Test/Kubernetes/Mock.pm view on Meta::CPAN
return 1;
}
# Get API - either mock or live based on environment
sub mock_api {
require Kubernetes::REST;
require Kubernetes::REST::Server;
require Kubernetes::REST::AuthToken;
return Kubernetes::REST->new(
server => Kubernetes::REST::Server->new(endpoint => 'http://mock.local'),
credentials => Kubernetes::REST::AuthToken->new(token => 'MockToken'),
resource_map_from_cluster => 0,
io => Test::Kubernetes::Mock::IO->new,
);
}
sub live_api {
require Kubernetes::REST::Kubeconfig;
die "TEST_KUBERNETES_REST_KUBECONFIG must be set for live tests"
unless $ENV{TEST_KUBERNETES_REST_KUBECONFIG};