This commit is contained in:
2025-10-21 09:30:22 +02:00
parent 1cde14f640
commit 12ad7b1e6f
29 changed files with 1878 additions and 557 deletions

View File

@@ -1,7 +1,7 @@
!!hero_code.generate_installer
name:''
classname:'Kubernetes'
classname:'KubeClient'
singleton:0
templates:1
default:1

View File

@@ -1,265 +1,59 @@
module kubernetes
import incubaid.herolib.osal.core as osal
import incubaid.herolib.ui.console
import incubaid.herolib.core.texttools
import time
import incubaid.herolib.osal.startupmanager
@[params]
pub struct DeployArgs {
pub mut:
deployment Deployment
wait bool = true
timeout_seconds int = 300
poll_interval_ms int = 5000
fn startupcmd() ![]startupmanager.ZProcessNewArgs {
return []startupmanager.ZProcessNewArgs{}
}
// Deploy a Deployment resource
pub fn (mut c KubernetesClient) deploy(args DeployArgs) !Deployment {
console.print_header('Deploying ${args.deployment.metadata.name}')
// Check if deployment already exists
existing := c.get[Deployment](
resource_type: 'deployments'
name: args.deployment.metadata.name
namespace: args.deployment.metadata.namespace
) or { none }
mut deployed_deployment := match existing {
Deployment { c.update[Deployment](args.deployment)! }
else { c.create[Deployment](args.deployment)! }
}
console.print_green('Deployment created/updated: ${args.deployment.metadata.name}')
if args.wait {
c.wait_for_deployment_ready(
name: args.deployment.metadata.name
namespace: args.deployment.metadata.namespace
timeout_seconds: args.timeout_seconds
)!
}
return deployed_deployment
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
}
@[params]
pub struct WaitForDeploymentArgs {
pub mut:
name string
namespace string = 'default'
timeout_seconds int = 300
poll_interval_ms int = 5000
}
// Wait for deployment to be ready
pub fn (mut c KubernetesClient) wait_for_deployment_ready(args WaitForDeploymentArgs) ! {
start_time := time.now()
for {
deployment := c.get[Deployment](
resource_type: 'deployments'
name: args.name
namespace: args.namespace
)!
if deployment.status != none {
status := deployment.status!
if status.observed_generation >= deployment.metadata.resource_version.int() {
if status.updated_replicas == deployment.spec.replicas {
if status.ready_replicas == deployment.spec.replicas {
console.print_green('Deployment ${args.name} is ready')
return
}
}
}
}
elapsed := time.now().unix_time() - start_time.unix_time()
if elapsed > args.timeout_seconds {
return error('Timeout waiting for deployment ${args.name} to be ready')
}
console.print_debug('Waiting for deployment ${args.name}...')
time.sleep(args.poll_interval_ms * time.millisecond)
fn start_pre() ! {
console.print_header('Pre-start checks')
if !osal.cmd_exists('kubectl') {
return error('kubectl not found in PATH')
}
}
@[params]
pub struct DeleteArgs {
pub mut:
resource_type string // 'pods', 'deployments', 'services', etc.
name string
namespace string = 'default'
grace_period_seconds int = 30
wait bool = true
timeout_seconds int = 300
fn start_post() ! {
console.print_header('Post-start validation')
}
// Delete a resource
pub fn (mut c KubernetesClient) delete_resource(args DeleteArgs) ! {
console.print_header('Deleting ${args.resource_type}/${args.name}')
c.delete(
resource_type: args.resource_type
name: args.name
namespace: args.namespace
)!
console.print_green('${args.resource_type}/${args.name} deleted')
if args.wait {
start_time := time.now()
for {
c.get[map[string]interface{}](
resource_type: args.resource_type
name: args.name
namespace: args.namespace
) or {
console.print_green('${args.resource_type}/${args.name} fully removed')
return
}
elapsed := time.now().unix_time() - start_time.unix_time()
if elapsed > args.timeout_seconds {
return error('Timeout waiting for ${args.resource_type}/${args.name} to be deleted')
}
time.sleep(5000 * time.millisecond)
}
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/')
}
}
@[params]
pub struct ScaleArgs {
pub mut:
deployment_name string
namespace string = 'default'
replicas int
wait bool = true
timeout_seconds int = 300
fn build() ! {
// Not applicable for kubectl wrapper
}
// Scale a deployment
pub fn (mut c KubernetesClient) scale_deployment(args ScaleArgs) !Deployment {
console.print_header('Scaling ${args.deployment_name} to ${args.replicas} replicas')
// Patch the deployment spec.replicas field
patch_data := {
'spec': {
'replicas': args.replicas
}
}
mut deployment := c.patch[Deployment](
resource_type: 'deployments'
name: args.deployment_name
namespace: args.namespace
patch_data: patch_data
)!
if args.wait {
c.wait_for_deployment_ready(
name: args.deployment_name
namespace: args.namespace
timeout_seconds: args.timeout_seconds
)!
}
return deployment
fn destroy() ! {
console.print_header('destroy kubernetes client')
// No cleanup needed for kubectl wrapper
}
@[params]
pub struct RollingRestartArgs {
pub mut:
deployment_name string
namespace string = 'default'
wait bool = true
timeout_seconds int = 600
}
// Perform rolling restart of a deployment
pub fn (mut c KubernetesClient) rolling_restart(args RollingRestartArgs) ! {
console.print_header('Rolling restart of ${args.deployment_name}')
now_timestamp := time.now().format_rfc3339()
patch_data := {
'spec': {
'template': {
'metadata': {
'annotations': {
'kubectl.kubernetes.io/restartedAt': now_timestamp
}
}
}
}
}
c.patch[Deployment](
resource_type: 'deployments'
name: args.deployment_name
namespace: args.namespace
patch_data: patch_data
)!
if args.wait {
c.wait_for_deployment_ready(
name: args.deployment_name
namespace: args.namespace
timeout_seconds: args.timeout_seconds
)!
}
console.print_green('${args.deployment_name} rolling restart completed')
}
@[params]
pub struct GetLogsArgs {
pub mut:
pod_name string
container_name string
namespace string = 'default'
tail_lines int = 100
follow bool
}
// Get pod logs
pub fn (mut c KubernetesClient) get_pod_logs(args GetLogsArgs) !string {
mut prefix := '/api/v1/namespaces/${args.namespace}/pods/${args.pod_name}/log'
prefix += '?tailLines=${args.tail_lines}'
if args.container_name.len > 0 {
prefix += '&container=${args.container_name}'
}
conn := c.connection()!
return conn.get_text(prefix: prefix)!
}
@[params]
pub struct ExecArgs {
pub mut:
pod_name string
container_name string
command []string
namespace string = 'default'
}
// Execute command in pod (requires WebSocket support - future enhancement)
pub fn (mut c KubernetesClient) exec_in_pod(args ExecArgs) ! {
// TODO: Implement WebSocket-based exec
return error('exec_in_pod requires WebSocket support - not yet implemented')
}
@[params]
pub struct PortForwardArgs {
pub mut:
pod_name string
namespace string = 'default'
local_port int
remote_port int
}
// Port forward to pod (requires WebSocket support - future enhancement)
pub fn (mut c KubernetesClient) port_forward(args PortForwardArgs) ! {
// TODO: Implement WebSocket-based port forward
return error('port_forward requires WebSocket support - not yet implemented')
fn configure() ! {
console.print_debug('Kubernetes client configured')
}

View File

@@ -1,223 +1,231 @@
module kubernetes
import incubaid.herolib.osal.core as osal
import incubaid.herolib.core.httpconnection
import net.http
import incubaid.herolib.core.pathlib
import incubaid.herolib.ui.console
import json
import encoding.base64
import os
@[heap]
pub struct KubernetesClient {
// Execute kubectl command with proper error handling
pub fn (mut k KubeClient) kubectl_exec(args KubectlExecArgs) !KubectlResult {
mut cmd := '${k.kubectl_path} '
if !k.config.namespace.is_empty() {
cmd += '--namespace=${k.config.namespace} '
}
if !k.kubeconfig_path.is_empty() {
cmd += '--kubeconfig=${k.kubeconfig_path} '
}
if !k.config.context.is_empty() {
cmd += '--context=${k.config.context} '
}
cmd += args.command
console.print_debug("executing: ${cmd}")
job := osal.exec(
cmd: cmd
timeout: args.timeout
retry: args.retry
raise_error: false
)!
return KubectlResult{
exit_code: job.exit_code
stdout: job.output
stderr: job.error
success: job.exit_code == 0
}
}
@[params]
pub struct KubectlExecArgs {
pub mut:
config KubernetesConfig
http_conn ?&httpconnection.HTTPConnection
api_groups map[string]APIGroupVersion
command string
timeout int = 30
retry int = 0
}
pub struct APIGroupVersion {
pub:
group_version string
resources []APIResource
pub struct KubectlResult {
pub mut:
exit_code int
stdout string
stderr string
success bool
}
pub struct APIResource {
pub:
name string
singularname string
namespaced bool
kind string
verbs []string // get, list, create, update, delete, patch, watch, etc.
}
pub struct APIResponse {
pub:
kind string
api_version string
metadata map[string]interface{}
items []json.Any
}
// Create new Kubernetes client
pub fn new(config KubernetesConfig) !&KubernetesClient {
mut cfg := config
cfg.validate()!
mut client := KubernetesClient{
config: cfg
// Test connection to cluster
pub fn (mut k KubeClient) test_connection() !bool {
result := k.kubectl_exec(command: 'cluster-info')!
if result.success {
k.connected = true
return true
}
client.http_conn = http_connection_create(cfg)!
return &client
return false
}
// Create HTTP connection with proper auth
fn http_connection_create(cfg KubernetesConfig) !&httpconnection.HTTPConnection {
mut conn := httpconnection.new(
name: 'kubernetes-${cfg.name}'
url: cfg.server
retry: 3
cache: false
)!
// Setup authentication
if cfg.token.len > 0 {
mut header := http.new_header()
header.add(.authorization, 'Bearer ${cfg.token}')
conn.default_header = header
} else if cfg.username.len > 0 && cfg.password.len > 0 {
conn.basic_auth(cfg.username, cfg.password)
// Get cluster info
pub fn (mut k KubeClient) cluster_info() !ClusterInfo {
// Get API server version
result := k.kubectl_exec(command: 'version -o json')!
if !result.success {
return error('Failed to get cluster version: ${result.stderr}')
}
// TLS configuration
if cfg.insecure_skip_verify {
// Skip verification (development only)
} else if cfg.certificate_authority.len > 0 {
// Load CA certificate
// TODO: Configure TLS with CA cert
}
return conn
}
// Get HTTP connection
pub fn (mut c KubernetesClient) connection() !&httpconnection.HTTPConnection {
if c.http_conn == none {
c.http_conn = http_connection_create(c.config)!
}
return c.http_conn!
}
version_data := json.decode(map[string]interface{}, result.stdout)!
server_version := version_data['serverVersion'] or { return error('No serverVersion') }
// ==================== DISCOVERY API ====================
// Get available API groups
pub fn (mut c KubernetesClient) get_api_groups() ![]APIGroupVersion {
conn := c.connection()!
response := conn.get_json_generic[map[string]interface{}](
prefix: '/apis'
)!
// Parse and return API groups
return []APIGroupVersion{}
}
// ==================== CORE API METHODS ====================
// Generic GET for any resource
pub fn (mut c KubernetesClient) get[T](
resource_type string
name string
namespace string = ''
) !T {
mut prefix := build_api_path(resource_type, name, namespace)
conn := c.connection()!
return conn.get_json_generic[T](prefix: prefix)!
}
// Generic LIST for any resource
pub fn (mut c KubernetesClient) list[T](
resource_type string
namespace string = ''
label_selector string = ''
) ![]T {
mut prefix := build_api_list_path(resource_type, namespace)
if label_selector.len > 0 {
prefix += '?labelSelector=${label_selector}'
}
conn := c.connection()!
response := conn.get_json_generic[map[string]interface{}](prefix: prefix)!
// Parse items array
return []T{}
}
// Generic CREATE for any resource
pub fn (mut c KubernetesClient) create[T](resource T) !T {
resource_type := get_resource_type[T]()
namespace := get_namespace[T](resource)
mut prefix := build_api_path_create(resource_type, namespace)
conn := c.connection()!
return conn.post_json_generic[T](
prefix: prefix
params: json.decode_object(json.encode(resource))!
)!
}
// Generic UPDATE (PUT) for any resource
pub fn (mut c KubernetesClient) update[T](resource T) !T {
resource_type := get_resource_type[T]()
name := get_resource_name[T](resource)
namespace := get_namespace[T](resource)
mut prefix := build_api_path(resource_type, name, namespace)
conn := c.connection()!
return conn.put_json_generic[T](
prefix: prefix
params: json.decode_object(json.encode(resource))!
)!
}
// Generic PATCH for any resource
pub fn (mut c KubernetesClient) patch[T](
resource_type string
name string
namespace string
patch_data map[string]interface{}
) !T {
mut prefix := build_api_path(resource_type, name, namespace)
conn := c.connection()!
mut header := http.new_header()
header.add(.content_type, 'application/merge-patch+json')
return conn.patch_json_generic[T](
prefix: prefix
params: patch_data
header: header
)!
}
// Generic DELETE for any resource
pub fn (mut c KubernetesClient) delete(
resource_type string
name string
namespace string = ''
) ! {
mut prefix := build_api_path(resource_type, name, namespace)
conn := c.connection()!
conn.delete(prefix: prefix)!
}
// Helper functions for building API paths
fn build_api_path(resource_type string, name string, namespace string) string {
if namespace.len > 0 {
return '/api/v1/namespaces/${namespace}/${resource_type}/${name}'
// 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 {
return '/api/v1/${resource_type}/${name}'
0
}
}
fn build_api_list_path(resource_type string, namespace string) string {
if namespace.len > 0 {
return '/api/v1/namespaces/${namespace}/${resource_type}'
// 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 {
return '/api/v1/${resource_type}'
0
}
// 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
}
}
fn build_api_path_create(resource_type string, namespace string) string {
return build_api_list_path(resource_type, namespace)
// Get resources (Pods, Deployments, Services, etc.)
pub fn (mut k KubeClient) get_pods(namespace string) ![]map[string]interface{} {
result := k.kubectl_exec(command: 'get pods -n ${namespace} -o json')!
if !result.success {
return error('Failed to get pods: ${result.stderr}')
}
data := json.decode(map[string]interface{}, result.stdout)!
items := data['items'] or { []interface{}{} }
return items as []map[string]interface{}
}
// Helper functions to extract metadata from generic types
fn get_resource_type[T]() string {
// TODO: Use compile-time reflection to get Kind from T
return 'pods'
pub fn (mut k KubeClient) get_deployments(namespace string) ![]map[string]interface{} {
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{}
}
fn get_resource_name[T](resource T) string {
// TODO: Extract metadata.name from resource
return ''
pub fn (mut k KubeClient) get_services(namespace string) ![]map[string]interface{} {
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{}
}
fn get_namespace[T](resource T) string {
// TODO: Extract metadata.namespace from resource
return 'default'
// Apply YAML file
pub fn (mut k KubeClient) apply_yaml(yaml_path string) !KubectlResult {
// Validate before applying
validation := yaml_validate(yaml_path)!
if !validation.valid {
return error('YAML validation failed: ${validation.errors.join(", ")}')
}
result := k.kubectl_exec(command: 'apply -f ${yaml_path}')!
if result.success {
console.print_green('Applied: ${validation.kind}/${validation.metadata.name}')
}
return result
}
// Delete resource
pub fn (mut k KubeClient) delete_resource(kind string, name string, namespace string) !KubectlResult {
result := k.kubectl_exec(command: 'delete ${kind} ${name} -n ${namespace}')!
return result
}
// Describe resource
pub fn (mut k KubeClient) describe_resource(kind string, name string, namespace string) !string {
result := k.kubectl_exec(command: 'describe ${kind} ${name} -n ${namespace}')!
if !result.success {
return error('Failed to describe resource: ${result.stderr}')
}
return result.stdout
}
// Port forward
pub fn (mut k KubeClient) port_forward(pod_name string, local_port int, remote_port int, namespace string) !string {
cmd := 'port-forward ${pod_name} ${local_port}:${remote_port} -n ${namespace}'
result := k.kubectl_exec(command: cmd, timeout: 300)!
return result.stdout
}
// Get logs
pub fn (mut k KubeClient) logs(pod_name string, namespace string, follow bool) !string {
mut cmd := 'logs ${pod_name} -n ${namespace}'
if follow {
cmd += ' -f'
}
result := k.kubectl_exec(command: cmd, timeout: 300)!
if !result.success {
return error('Failed to get logs: ${result.stderr}')
}
return result.stdout
}
// Exec into container
pub fn (mut k KubeClient) exec_pod(pod_name string, namespace string, container string, cmd_args []string) !string {
cmd := 'exec -it ${pod_name} -n ${namespace}'
if !container.is_empty() {
cmd += ' -c ${container}'
}
cmd += ' -- ${cmd_args.join(" ")}'
result := k.kubectl_exec(command: cmd, timeout: 300)!
if !result.success {
return error('Exec failed: ${result.stderr}')
}
return result.stdout
}
// Create namespace
pub fn (mut k KubeClient) create_namespace(namespace_name string) !KubectlResult {
result := k.kubectl_exec(command: 'create namespace ${namespace_name}')!
return result
}
// Watch resources (returns status)
pub fn (mut k KubeClient) watch_deployment(name string, namespace string, timeout_seconds int) !bool {
cmd := 'rollout status deployment/${name} -n ${namespace} --timeout=${timeout_seconds}s'
result := k.kubectl_exec(command: cmd, timeout: timeout_seconds + 10)!
return result.success
}

View File

@@ -2,47 +2,226 @@ module kubernetes
import incubaid.herolib.data.paramsparser
import incubaid.herolib.data.encoderhero
import incubaid.herolib.data.ourjson
import os
pub const version = '0.0.0'
pub const version = '1.0.0'
const singleton = false
const default = true
// THIS THE THE SOURCE OF THE INFORMATION OF THIS FILE, HERE WE HAVE THE CONFIG OBJECT CONFIGURED AND MODELLED
@[heap]
pub struct Kubernetes {
// K8s API Version and Kind tracking
@[params]
pub struct K8sMetadata {
pub mut:
name string = 'default'
homedir string
configpath string
username string
password string @[secret]
title string
host string
port int
name string
namespace string = 'default'
labels map[string]string
annotations map[string]string
owner_reference string
}
// your checking & initialization code if needed
fn obj_init(mycfg_ Kubernetes) !Kubernetes {
mut mycfg := mycfg_
// if mycfg.password == '' && mycfg.secret == '' {
// return error('password or secret needs to be filled in for ${mycfg.name}')
//}
return mycfg
// Pod Specification
@[params]
pub struct ContainerSpec {
pub mut:
name string
image string
image_pull_policy string = 'IfNotPresent'
ports []ContainerPort
env []EnvVar
resources ResourceRequirements
volume_mounts []VolumeMount
command []string
args []string
}
@[params]
pub struct ContainerPort {
pub mut:
name string
container_port int
protocol string = 'TCP'
host_port int
}
@[params]
pub struct EnvVar {
pub mut:
name string
value string
}
@[params]
pub struct ResourceRequirements {
pub mut:
requests map[string]string // cpu, memory
limits map[string]string
}
@[params]
pub struct VolumeMount {
pub mut:
name string
mount_path string
read_only bool
}
@[params]
pub struct PodSpec {
pub mut:
metadata K8sMetadata
containers []ContainerSpec
restart_policy string = 'Always'
service_account string
volumes []Volume
}
@[params]
pub struct Volume {
pub mut:
name string
config_map string
secret string
empty_dir bool
}
// Deployment Specification
@[params]
pub struct DeploymentSpec {
pub mut:
metadata K8sMetadata
replicas int = 1
selector map[string]string
template PodSpec
strategy DeploymentStrategy
progress_deadline_seconds int = 600
}
@[params]
pub struct DeploymentStrategy {
pub mut:
strategy_type string = 'RollingUpdate'
rolling_update RollingUpdateStrategy
}
@[params]
pub struct RollingUpdateStrategy {
pub mut:
max_surge string = '25%'
max_unavailable string = '25%'
}
// Service Specification
@[params]
pub struct ServiceSpec {
pub mut:
metadata K8sMetadata
service_type string = 'ClusterIP' // ClusterIP, NodePort, LoadBalancer
selector map[string]string
ports []ServicePort
cluster_ip string
external_ips []string
session_affinity string
}
@[params]
pub struct ServicePort {
pub mut:
name string
protocol string = 'TCP'
port int
target_port int
node_port int
}
// ConfigMap
@[params]
pub struct ConfigMapSpec {
pub mut:
metadata K8sMetadata
data map[string]string
}
// Secret
@[params]
pub struct SecretSpec {
pub mut:
metadata K8sMetadata
secret_type string = 'Opaque'
data map[string]string // base64 encoded
}
// Kube Client Configuration
@[params]
pub struct KubeConfig {
pub mut:
kubeconfig_path string
context string = ''
namespace string = 'default'
api_server string
ca_cert_path string
client_cert_path string
client_key_path string
token string
insecure_skip_tls_verify bool
}
@[heap]
pub struct KubeClient {
pub mut:
name string = 'default'
kubeconfig_path string
config KubeConfig
connected bool
api_version string = 'v1'
kubectl_path string = 'kubectl'
cache_enabled bool = true
cache_ttl_seconds int = 300
}
// Validation result for YAML files
pub struct K8sValidationResult {
pub mut:
valid bool
kind string
api_version string
metadata K8sMetadata
errors []string
}
// Cluster info
pub struct ClusterInfo {
pub mut:
version string
nodes int
namespaces int
running_pods int
api_server string
}
// Initialization
fn obj_init(mut cfg KubeClient) !KubeClient {
// Resolve kubeconfig path
if cfg.kubeconfig_path.is_empty() {
home := os.home_dir()
cfg.kubeconfig_path = '${home}/.kube/config'
}
// Ensure kubeconfig exists
if !os.path_exists(cfg.kubeconfig_path) {
return error('kubeconfig not found at ${cfg.kubeconfig_path}')
}
cfg.config.kubeconfig_path = cfg.kubeconfig_path
return cfg
}
// called before start if done
fn configure() ! {
// mut installer := get()!
// mut mycode := $tmpl('templates/atemplate.yaml')
// mut path := pathlib.get_file(path: cfg.configpath, create: true)!
// path.write(mycode)!
// console.print_debug(mycode)
// Configure any defaults or environment-specific settings
}
/////////////NORMALLY NO NEED TO TOUCH
pub fn heroscript_loads(heroscript string) !Kubernetes {
mut obj := encoderhero.decode[Kubernetes](heroscript)!
return obj
pub fn heroscript_loads(heroscript string) !KubeClient {
mut obj := encoderhero.decode[KubeClient](heroscript)!
return obj_init(obj)!
}

View File

@@ -0,0 +1,74 @@
module kubernetes
import time
import os
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
}
]
}
]
}
}
yaml := yaml_from_deployment(deployment)!
assert yaml.contains('apiVersion: apps/v1')
assert yaml.contains('kind: Deployment')
assert yaml.contains('test-app')
}
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_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)!
}

View File

@@ -1,53 +1,169 @@
module kubernetes
import encoding.yaml
import json
import incubaid.herolib.data.markdown
import incubaid.herolib.core.pathlib
import os
// Serialize Kubernetes resource to YAML
pub fn to_yaml[T](resource T) !string {
// TODO: Use V's YAML encoding to serialize
return encode_to_yaml(resource)!
}
// Parse YAML file and return validation result
pub fn yaml_validate(yaml_path string) !K8sValidationResult {
mut file := pathlib.get(yaml_path)
// Deserialize YAML to Kubernetes resource
pub fn from_yaml[T](content string) !T {
// TODO: Use V's YAML decoding to parse
return decode_from_yaml[T](content)!
}
if !file.exists() {
return error('YAML file not found: ${yaml_path}')
}
// Helper to handle YAML document conversion
fn encode_to_yaml[T](resource T) !string {
return yaml.encode(resource)!
}
yaml_content := file.read()!
fn decode_from_yaml[T](content string) !T {
return yaml.decode[T](content)!
}
// Extract kind and apiVersion from YAML
lines := yaml_content.split('\n')
mut kind := ''
mut api_version := ''
mut metadata_name := ''
mut metadata_namespace := 'default'
// Multi-document YAML support
pub fn to_yaml_multi[T](resources []T) !string {
mut result := ''
for i, resource in resources {
result += '---\n'
result += to_yaml(resource)!
if i < resources.len - 1 {
result += '\n'
for line in lines {
if line.starts_with('kind:') {
kind = line.replace('kind:', '').trim_space()
}
if line.starts_with('apiVersion:') {
api_version = line.replace('apiVersion:', '').trim_space()
}
if line.contains('name:') && line.trim_space().starts_with('name:') {
metadata_name = line.replace('name:', '').trim_space()
}
if line.contains('namespace:') && line.trim_space().starts_with('namespace:') {
metadata_namespace = line.replace('namespace:', '').trim_space()
}
}
return result
mut errors := []string{}
if kind.len == 0 {
errors << 'Missing "kind" field'
}
if api_version.len == 0 {
errors << 'Missing "apiVersion" field'
}
if metadata_name.len == 0 {
errors << 'Missing metadata.name field'
}
// Validate kind values
valid_kinds := ['Pod', 'Deployment', 'Service', 'ConfigMap', 'Secret', 'StatefulSet',
'DaemonSet', 'Job', 'CronJob', 'Ingress', 'PersistentVolume', 'PersistentVolumeClaim']
if kind !in valid_kinds {
errors << 'Invalid kind: ${kind}. Valid kinds: ${valid_kinds.join(", ")}'
}
return K8sValidationResult{
valid: errors.len == 0
kind: kind
api_version: api_version
metadata: K8sMetadata{
name: metadata_name
namespace: metadata_namespace
}
errors: errors
}
}
pub fn from_yaml_multi[T](content string) ![]T {
mut result := []T{}
docs := content.split('---')
for doc in docs {
trimmed := doc.trim_space()
if trimmed.len == 0 {
continue
// Generate YAML from model
pub fn yaml_from_deployment(spec DeploymentSpec) !string {
mut yaml := 'apiVersion: apps/v1\nkind: Deployment\nmetadata:\n'
yaml += ' name: ${spec.metadata.name}\n'
yaml += ' namespace: ${spec.metadata.namespace}\n'
if spec.metadata.labels.len > 0 {
yaml += ' labels:\n'
for key, value in spec.metadata.labels {
yaml += ' ${key}: ${value}\n'
}
resource := from_yaml[T](trimmed)!
result << resource
}
return result
yaml += 'spec:\n'
yaml += ' replicas: ${spec.replicas}\n'
yaml += ' selector:\n'
yaml += ' matchLabels:\n'
for key, value in spec.selector {
yaml += ' ${key}: ${value}\n'
}
yaml += ' template:\n'
yaml += ' metadata:\n'
yaml += ' labels:\n'
for key, value in spec.selector {
yaml += ' ${key}: ${value}\n'
}
yaml += ' spec:\n'
yaml += ' containers:\n'
for container in spec.template.containers {
yaml += ' - name: ${container.name}\n'
yaml += ' image: ${container.image}\n'
yaml += ' imagePullPolicy: ${container.image_pull_policy}\n'
if container.ports.len > 0 {
yaml += ' ports:\n'
for port in container.ports {
yaml += ' - name: ${port.name}\n'
yaml += ' containerPort: ${port.container_port}\n'
yaml += ' protocol: ${port.protocol}\n'
}
}
if container.env.len > 0 {
yaml += ' env:\n'
for env in container.env {
yaml += ' - name: ${env.name}\n'
yaml += ' value: "${env.value}"\n'
}
}
}
return yaml
}
pub fn yaml_from_service(spec ServiceSpec) !string {
mut yaml := 'apiVersion: v1\nkind: Service\nmetadata:\n'
yaml += ' name: ${spec.metadata.name}\n'
yaml += ' namespace: ${spec.metadata.namespace}\n'
yaml += 'spec:\n'
yaml += ' type: ${spec.service_type}\n'
yaml += ' selector:\n'
for key, value in spec.selector {
yaml += ' ${key}: ${value}\n'
}
yaml += ' ports:\n'
for port in spec.ports {
yaml += ' - name: ${port.name}\n'
yaml += ' protocol: ${port.protocol}\n'
yaml += ' port: ${port.port}\n'
yaml += ' targetPort: ${port.target_port}\n'
if port.node_port > 0 {
yaml += ' nodePort: ${port.node_port}\n'
}
}
return yaml
}
pub fn yaml_from_configmap(spec ConfigMapSpec) !string {
mut yaml := 'apiVersion: v1\nkind: ConfigMap\nmetadata:\n'
yaml += ' name: ${spec.metadata.name}\n'
yaml += ' namespace: ${spec.metadata.namespace}\n'
yaml += 'data:\n'
for key, value in spec.data {
yaml += ' ${key}: |\n'
// Indent multiline values
lines := value.split('\n')
for line in lines {
yaml += ' ${line}\n'
}
}
return yaml
}

View File

@@ -1,44 +1,187 @@
# kubernetes
# Kubernetes Client
A comprehensive Kubernetes client for HeroLib that wraps `kubectl` with additional safety and validation features.
## Features
To get started
-**Connection Testing**: Validate cluster connectivity before operations
-**YAML Validation**: Validate K8s YAML before applying
-**Resource Management**: Create, read, update, delete K8s resources
-**Cluster Info**: Get cluster status and metrics
-**Pod Management**: Logs, exec, port-forwarding
-**Model-Based**: Type-safe resource specifications
-**HeroScript Integration**: Full playbook support
## Installation
```bash
# Install kubectl
curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
sudo install -o root -g root -m 0755 kubectl /usr/local/bin/kubectl
# Setup kubeconfig
mkdir -p ~/.kube
cp /path/to/kubeconfig ~/.kube/config
chmod 600 ~/.kube/config
```
## Usage
### Basic Connection
```v
import incubaid.herolib.virt.kubernetes
import incubaid.herolib.installers.something.kubernetes as kubernetes_installer
heroscript:="
!!kubernetes.configure name:'test'
password: '1234'
port: 7701
!!kubernetes.start name:'test' reset:1
"
kubernetes_installer.play(heroscript=heroscript)!
//or we can call the default and do a start with reset
//mut installer:= kubernetes_installer.get()!
//installer.start(reset:true)!
mut k8s := kubernetes.new(name: 'production')!
k8s.start()!
info := k8s.cluster_info()!
println(info)
```
## example heroscript
### Apply YAML
```v
mut k8s := kubernetes.get(name: 'production')!
k8s.apply_yaml('deployment.yaml')!
```
```hero
### Validate YAML
```v
result := kubernetes.yaml_validate('my-deployment.yaml')!
if result.valid {
println('YAML is valid: ${result.kind}/${result.metadata.name}')
} else {
println('Validation errors: ${result.errors}')
}
```
### Get Resources
```v
mut k8s := kubernetes.get(name: 'production')!
pods := k8s.get_pods('default')!
for pod in pods {
println(pod)
}
```
### HeroScript
```heroscript
!!kubernetes.configure
homedir: '/home/user/kubernetes'
username: 'admin'
password: 'secretpassword'
title: 'Some Title'
host: 'localhost'
port: 8888
name: 'production'
kubeconfig_path: '~/.kube/config'
!!kubernetes.apply
name: 'production'
yaml_path: '/path/to/deployment.yaml'
!!kubernetes.info
name: 'production'
!!kubernetes.delete
name: 'production'
kind: 'deployment'
resource_name: 'my-app'
namespace: 'default'
```
## API Reference
### Configuration
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `name` | string | 'default' | Client instance name |
| `kubeconfig_path` | string | ~/.kube/config | Path to kubeconfig |
| `context` | string | '' | K8s context to use |
| `namespace` | string | 'default' | Default namespace |
| `kubectl_path` | string | 'kubectl' | Path to kubectl binary |
### Main Methods
- `test_connection() !bool` - Test cluster connectivity
- `cluster_info() !ClusterInfo` - Get cluster status
- `apply_yaml(path: string) !KubectlResult` - Apply YAML file
- `delete_resource(kind, name, namespace) !KubectlResult` - Delete resource
- `get_pods(namespace) ![]map` - List pods
- `get_deployments(namespace) ![]map` - List deployments
- `logs(pod, namespace) !string` - Get pod logs
- `exec_pod(pod, namespace, cmd) !string` - Execute in pod
## Best Practices
1. **Always validate YAML** before applying
2. **Test connection** before operations
3. **Use namespaces** to isolate workloads
4. **Check rollout status** for deployments
5. **Monitor logs** for troubleshooting
## Example: Complete Workflow
```v
#!/usr/bin/env -S v -cg -gc none -cc tcc -d use_openssl -enable-globals run
import incubaid.herolib.virt.kubernetes as k8s_mod
import incubaid.herolib.ui.console
// Create and connect
mut k8s := k8s_mod.new(
name: 'my-cluster'
kubeconfig_path: '~/.kube/config'
)!
k8s.start()!
// Validate YAML
result := k8s_mod.yaml_validate('my-deployment.yaml')!
if !result.valid {
console.print_stderr('Invalid YAML: ${result.errors.join(", ")}')
exit(1)
}
// Apply
k8s.apply_yaml('my-deployment.yaml')!
// Monitor rollout
if k8s.watch_deployment('my-app', 'default', 300)! {
console.print_green('Deployment successful!')
} else {
console.print_stderr('Deployment failed!')
}
// Check status
info := k8s.cluster_info()!
console.print_debug('Cluster: ${info.nodes} nodes, ${info.running_pods} pods')
k8s.stop()!
```
## Troubleshooting
### kubectl not found
```bash
which kubectl
# If not found, install kubectl or add to PATH
export PATH=$PATH:/usr/local/bin
```
### Kubeconfig not found
```bash
export KUBECONFIG=~/.kube/config
# Or specify in code: kubeconfig_path: '/path/to/config'
```
### Connection refused
```bash
# Check cluster is running
kubectl cluster-info
# Check credentials in kubeconfig
kubectl config view
```
### YAML validation errors
```bash
# Validate with kubectl
kubectl apply -f myfile.yaml --dry-run=client

View File

@@ -1,7 +1,8 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: {{.Name}}
namespace: {{.Namespace}}
name: ${metadata.name}
namespace: ${metadata.namespace}
data:
{{.Key}}: {{.Value}}
config.yaml: |
${config_content}

View File

@@ -1,22 +1,24 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{.Name}}
namespace: {{.Namespace}}
name: ${metadata.name}
namespace: ${metadata.namespace}
labels:
app: {{.AppLabel}}
app: ${metadata.name}
spec:
replicas: {{.Replicas}}
replicas: ${replicas}
selector:
matchLabels:
app: {{.AppLabel}}
app: ${metadata.name}
template:
metadata:
labels:
app: {{.AppLabel}}
app: ${metadata.name}
spec:
containers:
- name: {{.ContainerName}}
image: {{.Image}}
- name: ${container.name}
image: ${container.image}
imagePullPolicy: IfNotPresent
ports:
- containerPort: {{.ContainerPort}}
- containerPort: ${container.port}
name: http

View File

@@ -1,15 +1,13 @@
apiVersion: v1
kind: Service
metadata:
name: {{.Name}}
namespace: {{.Namespace}}
labels:
app: {{.AppLabel}}
name: ${metadata.name}
namespace: ${metadata.namespace}
spec:
type: ${service_type}
selector:
app: {{.AppLabel}}
app: ${metadata.name}
ports:
- protocol: TCP
port: {{.Port}}
targetPort: {{.TargetPort}}
type: {{.ServiceType}}
- protocol: TCP
port: ${port}
targetPort: ${target_port}

View File

@@ -0,0 +1,12 @@
!!hero_code.generate_installer
name:''
classname:'Kubernetes'
singleton:0
templates:1
default:1
title:''
supported_platforms:''
startupmanager:1
hasconfig:1
build:1

View File

@@ -0,0 +1,265 @@
module kubernetes
import incubaid.herolib.ui.console
import incubaid.herolib.core.texttools
import time
@[params]
pub struct DeployArgs {
pub mut:
deployment Deployment
wait bool = true
timeout_seconds int = 300
poll_interval_ms int = 5000
}
// Deploy a Deployment resource
pub fn (mut c KubernetesClient) deploy(args DeployArgs) !Deployment {
console.print_header('Deploying ${args.deployment.metadata.name}')
// Check if deployment already exists
existing := c.get[Deployment](
resource_type: 'deployments'
name: args.deployment.metadata.name
namespace: args.deployment.metadata.namespace
) or { none }
mut deployed_deployment := match existing {
Deployment { c.update[Deployment](args.deployment)! }
else { c.create[Deployment](args.deployment)! }
}
console.print_green('Deployment created/updated: ${args.deployment.metadata.name}')
if args.wait {
c.wait_for_deployment_ready(
name: args.deployment.metadata.name
namespace: args.deployment.metadata.namespace
timeout_seconds: args.timeout_seconds
)!
}
return deployed_deployment
}
@[params]
pub struct WaitForDeploymentArgs {
pub mut:
name string
namespace string = 'default'
timeout_seconds int = 300
poll_interval_ms int = 5000
}
// Wait for deployment to be ready
pub fn (mut c KubernetesClient) wait_for_deployment_ready(args WaitForDeploymentArgs) ! {
start_time := time.now()
for {
deployment := c.get[Deployment](
resource_type: 'deployments'
name: args.name
namespace: args.namespace
)!
if deployment.status != none {
status := deployment.status!
if status.observed_generation >= deployment.metadata.resource_version.int() {
if status.updated_replicas == deployment.spec.replicas {
if status.ready_replicas == deployment.spec.replicas {
console.print_green('Deployment ${args.name} is ready')
return
}
}
}
}
elapsed := time.now().unix_time() - start_time.unix_time()
if elapsed > args.timeout_seconds {
return error('Timeout waiting for deployment ${args.name} to be ready')
}
console.print_debug('Waiting for deployment ${args.name}...')
time.sleep(args.poll_interval_ms * time.millisecond)
}
}
@[params]
pub struct DeleteArgs {
pub mut:
resource_type string // 'pods', 'deployments', 'services', etc.
name string
namespace string = 'default'
grace_period_seconds int = 30
wait bool = true
timeout_seconds int = 300
}
// Delete a resource
pub fn (mut c KubernetesClient) delete_resource(args DeleteArgs) ! {
console.print_header('Deleting ${args.resource_type}/${args.name}')
c.delete(
resource_type: args.resource_type
name: args.name
namespace: args.namespace
)!
console.print_green('${args.resource_type}/${args.name} deleted')
if args.wait {
start_time := time.now()
for {
c.get[map[string]interface{}](
resource_type: args.resource_type
name: args.name
namespace: args.namespace
) or {
console.print_green('${args.resource_type}/${args.name} fully removed')
return
}
elapsed := time.now().unix_time() - start_time.unix_time()
if elapsed > args.timeout_seconds {
return error('Timeout waiting for ${args.resource_type}/${args.name} to be deleted')
}
time.sleep(5000 * time.millisecond)
}
}
}
@[params]
pub struct ScaleArgs {
pub mut:
deployment_name string
namespace string = 'default'
replicas int
wait bool = true
timeout_seconds int = 300
}
// Scale a deployment
pub fn (mut c KubernetesClient) scale_deployment(args ScaleArgs) !Deployment {
console.print_header('Scaling ${args.deployment_name} to ${args.replicas} replicas')
// Patch the deployment spec.replicas field
patch_data := {
'spec': {
'replicas': args.replicas
}
}
mut deployment := c.patch[Deployment](
resource_type: 'deployments'
name: args.deployment_name
namespace: args.namespace
patch_data: patch_data
)!
if args.wait {
c.wait_for_deployment_ready(
name: args.deployment_name
namespace: args.namespace
timeout_seconds: args.timeout_seconds
)!
}
return deployment
}
@[params]
pub struct RollingRestartArgs {
pub mut:
deployment_name string
namespace string = 'default'
wait bool = true
timeout_seconds int = 600
}
// Perform rolling restart of a deployment
pub fn (mut c KubernetesClient) rolling_restart(args RollingRestartArgs) ! {
console.print_header('Rolling restart of ${args.deployment_name}')
now_timestamp := time.now().format_rfc3339()
patch_data := {
'spec': {
'template': {
'metadata': {
'annotations': {
'kubectl.kubernetes.io/restartedAt': now_timestamp
}
}
}
}
}
c.patch[Deployment](
resource_type: 'deployments'
name: args.deployment_name
namespace: args.namespace
patch_data: patch_data
)!
if args.wait {
c.wait_for_deployment_ready(
name: args.deployment_name
namespace: args.namespace
timeout_seconds: args.timeout_seconds
)!
}
console.print_green('${args.deployment_name} rolling restart completed')
}
@[params]
pub struct GetLogsArgs {
pub mut:
pod_name string
container_name string
namespace string = 'default'
tail_lines int = 100
follow bool
}
// Get pod logs
pub fn (mut c KubernetesClient) get_pod_logs(args GetLogsArgs) !string {
mut prefix := '/api/v1/namespaces/${args.namespace}/pods/${args.pod_name}/log'
prefix += '?tailLines=${args.tail_lines}'
if args.container_name.len > 0 {
prefix += '&container=${args.container_name}'
}
conn := c.connection()!
return conn.get_text(prefix: prefix)!
}
@[params]
pub struct ExecArgs {
pub mut:
pod_name string
container_name string
command []string
namespace string = 'default'
}
// Execute command in pod (requires WebSocket support - future enhancement)
pub fn (mut c KubernetesClient) exec_in_pod(args ExecArgs) ! {
// TODO: Implement WebSocket-based exec
return error('exec_in_pod requires WebSocket support - not yet implemented')
}
@[params]
pub struct PortForwardArgs {
pub mut:
pod_name string
namespace string = 'default'
local_port int
remote_port int
}
// Port forward to pod (requires WebSocket support - future enhancement)
pub fn (mut c KubernetesClient) port_forward(args PortForwardArgs) ! {
// TODO: Implement WebSocket-based port forward
return error('port_forward requires WebSocket support - not yet implemented')
}

View File

@@ -0,0 +1,223 @@
module kubernetes
import incubaid.herolib.core.httpconnection
import net.http
import json
import encoding.base64
@[heap]
pub struct KubernetesClient {
pub mut:
config KubernetesConfig
http_conn ?&httpconnection.HTTPConnection
api_groups map[string]APIGroupVersion
}
pub struct APIGroupVersion {
pub:
group_version string
resources []APIResource
}
pub struct APIResource {
pub:
name string
singularname string
namespaced bool
kind string
verbs []string // get, list, create, update, delete, patch, watch, etc.
}
pub struct APIResponse {
pub:
kind string
api_version string
metadata map[string]interface{}
items []json.Any
}
// Create new Kubernetes client
pub fn new(config KubernetesConfig) !&KubernetesClient {
mut cfg := config
cfg.validate()!
mut client := KubernetesClient{
config: cfg
}
client.http_conn = http_connection_create(cfg)!
return &client
}
// Create HTTP connection with proper auth
fn http_connection_create(cfg KubernetesConfig) !&httpconnection.HTTPConnection {
mut conn := httpconnection.new(
name: 'kubernetes-${cfg.name}'
url: cfg.server
retry: 3
cache: false
)!
// Setup authentication
if cfg.token.len > 0 {
mut header := http.new_header()
header.add(.authorization, 'Bearer ${cfg.token}')
conn.default_header = header
} else if cfg.username.len > 0 && cfg.password.len > 0 {
conn.basic_auth(cfg.username, cfg.password)
}
// TLS configuration
if cfg.insecure_skip_verify {
// Skip verification (development only)
} else if cfg.certificate_authority.len > 0 {
// Load CA certificate
// TODO: Configure TLS with CA cert
}
return conn
}
// Get HTTP connection
pub fn (mut c KubernetesClient) connection() !&httpconnection.HTTPConnection {
if c.http_conn == none {
c.http_conn = http_connection_create(c.config)!
}
return c.http_conn!
}
// ==================== DISCOVERY API ====================
// Get available API groups
pub fn (mut c KubernetesClient) get_api_groups() ![]APIGroupVersion {
conn := c.connection()!
response := conn.get_json_generic[map[string]interface{}](
prefix: '/apis'
)!
// Parse and return API groups
return []APIGroupVersion{}
}
// ==================== CORE API METHODS ====================
// Generic GET for any resource
pub fn (mut c KubernetesClient) get[T](
resource_type string
name string
namespace string = ''
) !T {
mut prefix := build_api_path(resource_type, name, namespace)
conn := c.connection()!
return conn.get_json_generic[T](prefix: prefix)!
}
// Generic LIST for any resource
pub fn (mut c KubernetesClient) list[T](
resource_type string
namespace string = ''
label_selector string = ''
) ![]T {
mut prefix := build_api_list_path(resource_type, namespace)
if label_selector.len > 0 {
prefix += '?labelSelector=${label_selector}'
}
conn := c.connection()!
response := conn.get_json_generic[map[string]interface{}](prefix: prefix)!
// Parse items array
return []T{}
}
// Generic CREATE for any resource
pub fn (mut c KubernetesClient) create[T](resource T) !T {
resource_type := get_resource_type[T]()
namespace := get_namespace[T](resource)
mut prefix := build_api_path_create(resource_type, namespace)
conn := c.connection()!
return conn.post_json_generic[T](
prefix: prefix
params: json.decode_object(json.encode(resource))!
)!
}
// Generic UPDATE (PUT) for any resource
pub fn (mut c KubernetesClient) update[T](resource T) !T {
resource_type := get_resource_type[T]()
name := get_resource_name[T](resource)
namespace := get_namespace[T](resource)
mut prefix := build_api_path(resource_type, name, namespace)
conn := c.connection()!
return conn.put_json_generic[T](
prefix: prefix
params: json.decode_object(json.encode(resource))!
)!
}
// Generic PATCH for any resource
pub fn (mut c KubernetesClient) patch[T](
resource_type string
name string
namespace string
patch_data map[string]interface{}
) !T {
mut prefix := build_api_path(resource_type, name, namespace)
conn := c.connection()!
mut header := http.new_header()
header.add(.content_type, 'application/merge-patch+json')
return conn.patch_json_generic[T](
prefix: prefix
params: patch_data
header: header
)!
}
// Generic DELETE for any resource
pub fn (mut c KubernetesClient) delete(
resource_type string
name string
namespace string = ''
) ! {
mut prefix := build_api_path(resource_type, name, namespace)
conn := c.connection()!
conn.delete(prefix: prefix)!
}
// Helper functions for building API paths
fn build_api_path(resource_type string, name string, namespace string) string {
if namespace.len > 0 {
return '/api/v1/namespaces/${namespace}/${resource_type}/${name}'
} else {
return '/api/v1/${resource_type}/${name}'
}
}
fn build_api_list_path(resource_type string, namespace string) string {
if namespace.len > 0 {
return '/api/v1/namespaces/${namespace}/${resource_type}'
} else {
return '/api/v1/${resource_type}'
}
}
fn build_api_path_create(resource_type string, namespace string) string {
return build_api_list_path(resource_type, namespace)
}
// Helper functions to extract metadata from generic types
fn get_resource_type[T]() string {
// TODO: Use compile-time reflection to get Kind from T
return 'pods'
}
fn get_resource_name[T](resource T) string {
// TODO: Extract metadata.name from resource
return ''
}
fn get_namespace[T](resource T) string {
// TODO: Extract metadata.namespace from resource
return 'default'
}

View File

@@ -0,0 +1,312 @@
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]&Kubernetes
kubernetes_default string
)
/////////FACTORY
@[params]
pub struct ArgsGet {
pub mut:
name string = 'default'
fromdb bool // will load from filesystem
create bool // default will not create if not exist
}
pub fn new(args ArgsGet) !&Kubernetes {
mut obj := Kubernetes{
name: args.name
}
set(obj)!
return get(name: args.name)!
}
pub fn get(args ArgsGet) !&Kubernetes {
mut context := base.context()!
kubernetes_default = args.name
if args.fromdb || args.name !in kubernetes_global {
mut r := context.redis()!
if r.hexists('context:kubernetes', args.name)! {
data := r.hget('context:kubernetes', args.name)!
if data.len == 0 {
print_backtrace()
return error('Kubernetes with name: kubernetes does not exist, prob bug.')
}
mut obj := json.decode(Kubernetes, data)!
set_in_mem(obj)!
} else {
if args.create {
new(args)!
} else {
print_backtrace()
return error("Kubernetes with name 'kubernetes' does not exist")
}
}
return get(name: args.name)! // no longer from db nor create
}
return kubernetes_global[args.name] or {
print_backtrace()
return error('could not get config for kubernetes with name:kubernetes')
}
}
// register the config for the future
pub fn set(o Kubernetes) ! {
mut o2 := set_in_mem(o)!
kubernetes_default = o2.name
mut context := base.context()!
mut r := context.redis()!
r.hset('context:kubernetes', o2.name, json.encode(o2))!
}
// does the config exists?
pub fn exists(args ArgsGet) !bool {
mut context := base.context()!
mut r := context.redis()!
return r.hexists('context:kubernetes', args.name)!
}
pub fn delete(args ArgsGet) ! {
mut context := base.context()!
mut r := context.redis()!
r.hdel('context:kubernetes', args.name)!
}
@[params]
pub struct ArgsList {
pub mut:
fromdb bool // will load from filesystem
}
// if fromdb set: load from filesystem, and not from mem, will also reset what is in mem
pub fn list(args ArgsList) ![]&Kubernetes {
mut res := []&Kubernetes{}
mut context := base.context()!
if args.fromdb {
// reset what is in mem
kubernetes_global = map[string]&Kubernetes{}
kubernetes_default = ''
}
if args.fromdb {
mut r := context.redis()!
mut l := r.hkeys('context:kubernetes')!
for name in l {
res << get(name: name, fromdb: true)!
}
return res
} else {
// load from memory
for _, client in kubernetes_global {
res << client
}
}
return res
}
// only sets in mem, does not set as config
fn set_in_mem(o Kubernetes) !Kubernetes {
mut o2 := obj_init(o)!
kubernetes_global[o2.name] = &o2
kubernetes_default = o2.name
return o2
}
pub fn play(mut plbook PlayBook) ! {
if !plbook.exists(filter: 'kubernetes.') {
return
}
mut install_actions := plbook.find(filter: 'kubernetes.configure')!
if install_actions.len > 0 {
for mut install_action in install_actions {
heroscript := install_action.heroscript()
mut obj2 := heroscript_loads(heroscript)!
set(obj2)!
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 Kubernetes) reload() ! {
switch(self.name)
self = obj_init(self)!
}
pub fn (mut self Kubernetes) 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 Kubernetes) install_start(args InstallArgs) ! {
switch(self.name)
self.install(args)!
self.start()!
}
pub fn (mut self Kubernetes) 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 Kubernetes) restart() ! {
switch(self.name)
self.stop()!
self.start()!
}
pub fn (mut self Kubernetes) 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 Kubernetes) install(args InstallArgs) ! {
switch(self.name)
if args.reset || (!installed()!) {
install()!
}
}
pub fn (mut self Kubernetes) build() ! {
switch(self.name)
build()!
}
pub fn (mut self Kubernetes) destroy() ! {
switch(self.name)
self.stop() or {}
destroy()!
}
// switch instance to be used for kubernetes
pub fn switch(name string) {
kubernetes_default = name
}

View File

@@ -0,0 +1,48 @@
module kubernetes
import incubaid.herolib.data.paramsparser
import incubaid.herolib.data.encoderhero
import os
pub const version = '0.0.0'
const singleton = false
const default = true
// THIS THE THE SOURCE OF THE INFORMATION OF THIS FILE, HERE WE HAVE THE CONFIG OBJECT CONFIGURED AND MODELLED
@[heap]
pub struct Kubernetes {
pub mut:
name string = 'default'
homedir string
configpath string
username string
password string @[secret]
title string
host string
port int
}
// your checking & initialization code if needed
fn obj_init(mycfg_ Kubernetes) !Kubernetes {
mut mycfg := mycfg_
// if mycfg.password == '' && mycfg.secret == '' {
// return error('password or secret needs to be filled in for ${mycfg.name}')
//}
return mycfg
}
// called before start if done
fn configure() ! {
// mut installer := get()!
// mut mycode := $tmpl('templates/atemplate.yaml')
// mut path := pathlib.get_file(path: cfg.configpath, create: true)!
// path.write(mycode)!
// console.print_debug(mycode)
}
/////////////NORMALLY NO NEED TO TOUCH
pub fn heroscript_loads(heroscript string) !Kubernetes {
mut obj := encoderhero.decode[Kubernetes](heroscript)!
return obj
}

View File

@@ -0,0 +1,53 @@
module kubernetes
import encoding.yaml
// Serialize Kubernetes resource to YAML
pub fn to_yaml[T](resource T) !string {
// TODO: Use V's YAML encoding to serialize
return encode_to_yaml(resource)!
}
// Deserialize YAML to Kubernetes resource
pub fn from_yaml[T](content string) !T {
// TODO: Use V's YAML decoding to parse
return decode_from_yaml[T](content)!
}
// Helper to handle YAML document conversion
fn encode_to_yaml[T](resource T) !string {
return yaml.encode(resource)!
}
fn decode_from_yaml[T](content string) !T {
return yaml.decode[T](content)!
}
// Multi-document YAML support
pub fn to_yaml_multi[T](resources []T) !string {
mut result := ''
for i, resource in resources {
result += '---\n'
result += to_yaml(resource)!
if i < resources.len - 1 {
result += '\n'
}
}
return result
}
pub fn from_yaml_multi[T](content string) ![]T {
mut result := []T{}
docs := content.split('---')
for doc in docs {
trimmed := doc.trim_space()
if trimmed.len == 0 {
continue
}
resource := from_yaml[T](trimmed)!
result << resource
}
return result
}

View File

@@ -0,0 +1,44 @@
# kubernetes
To get started
```v
import incubaid.herolib.installers.something.kubernetes as kubernetes_installer
heroscript:="
!!kubernetes.configure name:'test'
password: '1234'
port: 7701
!!kubernetes.start name:'test' reset:1
"
kubernetes_installer.play(heroscript=heroscript)!
//or we can call the default and do a start with reset
//mut installer:= kubernetes_installer.get()!
//installer.start(reset:true)!
```
## example heroscript
```hero
!!kubernetes.configure
homedir: '/home/user/kubernetes'
username: 'admin'
password: 'secretpassword'
title: 'Some Title'
host: 'localhost'
port: 8888
```

View File

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

View File

@@ -0,0 +1,7 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: {{.Name}}
namespace: {{.Namespace}}
data:
{{.Key}}: {{.Value}}

View File

@@ -0,0 +1,22 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{.Name}}
namespace: {{.Namespace}}
labels:
app: {{.AppLabel}}
spec:
replicas: {{.Replicas}}
selector:
matchLabels:
app: {{.AppLabel}}
template:
metadata:
labels:
app: {{.AppLabel}}
spec:
containers:
- name: {{.ContainerName}}
image: {{.Image}}
ports:
- containerPort: {{.ContainerPort}}

View File

@@ -0,0 +1,15 @@
apiVersion: v1
kind: Service
metadata:
name: {{.Name}}
namespace: {{.Namespace}}
labels:
app: {{.AppLabel}}
spec:
selector:
app: {{.AppLabel}}
ports:
- protocol: TCP
port: {{.Port}}
targetPort: {{.TargetPort}}
type: {{.ServiceType}}