Kubernetes-REST

 view release on metacpan or  search on metacpan

lib/Kubernetes/REST/Kubeconfig.pm  view on Meta::CPAN

        return $item if $item->{name} eq $name;
    }
    return undef;
}

sub context {
    my ($self, $name) = @_;
    $name //= $self->current_context_name;
    my $ctx = $self->_find_by_name($self->_config->{contexts}, $name)
        or croak "Context not found: $name";
    return $ctx->{context};
}

sub cluster {
    my ($self, $name) = @_;
    my $cluster = $self->_find_by_name($self->_config->{clusters}, $name)
        or croak "Cluster not found: $name";
    return $cluster->{cluster};
}

sub user {
    my ($self, $name) = @_;
    my $user = $self->_find_by_name($self->_config->{users}, $name)
        or croak "User not found: $name";
    return $user->{user};
}

sub _resolve_cert {
    my ($self, $hash, $key) = @_;

    my $data_key = "${key}-data";
    if (my $data = $hash->{$data_key}) {
        return (pem => decode_base64($data));
    }

    if (my $file = $hash->{$key}) {
        return (file => $file);
    }

    return ();
}

sub api {
    my ($self, $context_name) = @_;


    # If no kubeconfig, try in-cluster
    unless ($self->_config) {
        return $self->_in_cluster_api
            // croak "Kubeconfig not found: " . $self->kubeconfig_path
                   . " 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;
    }

    if (my %cert = $self->_resolve_cert($user, 'client-certificate')) {
        $server{ $cert{pem} ? 'ssl_cert_pem' : 'ssl_cert_file' } = $cert{pem} // $cert{file};
    }

    if (my %key = $self->_resolve_cert($user, 'client-key')) {
        $server{ $key{pem} ? 'ssl_key_pem' : 'ssl_key_file' } = $key{pem} // $key{file};
    }

    # Build credentials
    my $credentials;
    if (my $token = $user->{token}) {
        $credentials = Kubernetes::REST::AuthToken->new(token => $token);
    } elsif (my $exec = $user->{exec}) {
        $credentials = $self->_exec_credential($exec);
    } else {
        # No token auth, might be using client certs only
        $credentials = Kubernetes::REST::AuthToken->new(token => '');
    }

    return Kubernetes::REST->new(
        server => Kubernetes::REST::Server->new(%server),
        credentials => $credentials,
    );
}

sub _exec_credential {
    my ($self, $exec) = @_;

    my $cmd = $exec->{command};
    my @args = @{$exec->{args} // []};

    # Set up environment
    local %ENV = %ENV;
    for my $env (@{$exec->{env} // []}) {
        $ENV{$env->{name}} = $env->{value};
    }

    my $output = `$cmd @args`;
    croak "exec credential command failed: $cmd" if $?;

    my $cred = YAML::XS::Load($output);
    my $token = $cred->{status}{token}
        or croak "exec credential did not return token";

    return Kubernetes::REST::AuthToken->new(token => $token);
}

my $SA_TOKEN = '/var/run/secrets/kubernetes.io/serviceaccount/token';
my $SA_CA    = '/var/run/secrets/kubernetes.io/serviceaccount/ca.crt';

sub _in_cluster_api {
    my ($self) = @_;
    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__

=pod

=encoding UTF-8

=head1 NAME

Kubernetes::REST::Kubeconfig - Parse kubeconfig files and create Kubernetes::REST instances

=head1 VERSION

version 1.104

=head1 SYNOPSIS

    use Kubernetes::REST::Kubeconfig;

    # Use default kubeconfig and current context
    my $kc = Kubernetes::REST::Kubeconfig->new;
    my $api = $kc->api;

    # Specify kubeconfig and context
    my $kc = Kubernetes::REST::Kubeconfig->new(
        kubeconfig_path => '/path/to/kubeconfig',
        context_name => 'my-cluster',
    );

    # List available contexts
    my $contexts = $kc->contexts;

    # Get API for specific context
    my $api = $kc->api('production');

    # Inside a Kubernetes pod: no kubeconfig needed, auto-detects service account
    my $api = Kubernetes::REST::Kubeconfig->new->api;

=head1 DESCRIPTION

Parses Kubernetes kubeconfig files (typically C<~/.kube/config>) and creates configured L<Kubernetes::REST> instances.

When no kubeconfig file is found, automatically falls back to in-cluster
authentication using the pod's service account token.

Supports:

=over 4

=item * Multiple clusters and contexts

=item * Token authentication



( run in 0.878 second using v1.01-cache-2.11-cpan-524268b4103 )