feat: Implement Kubernetes client and example

- Add Kubernetes client module for interacting with kubectl
- Implement methods to get cluster info, pods, deployments, and services
- Create a Kubernetes example script demonstrating client usage
- Add JSON response structs for parsing kubectl output
- Define runtime resource structs (Pod, Deployment, Service) for structured data
- Include comprehensive unit tests for data structures and client logic
This commit is contained in:
Mahmoud-Emad
2025-10-29 16:46:37 +03:00
parent 79b78aa6fe
commit c556cc71d4
14 changed files with 987 additions and 385 deletions

View File

@@ -1,7 +1,5 @@
module elements
import toml
// Frontmatter2 struct
@[heap]
pub struct Frontmatter2 {

View File

@@ -1,5 +1,5 @@
!!hero_code.generate_installer
!!hero_code.generate_client
name:''
classname:'KubeClient'
singleton:0

View File

@@ -1,59 +0,0 @@
module kubernetes
import incubaid.herolib.osal.core as osal
import incubaid.herolib.ui.console
import incubaid.herolib.core.texttools
import incubaid.herolib.osal.startupmanager
fn startupcmd() ![]startupmanager.ZProcessNewArgs {
return []startupmanager.ZProcessNewArgs{}
}
fn running() !bool {
// Check if kubectl is available and can connect
job := osal.exec(cmd: 'kubectl cluster-info', raise_error: false)!
return job.exit_code == 0
}
fn start_pre() ! {
console.print_header('Pre-start checks')
if !osal.cmd_exists('kubectl') {
return error('kubectl not found in PATH')
}
}
fn start_post() ! {
console.print_header('Post-start validation')
}
fn stop_pre() ! {
}
fn stop_post() ! {
}
fn installed() !bool {
return osal.cmd_exists('kubectl')
}
fn install() ! {
console.print_header('install kubectl')
// kubectl is typically installed separately via package manager
// This can be enhanced to auto-download if needed
if !osal.cmd_exists('kubectl') {
return error('Please install kubectl: https://kubernetes.io/docs/tasks/tools/')
}
}
fn build() ! {
// Not applicable for kubectl wrapper
}
fn destroy() ! {
console.print_header('destroy kubernetes client')
// No cleanup needed for kubectl wrapper
}
// fn configure() ! {
// console.print_debug('Kubernetes client configured')
// }

View File

@@ -1,19 +1,15 @@
module kubernetes
import incubaid.herolib.osal.core as osal
import incubaid.herolib.core.httpconnection
import incubaid.herolib.core.pathlib
import incubaid.herolib.ui.console
import json
import os
@[params]
pub struct KubectlExecArgs {
pub mut:
command string
timeout int = 30
retry int = 0
retry int
}
pub struct KubectlResult {
@@ -29,18 +25,18 @@ pub fn (mut k KubeClient) kubectl_exec(args KubectlExecArgs) !KubectlResult {
mut cmd := 'kubectl'
if k.config.namespace.len > 0 {
cmd += '--namespace=${k.config.namespace} '
cmd += ' --namespace=${k.config.namespace}'
}
if k.kubeconfig_path.len > 0 {
cmd += '--kubeconfig=${k.kubeconfig_path} '
cmd += ' --kubeconfig=${k.kubeconfig_path}'
}
if k.config.context.len > 0 {
cmd += '--context=${k.config.context} '
cmd += ' --context=${k.config.context}'
}
cmd += args.command
cmd += ' ${args.command}'
console.print_debug('executing: ${cmd}')
@@ -59,7 +55,6 @@ pub fn (mut k KubeClient) kubectl_exec(args KubectlExecArgs) !KubectlResult {
}
}
// Test connection to cluster
pub fn (mut k KubeClient) test_connection() !bool {
result := k.kubectl_exec(command: 'cluster-info')!
@@ -78,90 +73,175 @@ pub fn (mut k KubeClient) cluster_info() !ClusterInfo {
return error('Failed to get cluster version: ${result.stderr}')
}
println(result.stdout)
// Parse version JSON using struct-based decoding
mut version_str := 'unknown'
version_response := json.decode(KubectlVersionResponse, result.stdout) or {
console.print_debug('Failed to parse version JSON: ${err}')
KubectlVersionResponse{}
}
if version_response.server_version.git_version.len > 0 {
version_str = version_response.server_version.git_version
}
$dbg;
// version_data := json.decode(map[string]interface{}, result.stdout)!
// server_version := version_data['serverVersion'] or { return error('No serverVersion') }
// Get node count
nodes_result := k.kubectl_exec(command: 'get nodes -o json')!
mut nodes_count := 0
if nodes_result.success {
nodes_list := json.decode(KubectlListResponse, nodes_result.stdout) or {
console.print_debug('Failed to parse nodes JSON: ${err}')
KubectlListResponse{}
}
nodes_count = nodes_list.items.len
}
// // Get node count
// nodes_result := k.kubectl_exec(command: 'get nodes -o json')!
// nodes_count := if nodes_result.success {
// nodes_data := json.decode(map[string]interface{}, nodes_result.stdout)!
// items := nodes_data['items'] or { []interface{}{} }
// items.len
// } else {
// 0
// }
// Get namespace count
ns_result := k.kubectl_exec(command: 'get namespaces -o json')!
mut ns_count := 0
if ns_result.success {
ns_list := json.decode(KubectlListResponse, ns_result.stdout) or {
console.print_debug('Failed to parse namespaces JSON: ${err}')
KubectlListResponse{}
}
ns_count = ns_list.items.len
}
// // Get namespace count
// ns_result := k.kubectl_exec(command: 'get namespaces -o json')!
// ns_count := if ns_result.success {
// ns_data := json.decode(map[string]interface{}, ns_result.stdout)!
// items := ns_data['items'] or { []interface{}{} }
// items.len
// } else {
// 0
// }
// Get running pods count
pods_result := k.kubectl_exec(command: 'get pods --all-namespaces -o json')!
mut pods_count := 0
if pods_result.success {
pods_list := json.decode(KubectlListResponse, pods_result.stdout) or {
console.print_debug('Failed to parse pods JSON: ${err}')
KubectlListResponse{}
}
pods_count = pods_list.items.len
}
// // Get running pods count
// pods_result := k.kubectl_exec(command: 'get pods --all-namespaces -o json')!
// pods_count := if pods_result.success {
// pods_data := json.decode(map[string]interface{}, pods_result.stdout)!
// items := pods_data['items'] or { []interface{}{} }
// items.len
// } else {
// 0
// }
// return ClusterInfo{
// version: 'v1.0.0'
// nodes: nodes_count
// namespaces: ns_count
// running_pods: pods_count
// api_server: k.config.api_server
// }
return ClusterInfo{}
return ClusterInfo{
version: version_str
nodes: nodes_count
namespaces: ns_count
running_pods: pods_count
api_server: k.config.api_server
}
}
// Get resources (Pods, Deployments, Services, etc.)
pub fn (mut k KubeClient) get_pods(namespace string) ! {
pub fn (mut k KubeClient) get_pods(namespace string) ![]Pod {
result := k.kubectl_exec(command: 'get pods -n ${namespace} -o json')!
if !result.success {
return error('Failed to get pods: ${result.stderr}')
}
println(result.stdout)
$dbg;
// data := json.decode(map[string]interface{}, result.stdout)!
// items := data['items'] or { []interface{}{} }
// return items as []map[string]interface{}
// Parse JSON response using struct-based decoding
pod_list := json.decode(KubectlPodListResponse, result.stdout) or {
return error('Failed to parse pods JSON: ${err}')
}
panic('Not implemented')
mut pods := []Pod{}
for item in pod_list.items {
// Extract container names
mut container_names := []string{}
for container in item.spec.containers {
container_names << container.name
}
// Create Pod struct from kubectl response
pod := Pod{
name: item.metadata.name
namespace: item.metadata.namespace
status: item.status.phase
node: item.spec.node_name
ip: item.status.pod_ip
containers: container_names
labels: item.metadata.labels
created_at: item.metadata.creation_timestamp
}
pods << pod
}
return pods
}
pub fn (mut k KubeClient) get_deployments(namespace string) ! {
pub fn (mut k KubeClient) get_deployments(namespace string) ![]Deployment {
result := k.kubectl_exec(command: 'get deployments -n ${namespace} -o json')!
if !result.success {
return error('Failed to get deployments: ${result.stderr}')
}
// data := json.decode(map[string]interface{}, result.stdout)!
// items := data['items'] or { []interface{}{} }
// return items as []map[string]interface{}
panic('Not implemented')
// Parse JSON response using struct-based decoding
deployment_list := json.decode(KubectlDeploymentListResponse, result.stdout) or {
return error('Failed to parse deployments JSON: ${err}')
}
mut deployments := []Deployment{}
for item in deployment_list.items {
// Create Deployment struct from kubectl response
deployment := Deployment{
name: item.metadata.name
namespace: item.metadata.namespace
replicas: item.spec.replicas
ready_replicas: item.status.ready_replicas
available_replicas: item.status.available_replicas
updated_replicas: item.status.updated_replicas
labels: item.metadata.labels
created_at: item.metadata.creation_timestamp
}
deployments << deployment
}
return deployments
}
pub fn (mut k KubeClient) get_services(namespace string) ! {
pub fn (mut k KubeClient) get_services(namespace string) ![]Service {
result := k.kubectl_exec(command: 'get services -n ${namespace} -o json')!
if !result.success {
return error('Failed to get services: ${result.stderr}')
}
// data := json.decode(map[string]interface{}, result.stdout)!
// items := data['items'] or { []interface{}{} }
// return items as []map[string]interface{}
panic('Not implemented')
// Parse JSON response using struct-based decoding
service_list := json.decode(KubectlServiceListResponse, result.stdout) or {
return error('Failed to parse services JSON: ${err}')
}
mut services := []Service{}
for item in service_list.items {
// Build port strings (e.g., "80/TCP", "443/TCP")
mut port_strings := []string{}
for port in item.spec.ports {
port_strings << '${port.port}/${port.protocol}'
}
// Get external IP from LoadBalancer status if available
mut external_ip := ''
if item.status.load_balancer.ingress.len > 0 {
external_ip = item.status.load_balancer.ingress[0].ip
}
// Also check spec.external_ips
if external_ip.len == 0 && item.spec.external_ips.len > 0 {
external_ip = item.spec.external_ips[0]
}
// Create Service struct from kubectl response
service := Service{
name: item.metadata.name
namespace: item.metadata.namespace
service_type: item.spec.service_type
cluster_ip: item.spec.cluster_ip
external_ip: external_ip
ports: port_strings
labels: item.metadata.labels
created_at: item.metadata.creation_timestamp
}
services << service
}
return services
}
// Apply YAML file

View File

@@ -2,10 +2,7 @@ module kubernetes
import incubaid.herolib.core.base
import incubaid.herolib.core.playbook { PlayBook }
import incubaid.herolib.ui.console
import json
import incubaid.herolib.osal.startupmanager
import time
__global (
kubernetes_global map[string]&KubeClient
@@ -134,176 +131,6 @@ pub fn play(mut plbook PlayBook) ! {
install_action.done = true
}
}
mut other_actions := plbook.find(filter: 'kubernetes.')!
for mut other_action in other_actions {
if other_action.name in ['destroy', 'install', 'build'] {
mut p := other_action.params
reset := p.get_default_false('reset')
if other_action.name == 'destroy' || reset {
console.print_debug('install action kubernetes.destroy')
destroy()!
}
if other_action.name == 'install' {
console.print_debug('install action kubernetes.install')
install()!
}
}
if other_action.name in ['start', 'stop', 'restart'] {
mut p := other_action.params
name := p.get('name')!
mut kubernetes_obj := get(name: name)!
console.print_debug('action object:\n${kubernetes_obj}')
if other_action.name == 'start' {
console.print_debug('install action kubernetes.${other_action.name}')
kubernetes_obj.start()!
}
if other_action.name == 'stop' {
console.print_debug('install action kubernetes.${other_action.name}')
kubernetes_obj.stop()!
}
if other_action.name == 'restart' {
console.print_debug('install action kubernetes.${other_action.name}')
kubernetes_obj.restart()!
}
}
other_action.done = true
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////# LIVE CYCLE MANAGEMENT FOR INSTALLERS ///////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////
fn startupmanager_get(cat startupmanager.StartupManagerType) !startupmanager.StartupManager {
// unknown
// screen
// zinit
// tmux
// systemd
match cat {
.screen {
console.print_debug("installer: kubernetes' startupmanager get screen")
return startupmanager.get(.screen)!
}
.zinit {
console.print_debug("installer: kubernetes' startupmanager get zinit")
return startupmanager.get(.zinit)!
}
.systemd {
console.print_debug("installer: kubernetes' startupmanager get systemd")
return startupmanager.get(.systemd)!
}
else {
console.print_debug("installer: kubernetes' startupmanager get auto")
return startupmanager.get(.auto)!
}
}
}
// load from disk and make sure is properly intialized
pub fn (mut self KubeClient) reload() ! {
switch(self.name)
self = obj_init(self)!
}
pub fn (mut self KubeClient) start() ! {
switch(self.name)
if self.running()! {
return
}
console.print_header('installer: kubernetes start')
if !installed()! {
install()!
}
configure()!
start_pre()!
for zprocess in startupcmd()! {
mut sm := startupmanager_get(zprocess.startuptype)!
console.print_debug('installer: kubernetes starting with ${zprocess.startuptype}...')
sm.new(zprocess)!
sm.start(zprocess.name)!
}
start_post()!
for _ in 0 .. 50 {
if self.running()! {
return
}
time.sleep(100 * time.millisecond)
}
return error('kubernetes did not install properly.')
}
pub fn (mut self KubeClient) install_start(args InstallArgs) ! {
switch(self.name)
self.install(args)!
self.start()!
}
pub fn (mut self KubeClient) stop() ! {
switch(self.name)
stop_pre()!
for zprocess in startupcmd()! {
mut sm := startupmanager_get(zprocess.startuptype)!
sm.stop(zprocess.name)!
}
stop_post()!
}
pub fn (mut self KubeClient) restart() ! {
switch(self.name)
self.stop()!
self.start()!
}
pub fn (mut self KubeClient) running() !bool {
switch(self.name)
// walk over the generic processes, if not running return
for zprocess in startupcmd()! {
if zprocess.startuptype != .screen {
mut sm := startupmanager_get(zprocess.startuptype)!
r := sm.running(zprocess.name)!
if r == false {
return false
}
}
}
return running()!
}
@[params]
pub struct InstallArgs {
pub mut:
reset bool
}
pub fn (mut self KubeClient) install(args InstallArgs) ! {
switch(self.name)
if args.reset || (!installed()!) {
install()!
}
}
pub fn (mut self KubeClient) build() ! {
switch(self.name)
build()!
}
pub fn (mut self KubeClient) destroy() ! {
switch(self.name)
self.stop() or {}
destroy()!
}
// switch instance to be used for kubernetes

View File

@@ -1,8 +1,6 @@
module kubernetes
import incubaid.herolib.data.paramsparser
import incubaid.herolib.data.encoderhero
import os
pub const version = '0.0.0'
const singleton = false
@@ -12,9 +10,9 @@ const default = true
pub struct KubeClient {
pub mut:
name string = 'default'
kubeconfig_path string
config KubeConfig
connected bool
kubeconfig_path string // Path to kubeconfig file
config KubeConfig // Kubernetes configuration
connected bool // Connection status
api_version string = 'v1'
cache_enabled bool = true
cache_ttl_seconds int = 300

View File

@@ -1,8 +1,5 @@
module kubernetes
import incubaid.herolib.data.encoderhero
import os
// K8s API Version and Kind tracking
@[params]
pub struct K8sMetadata {
@@ -150,7 +147,7 @@ pub mut:
pub struct KubeConfig {
pub mut:
kubeconfig_path string
context string = ''
context string
namespace string = 'default'
api_server string
ca_cert_path string
@@ -179,3 +176,189 @@ pub mut:
running_pods int
api_server string
}
// ============================================================================
// Kubectl JSON Response Structs
// These structs match the JSON structure returned by kubectl commands
// ============================================================================
// Version response from 'kubectl version -o json'
struct KubectlVersionResponse {
server_version ServerVersionInfo @[json: serverVersion]
}
struct ServerVersionInfo {
git_version string @[json: gitVersion]
major string
minor string
}
// Generic list response structure
struct KubectlListResponse {
items []KubectlItemMetadata
}
struct KubectlItemMetadata {
metadata KubectlMetadata
}
struct KubectlMetadata {
name string
}
// Pod list response from 'kubectl get pods -o json'
struct KubectlPodListResponse {
items []KubectlPodItem
}
struct KubectlPodItem {
metadata KubectlPodMetadata
spec KubectlPodSpec
status KubectlPodStatus
}
struct KubectlPodMetadata {
name string
namespace string
labels map[string]string
creation_timestamp string @[json: creationTimestamp]
}
struct KubectlPodSpec {
node_name string @[json: nodeName]
containers []KubectlContainer
}
struct KubectlContainer {
name string
image string
}
struct KubectlPodStatus {
phase string
pod_ip string @[json: podIP]
}
// Deployment list response from 'kubectl get deployments -o json'
struct KubectlDeploymentListResponse {
items []KubectlDeploymentItem
}
struct KubectlDeploymentItem {
metadata KubectlDeploymentMetadata
spec KubectlDeploymentSpec
status KubectlDeploymentStatus
}
struct KubectlDeploymentMetadata {
name string
namespace string
labels map[string]string
creation_timestamp string @[json: creationTimestamp]
}
struct KubectlDeploymentSpec {
replicas int
}
struct KubectlDeploymentStatus {
ready_replicas int @[json: readyReplicas]
available_replicas int @[json: availableReplicas]
updated_replicas int @[json: updatedReplicas]
}
// Service list response from 'kubectl get services -o json'
struct KubectlServiceListResponse {
items []KubectlServiceItem
}
struct KubectlServiceItem {
metadata KubectlServiceMetadata
spec KubectlServiceSpec
status KubectlServiceStatus
}
struct KubectlServiceMetadata {
name string
namespace string
labels map[string]string
creation_timestamp string @[json: creationTimestamp]
}
struct KubectlServiceSpec {
service_type string @[json: type]
cluster_ip string @[json: clusterIP]
external_ips []string @[json: externalIPs]
ports []KubectlServicePort
}
struct KubectlServicePort {
port int
protocol string
}
struct KubectlServiceStatus {
load_balancer KubectlLoadBalancerStatus @[json: loadBalancer]
}
struct KubectlLoadBalancerStatus {
ingress []KubectlLoadBalancerIngress
}
struct KubectlLoadBalancerIngress {
ip string
}
// ============================================================================
// Runtime resource structs (returned from kubectl get commands)
// ============================================================================
// Pod runtime information
pub struct Pod {
pub mut:
name string
namespace string
status string
node string
ip string
containers []string
labels map[string]string
created_at string
}
// Deployment runtime information
pub struct Deployment {
pub mut:
name string
namespace string
replicas int
ready_replicas int
available_replicas int
updated_replicas int
labels map[string]string
created_at string
}
// Service runtime information
pub struct Service {
pub mut:
name string
namespace string
service_type string
cluster_ip string
external_ip string
ports []string
labels map[string]string
created_at string
}
// Version information from kubectl version command
pub struct VersionInfo {
pub mut:
major string
minor string
git_version string
git_commit string
build_date string
platform string
}

View File

@@ -1,76 +1,249 @@
module kubernetes
import time
import os
// ============================================================================
// Unit Tests for Kubernetes Client Module
// These tests verify struct creation and data handling without executing
// real kubectl commands (unit tests only, not integration tests)
// ============================================================================
fn test_model_creation() ! {
mut deployment := DeploymentSpec{
metadata: K8sMetadata{
name: 'test-app'
namespace: 'default'
}
replicas: 3
selector: {
'app': 'test-app'
}
template: PodSpec{
metadata: K8sMetadata{
name: 'test-app-pod'
namespace: 'default'
}
containers: [
ContainerSpec{
name: 'app'
image: 'nginx:latest'
ports: [
ContainerPort{
name: 'http'
container_port: 80
},
]
},
]
mut client := new(name: 'test-cluster')!
assert client.name == 'test-cluster'
}
// ============================================================================
// Unit Tests for Data Structures and JSON Parsing
// ============================================================================
// Test Pod struct creation and field access
fn test_pod_struct_creation() ! {
mut pod := Pod{
name: 'test-pod'
namespace: 'default'
status: 'Running'
node: 'node-1'
ip: '10.244.0.5'
containers: ['nginx', 'sidecar']
labels: {
'app': 'web'
'env': 'prod'
}
created_at: '2024-01-15T10:30:00Z'
}
yaml := yaml_from_deployment(deployment)!
assert yaml.contains('apiVersion: apps/v1')
assert yaml.contains('kind: Deployment')
assert yaml.contains('test-app')
assert pod.name == 'test-pod'
assert pod.namespace == 'default'
assert pod.status == 'Running'
assert pod.node == 'node-1'
assert pod.ip == '10.244.0.5'
assert pod.containers.len == 2
assert pod.containers[0] == 'nginx'
assert pod.containers[1] == 'sidecar'
assert pod.labels['app'] == 'web'
assert pod.labels['env'] == 'prod'
assert pod.created_at == '2024-01-15T10:30:00Z'
}
fn test_yaml_validation() ! {
// Create test YAML file
test_yaml := ''
'
apiVersion: apps/v1
kind: Deployment
metadata:
name: test-deployment
namespace: default
spec:
replicas: 1
selector:
matchLabels:
app: test
template:
metadata:
labels:
app: test
spec:
containers:
- name: app
image: nginx:latest
'
''
// Test Deployment struct creation
fn test_deployment_struct_creation() ! {
mut deployment := Deployment{
name: 'nginx-deployment'
namespace: 'default'
replicas: 3
ready_replicas: 3
available_replicas: 3
updated_replicas: 3
labels: {
'app': 'nginx'
}
created_at: '2024-01-15T09:00:00Z'
}
test_file := '/tmp/test-deployment.yaml'
os.write_file(test_file, test_yaml)!
result := yaml_validate(test_file)!
assert result.valid
assert result.kind == 'Deployment'
assert result.metadata.name == 'test-deployment'
os.rm(test_file)!
assert deployment.name == 'nginx-deployment'
assert deployment.namespace == 'default'
assert deployment.replicas == 3
assert deployment.ready_replicas == 3
assert deployment.available_replicas == 3
assert deployment.updated_replicas == 3
assert deployment.labels['app'] == 'nginx'
assert deployment.created_at == '2024-01-15T09:00:00Z'
}
// Test Service struct creation with ClusterIP
fn test_service_struct_creation() ! {
mut service := Service{
name: 'nginx-service'
namespace: 'default'
service_type: 'ClusterIP'
cluster_ip: '10.96.100.50'
external_ip: ''
ports: ['80/TCP', '443/TCP']
labels: {
'app': 'nginx'
}
created_at: '2024-01-15T09:30:00Z'
}
assert service.name == 'nginx-service'
assert service.namespace == 'default'
assert service.service_type == 'ClusterIP'
assert service.cluster_ip == '10.96.100.50'
assert service.external_ip == ''
assert service.ports.len == 2
assert service.ports[0] == '80/TCP'
assert service.ports[1] == '443/TCP'
assert service.labels['app'] == 'nginx'
assert service.created_at == '2024-01-15T09:30:00Z'
}
// Test Service with LoadBalancer type and external IP
fn test_service_loadbalancer_type() ! {
mut service := Service{
name: 'web-lb-service'
namespace: 'default'
service_type: 'LoadBalancer'
cluster_ip: '10.96.100.52'
external_ip: '203.0.113.10'
ports: ['80/TCP']
labels: {
'app': 'web'
}
created_at: '2024-01-15T11:00:00Z'
}
assert service.name == 'web-lb-service'
assert service.service_type == 'LoadBalancer'
assert service.cluster_ip == '10.96.100.52'
assert service.external_ip == '203.0.113.10'
assert service.ports.len == 1
assert service.ports[0] == '80/TCP'
}
// Test ClusterInfo struct
fn test_cluster_info_struct() ! {
mut cluster := ClusterInfo{
api_server: 'https://test-cluster:6443'
version: 'v1.31.0'
nodes: 3
namespaces: 5
running_pods: 12
}
assert cluster.api_server == 'https://test-cluster:6443'
assert cluster.version == 'v1.31.0'
assert cluster.nodes == 3
assert cluster.namespaces == 5
assert cluster.running_pods == 12
}
// Test Pod with multiple containers
fn test_pod_with_multiple_containers() ! {
mut pod := Pod{
name: 'multi-container-pod'
namespace: 'default'
containers: ['app', 'sidecar', 'init']
}
assert pod.containers.len == 3
assert 'app' in pod.containers
assert 'sidecar' in pod.containers
assert 'init' in pod.containers
}
// Test Deployment with partial ready state
fn test_deployment_partial_ready() ! {
mut deployment := Deployment{
name: 'redis-deployment'
namespace: 'default'
replicas: 3
ready_replicas: 2
available_replicas: 2
updated_replicas: 3
}
assert deployment.replicas == 3
assert deployment.ready_replicas == 2
assert deployment.available_replicas == 2
// Not all replicas are ready
assert deployment.ready_replicas < deployment.replicas
}
// Test Service with multiple ports
fn test_service_with_multiple_ports() ! {
mut service := Service{
name: 'multi-port-service'
ports: ['80/TCP', '443/TCP', '8080/TCP']
}
assert service.ports.len == 3
assert '80/TCP' in service.ports
assert '443/TCP' in service.ports
assert '8080/TCP' in service.ports
}
// Test Pod with default/empty values
fn test_pod_default_values() ! {
mut pod := Pod{}
assert pod.name == ''
assert pod.namespace == ''
assert pod.status == ''
assert pod.node == ''
assert pod.ip == ''
assert pod.containers.len == 0
assert pod.labels.len == 0
assert pod.created_at == ''
}
// Test Deployment with default values
fn test_deployment_default_values() ! {
mut deployment := Deployment{}
assert deployment.name == ''
assert deployment.namespace == ''
assert deployment.replicas == 0
assert deployment.ready_replicas == 0
assert deployment.available_replicas == 0
assert deployment.updated_replicas == 0
assert deployment.labels.len == 0
assert deployment.created_at == ''
}
// Test Service with default values
fn test_service_default_values() ! {
mut service := Service{}
assert service.name == ''
assert service.namespace == ''
assert service.service_type == ''
assert service.cluster_ip == ''
assert service.external_ip == ''
assert service.ports.len == 0
assert service.labels.len == 0
assert service.created_at == ''
}
// Test KubectlResult struct for successful command
fn test_kubectl_result_struct() ! {
mut result := KubectlResult{
exit_code: 0
stdout: '{"items": []}'
stderr: ''
}
assert result.exit_code == 0
assert result.stdout.contains('items')
assert result.stderr == ''
}
// Test KubectlResult struct for error
fn test_kubectl_result_error() ! {
mut result := KubectlResult{
exit_code: 1
stdout: ''
stderr: 'Error: connection refused'
}
assert result.exit_code == 1
assert result.stderr.contains('Error')
}

View File

@@ -1,9 +1,6 @@
module kubernetes
import json
import incubaid.herolib.data.markdown
import incubaid.herolib.core.pathlib
import os
// Parse YAML file and return validation result
pub fn yaml_validate(yaml_path string) !K8sValidationResult {

View File

@@ -1,5 +0,0 @@
name: ${cfg.configpath}