...
This commit is contained in:
@@ -1,12 +0,0 @@
|
||||
|
||||
!!hero_code.generate_installer
|
||||
name:''
|
||||
classname:'Kubernetes'
|
||||
singleton:0
|
||||
templates:1
|
||||
default:1
|
||||
title:''
|
||||
supported_platforms:''
|
||||
startupmanager:1
|
||||
hasconfig:1
|
||||
build:1
|
||||
@@ -1,151 +0,0 @@
|
||||
module kubernetes
|
||||
|
||||
import incubaid.herolib.core.base
|
||||
import incubaid.herolib.core.playbook { PlayBook }
|
||||
import json
|
||||
|
||||
__global (
|
||||
kubernetes_clients map[string]&KubernetesClient
|
||||
default_client_name string
|
||||
)
|
||||
|
||||
@[params]
|
||||
pub struct ClientGetArgs {
|
||||
pub mut:
|
||||
name string = 'default'
|
||||
config ?KubernetesConfig
|
||||
create bool = true
|
||||
}
|
||||
|
||||
// Get or create a Kubernetes client
|
||||
pub fn get_client(args ClientGetArgs) !&KubernetesClient {
|
||||
if args.name in kubernetes_clients {
|
||||
return kubernetes_clients[args.name] or {
|
||||
return error('Failed to retrieve client: ${args.name}')
|
||||
}
|
||||
}
|
||||
|
||||
if args.config != none {
|
||||
mut cfg := args.config!
|
||||
cfg.name = args.name
|
||||
mut client := new(cfg)!
|
||||
kubernetes_clients[args.name] = client
|
||||
default_client_name = args.name
|
||||
return client
|
||||
}
|
||||
|
||||
// Try to load from kubeconfig
|
||||
cfg := load_kubeconfig(args.name) or {
|
||||
if args.create {
|
||||
return error('No kubeconfig found and create=false')
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
mut client := new(cfg)!
|
||||
kubernetes_clients[args.name] = client
|
||||
default_client_name = args.name
|
||||
return client
|
||||
}
|
||||
|
||||
// Use a specific client as default
|
||||
pub fn use_client(name string) ! {
|
||||
if name !in kubernetes_clients {
|
||||
return error('Client ${name} not found')
|
||||
}
|
||||
default_client_name = name
|
||||
}
|
||||
|
||||
// Get default client
|
||||
pub fn client() !&KubernetesClient {
|
||||
if default_client_name == '' {
|
||||
default_client_name = 'default'
|
||||
}
|
||||
|
||||
if default_client_name !in kubernetes_clients {
|
||||
// Try to load default from kubeconfig
|
||||
return get_client(name: 'default', create: true)!
|
||||
}
|
||||
|
||||
return kubernetes_clients[default_client_name] or {
|
||||
return error('Default client not available')
|
||||
}
|
||||
}
|
||||
|
||||
// Register a client
|
||||
pub fn register_client(name string, cfg KubernetesConfig) !&KubernetesClient {
|
||||
mut client := new(cfg)!
|
||||
kubernetes_clients[name] = client
|
||||
return client
|
||||
}
|
||||
|
||||
// List all registered clients
|
||||
pub fn list_clients() []string {
|
||||
mut result := []string{}
|
||||
for name, _ in kubernetes_clients {
|
||||
result << name
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// Play HeroScript for Kubernetes
|
||||
pub fn play(mut plbook PlayBook) ! {
|
||||
if !plbook.exists(filter: 'kubernetes.') {
|
||||
return
|
||||
}
|
||||
|
||||
// Handle configuration actions
|
||||
configure_actions := plbook.find(filter: 'kubernetes.configure')!
|
||||
for mut action in configure_actions {
|
||||
mut p := action.params
|
||||
name := p.get_default('name', 'default')!
|
||||
server := p.get('server')!
|
||||
token := p.get_default('token', '')!
|
||||
|
||||
cfg := KubernetesConfig{
|
||||
name: name
|
||||
server: server
|
||||
token: token
|
||||
}
|
||||
|
||||
register_client(name, cfg)!
|
||||
action.done = true
|
||||
}
|
||||
|
||||
// Handle resource actions (create, delete, scale, etc.)
|
||||
resource_actions := plbook.find(filter: 'kubernetes.')!
|
||||
for mut action in resource_actions {
|
||||
mut p := action.params
|
||||
client_name := p.get_default('client', 'default')!
|
||||
mut client := get_client(name: client_name)!
|
||||
|
||||
match action.name {
|
||||
'deploy' {
|
||||
// Handle deployment
|
||||
// yaml_content := p.get('yaml')!
|
||||
// deployment := from_yaml[Deployment](yaml_content)!
|
||||
// client.deploy(deployment: deployment)!
|
||||
}
|
||||
'delete' {
|
||||
resource_type := p.get('resource_type')!
|
||||
resource_name := p.get('name')!
|
||||
namespace := p.get_default('namespace', 'default')!
|
||||
client.delete_resource(
|
||||
resource_type: resource_type
|
||||
name: resource_name
|
||||
namespace: namespace
|
||||
)!
|
||||
}
|
||||
'scale' {
|
||||
deployment_name := p.get('deployment')!
|
||||
replicas := p.get_int('replicas')!
|
||||
client.scale_deployment(
|
||||
deployment_name: deployment_name
|
||||
replicas: replicas
|
||||
)!
|
||||
}
|
||||
else {}
|
||||
}
|
||||
action.done = true
|
||||
}
|
||||
}
|
||||
@@ -1,265 +0,0 @@
|
||||
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')
|
||||
}
|
||||
@@ -1,223 +0,0 @@
|
||||
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'
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
module kubernetes
|
||||
|
||||
import incubaid.herolib.core.pathlib
|
||||
import os
|
||||
|
||||
@[params]
|
||||
pub struct KubernetesConfig {
|
||||
pub mut:
|
||||
name string = 'default'
|
||||
server string // API server URL (e.g., https://localhost:6443)
|
||||
certificate_authority string // Path to CA cert
|
||||
client_certificate string // Path to client cert
|
||||
client_key string // Path to client key
|
||||
username string
|
||||
password string @[secret]
|
||||
token string @[secret]
|
||||
insecure_skip_verify bool // Skip TLS verification (not recommended)
|
||||
namespace string = 'default'
|
||||
timeout_seconds int = 30
|
||||
context_name string = 'default'
|
||||
cluster_name string = 'default'
|
||||
}
|
||||
|
||||
pub struct KubernetesContext {
|
||||
pub mut:
|
||||
config KubernetesConfig
|
||||
client &KubernetesClient
|
||||
}
|
||||
|
||||
// Load kubeconfig from standard location (~/.kube/config)
|
||||
pub fn load_kubeconfig(name string) !KubernetesConfig {
|
||||
mut config_path := pathlib.get('~/.kube/config')!
|
||||
if !config_path.exists() {
|
||||
return error('kubeconfig not found at ${config_path.path}')
|
||||
}
|
||||
|
||||
// Parse YAML kubeconfig file
|
||||
content := config_path.read()!
|
||||
// TODO: Parse YAML and extract config for specified context
|
||||
|
||||
return KubernetesConfig{name: name}
|
||||
}
|
||||
|
||||
// Load from in-memory config
|
||||
pub fn load_config(cfg KubernetesConfig) !KubernetesConfig {
|
||||
if cfg.server == '' {
|
||||
return error('server URL must be provided')
|
||||
}
|
||||
return cfg
|
||||
}
|
||||
|
||||
// Validate configuration
|
||||
pub fn (cfg KubernetesConfig) validate() ! {
|
||||
if cfg.server == '' {
|
||||
return error('server URL is required')
|
||||
}
|
||||
if !cfg.insecure_skip_verify {
|
||||
if cfg.certificate_authority == '' {
|
||||
return error('certificate_authority is required when insecure_skip_verify is false')
|
||||
}
|
||||
}
|
||||
if cfg.token == '' && (cfg.username == '' || cfg.password == '') {
|
||||
return error('either token or username/password must be provided')
|
||||
}
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
module kubernetes
|
||||
|
||||
pub struct KubernetesError {
|
||||
Error
|
||||
pub:
|
||||
code int // HTTP status code
|
||||
reason string // Kubernetes reason field
|
||||
message string // Descriptive error message
|
||||
namespace string
|
||||
resource string
|
||||
}
|
||||
|
||||
pub fn (err KubernetesError) msg() string {
|
||||
return '${err.reason} (${err.code}): ${err.message} in ${err.namespace}/${err.resource}'
|
||||
}
|
||||
|
||||
pub fn (err KubernetesError) code() int {
|
||||
return err.code
|
||||
}
|
||||
|
||||
pub struct ResourceNotFoundError {
|
||||
Error
|
||||
pub:
|
||||
resource_type string
|
||||
resource_name string
|
||||
namespace string
|
||||
}
|
||||
|
||||
pub struct ConnectionError {
|
||||
Error
|
||||
pub:
|
||||
host string
|
||||
port int
|
||||
details string
|
||||
}
|
||||
|
||||
pub struct ValidationError {
|
||||
Error
|
||||
pub:
|
||||
field string
|
||||
message string
|
||||
}
|
||||
@@ -1,312 +0,0 @@
|
||||
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
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
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
|
||||
}
|
||||
@@ -1,622 +0,0 @@
|
||||
module kubernetes
|
||||
|
||||
import incubaid.herolib.data.ourtime
|
||||
|
||||
// ==================== METADATA ====================
|
||||
|
||||
@[params]
|
||||
pub struct ObjectMeta {
|
||||
pub mut:
|
||||
name string
|
||||
namespace string = 'default'
|
||||
uid string
|
||||
resource_version string
|
||||
creation_timestamp ourtime.OurTime
|
||||
deletion_timestamp ?ourtime.OurTime
|
||||
labels map[string]string
|
||||
annotations map[string]string
|
||||
owner_references []OwnerReference
|
||||
finalizers []string
|
||||
}
|
||||
|
||||
pub struct OwnerReference {
|
||||
pub:
|
||||
api_version string
|
||||
kind string
|
||||
name string
|
||||
uid string
|
||||
controller bool
|
||||
}
|
||||
|
||||
pub struct TypeMeta {
|
||||
pub:
|
||||
api_version string = 'v1'
|
||||
kind string
|
||||
}
|
||||
|
||||
// ==================== POD ====================
|
||||
|
||||
@[params]
|
||||
pub struct Pod {
|
||||
pub mut:
|
||||
api_version string = 'v1'
|
||||
kind string = 'Pod'
|
||||
metadata ObjectMeta
|
||||
spec PodSpec
|
||||
status ?PodStatus
|
||||
}
|
||||
|
||||
@[params]
|
||||
pub struct PodSpec {
|
||||
pub mut:
|
||||
containers []Container
|
||||
init_containers []Container
|
||||
restart_policy string = 'Always' // Always, OnFailure, Never
|
||||
termination_grace_period_seconds int = 30
|
||||
dns_policy string = 'ClusterFirst'
|
||||
service_account_name string
|
||||
node_selector map[string]string
|
||||
tolerations []Toleration
|
||||
affinity ?Affinity
|
||||
volumes []Volume
|
||||
image_pull_secrets []LocalObjectReference
|
||||
}
|
||||
|
||||
@[params]
|
||||
pub struct Container {
|
||||
pub mut:
|
||||
name string
|
||||
image string
|
||||
image_pull_policy string = 'IfNotPresent'
|
||||
ports []ContainerPort
|
||||
env []EnvVar
|
||||
resources ?ResourceRequirements
|
||||
volume_mounts []VolumeMount
|
||||
liveness_probe ?Probe
|
||||
readiness_probe ?Probe
|
||||
startup_probe ?Probe
|
||||
security_context ?SecurityContext
|
||||
stdin bool
|
||||
tty bool
|
||||
working_dir string
|
||||
command []string
|
||||
args []string
|
||||
}
|
||||
|
||||
pub struct ContainerPort {
|
||||
pub mut:
|
||||
name string
|
||||
container_port int
|
||||
host_port ?int
|
||||
protocol string = 'TCP' // TCP, UDP
|
||||
}
|
||||
|
||||
@[params]
|
||||
pub struct EnvVar {
|
||||
pub:
|
||||
name string
|
||||
value ?string
|
||||
value_from ?EnvVarSource
|
||||
}
|
||||
|
||||
pub struct EnvVarSource {
|
||||
pub:
|
||||
config_map_key_ref ?ConfigMapKeySelector
|
||||
secret_key_ref ?SecretKeySelector
|
||||
}
|
||||
|
||||
pub struct ConfigMapKeySelector {
|
||||
pub:
|
||||
name string
|
||||
key string
|
||||
}
|
||||
|
||||
pub struct SecretKeySelector {
|
||||
pub:
|
||||
name string
|
||||
key string
|
||||
}
|
||||
|
||||
pub struct VolumeMount {
|
||||
pub:
|
||||
name string
|
||||
mount_path string
|
||||
read_only bool
|
||||
sub_path string
|
||||
}
|
||||
|
||||
pub struct ResourceRequirements {
|
||||
pub:
|
||||
limits map[string]string // e.g. {"cpu": "500m", "memory": "512Mi"}
|
||||
requests map[string]string
|
||||
}
|
||||
|
||||
pub struct Probe {
|
||||
pub:
|
||||
exec ?ExecAction
|
||||
http_get ?HTTPGetAction
|
||||
tcp_socket ?TCPSocketAction
|
||||
initial_delay_seconds int = 0
|
||||
timeout_seconds int = 1
|
||||
period_seconds int = 10
|
||||
success_threshold int = 1
|
||||
failure_threshold int = 3
|
||||
}
|
||||
|
||||
pub struct ExecAction {
|
||||
pub:
|
||||
command []string
|
||||
}
|
||||
|
||||
pub struct HTTPGetAction {
|
||||
pub:
|
||||
path string
|
||||
port int
|
||||
scheme string = 'HTTP'
|
||||
}
|
||||
|
||||
pub struct TCPSocketAction {
|
||||
pub:
|
||||
port int
|
||||
}
|
||||
|
||||
pub struct SecurityContext {
|
||||
pub:
|
||||
run_as_user ?int
|
||||
run_as_group ?int
|
||||
fs_group ?int
|
||||
read_only_root_fs bool
|
||||
allow_privilege_escalation bool
|
||||
}
|
||||
|
||||
pub struct Toleration {
|
||||
pub:
|
||||
key string
|
||||
operator string // Equal, Exists
|
||||
value string
|
||||
effect string // NoSchedule, NoExecute, PreferNoSchedule
|
||||
toleration_seconds ?int
|
||||
}
|
||||
|
||||
pub struct Affinity {
|
||||
pub:
|
||||
pod_affinity ?PodAffinity
|
||||
pod_anti_affinity ?PodAntiAffinity
|
||||
node_affinity ?NodeAffinity
|
||||
}
|
||||
|
||||
pub struct PodAffinity {
|
||||
pub:
|
||||
required_during_scheduling []PodAffinityTerm
|
||||
preferred_during_scheduling []WeightedPodAffinityTerm
|
||||
}
|
||||
|
||||
pub struct PodAffinityTerm {
|
||||
pub:
|
||||
label_selector map[string]string
|
||||
topology_key string
|
||||
}
|
||||
|
||||
pub struct WeightedPodAffinityTerm {
|
||||
pub:
|
||||
weight int
|
||||
pod_affinity_term PodAffinityTerm
|
||||
}
|
||||
|
||||
pub struct PodAntiAffinity {
|
||||
pub:
|
||||
required_during_scheduling []PodAffinityTerm
|
||||
preferred_during_scheduling []WeightedPodAffinityTerm
|
||||
}
|
||||
|
||||
pub struct NodeAffinity {
|
||||
pub:
|
||||
required_during_scheduling ?NodeSelector
|
||||
preferred_during_scheduling []PreferredSchedulingTerm
|
||||
}
|
||||
|
||||
pub struct NodeSelector {
|
||||
pub:
|
||||
node_selector_terms []NodeSelectorTerm
|
||||
}
|
||||
|
||||
pub struct NodeSelectorTerm {
|
||||
pub:
|
||||
match_expressions []NodeSelectorRequirement
|
||||
}
|
||||
|
||||
pub struct NodeSelectorRequirement {
|
||||
pub:
|
||||
key string
|
||||
operator string // In, NotIn, Exists, NotExists, Gt, Lt
|
||||
values []string
|
||||
}
|
||||
|
||||
pub struct PreferredSchedulingTerm {
|
||||
pub:
|
||||
weight int
|
||||
preference NodeSelectorTerm
|
||||
}
|
||||
|
||||
pub struct Volume {
|
||||
pub mut:
|
||||
name string
|
||||
empty_dir ?EmptyDirVolumeSource
|
||||
config_map ?ConfigMapVolumeSource
|
||||
secret ?SecretVolumeSource
|
||||
persistent_volume_claim ?PersistentVolumeClaimVolumeSource
|
||||
host_path ?HostPathVolumeSource
|
||||
}
|
||||
|
||||
pub struct EmptyDirVolumeSource {
|
||||
pub:
|
||||
medium string
|
||||
size_limit string
|
||||
}
|
||||
|
||||
pub struct ConfigMapVolumeSource {
|
||||
pub:
|
||||
name string
|
||||
default_mode int
|
||||
items []KeyToPath
|
||||
}
|
||||
|
||||
pub struct SecretVolumeSource {
|
||||
pub:
|
||||
secret_name string
|
||||
default_mode int
|
||||
items []KeyToPath
|
||||
}
|
||||
|
||||
pub struct KeyToPath {
|
||||
pub:
|
||||
key string
|
||||
path string
|
||||
mode int
|
||||
}
|
||||
|
||||
pub struct PersistentVolumeClaimVolumeSource {
|
||||
pub:
|
||||
claim_name string
|
||||
read_only bool
|
||||
}
|
||||
|
||||
pub struct HostPathVolumeSource {
|
||||
pub:
|
||||
path string
|
||||
type string // Directory, File, Socket, CharDevice, BlockDevice
|
||||
}
|
||||
|
||||
pub struct LocalObjectReference {
|
||||
pub:
|
||||
name string
|
||||
}
|
||||
|
||||
pub struct PodStatus {
|
||||
pub:
|
||||
phase string // Pending, Running, Succeeded, Failed, Unknown
|
||||
conditions []PodCondition
|
||||
host_ip string
|
||||
pod_ip string
|
||||
container_statuses []ContainerStatus
|
||||
}
|
||||
|
||||
pub struct PodCondition {
|
||||
pub:
|
||||
type_ string // Initialized, Ready, ContainersReady, PodScheduled
|
||||
status string // True, False, Unknown
|
||||
reason string
|
||||
message string
|
||||
last_probe_time ourtime.OurTime
|
||||
last_transition_time ourtime.OurTime
|
||||
}
|
||||
|
||||
pub struct ContainerStatus {
|
||||
pub:
|
||||
name string
|
||||
state ContainerState
|
||||
ready bool
|
||||
restart_count int
|
||||
image string
|
||||
image_id string
|
||||
}
|
||||
|
||||
pub struct ContainerState {
|
||||
pub:
|
||||
waiting ?ContainerStateWaiting
|
||||
running ?ContainerStateRunning
|
||||
terminated ?ContainerStateTerminated
|
||||
}
|
||||
|
||||
pub struct ContainerStateWaiting {
|
||||
pub:
|
||||
reason string
|
||||
message string
|
||||
}
|
||||
|
||||
pub struct ContainerStateRunning {
|
||||
pub:
|
||||
started_at ourtime.OurTime
|
||||
}
|
||||
|
||||
pub struct ContainerStateTerminated {
|
||||
pub:
|
||||
exit_code int
|
||||
signal int
|
||||
reason string
|
||||
message string
|
||||
started_at ourtime.OurTime
|
||||
finished_at ourtime.OurTime
|
||||
container_id string
|
||||
}
|
||||
|
||||
// ==================== DEPLOYMENT ====================
|
||||
|
||||
@[params]
|
||||
pub struct Deployment {
|
||||
pub mut:
|
||||
api_version string = 'apps/v1'
|
||||
kind string = 'Deployment'
|
||||
metadata ObjectMeta
|
||||
spec DeploymentSpec
|
||||
status ?DeploymentStatus
|
||||
}
|
||||
|
||||
@[params]
|
||||
pub struct DeploymentSpec {
|
||||
pub mut:
|
||||
replicas int = 1
|
||||
selector ?LabelSelector
|
||||
template PodTemplateSpec
|
||||
strategy ?DeploymentStrategy
|
||||
min_ready_seconds int
|
||||
revision_history_limit int
|
||||
paused bool
|
||||
progress_deadline_seconds int
|
||||
}
|
||||
|
||||
pub struct PodTemplateSpec {
|
||||
pub:
|
||||
metadata ObjectMeta
|
||||
spec PodSpec
|
||||
}
|
||||
|
||||
pub struct LabelSelector {
|
||||
pub:
|
||||
match_labels map[string]string
|
||||
match_expressions []LabelSelectorRequirement
|
||||
}
|
||||
|
||||
pub struct LabelSelectorRequirement {
|
||||
pub:
|
||||
key string
|
||||
operator string // In, NotIn, Exists, DoesNotExist
|
||||
values []string
|
||||
}
|
||||
|
||||
pub struct DeploymentStrategy {
|
||||
pub:
|
||||
type_ string // RollingUpdate, Recreate
|
||||
rolling_update ?RollingUpdateDeploymentStrategy
|
||||
}
|
||||
|
||||
pub struct RollingUpdateDeploymentStrategy {
|
||||
pub:
|
||||
max_surge string
|
||||
max_unavailable string
|
||||
}
|
||||
|
||||
pub struct DeploymentStatus {
|
||||
pub:
|
||||
observed_generation int
|
||||
replicas int
|
||||
updated_replicas int
|
||||
ready_replicas int
|
||||
available_replicas int
|
||||
unavailable_replicas int
|
||||
conditions []DeploymentCondition
|
||||
}
|
||||
|
||||
pub struct DeploymentCondition {
|
||||
pub:
|
||||
type_ string // Progressing, Available, ReplicaFailure
|
||||
status string // True, False, Unknown
|
||||
reason string
|
||||
message string
|
||||
last_update_time ourtime.OurTime
|
||||
last_transition_time ourtime.OurTime
|
||||
}
|
||||
|
||||
// ==================== SERVICE ====================
|
||||
|
||||
@[params]
|
||||
pub struct Service {
|
||||
pub mut:
|
||||
api_version string = 'v1'
|
||||
kind string = 'Service'
|
||||
metadata ObjectMeta
|
||||
spec ServiceSpec
|
||||
status ?ServiceStatus
|
||||
}
|
||||
|
||||
@[params]
|
||||
pub struct ServiceSpec {
|
||||
pub mut:
|
||||
service_type string = 'ClusterIP' // ClusterIP, NodePort, LoadBalancer, ExternalName
|
||||
selector map[string]string
|
||||
ports []ServicePort
|
||||
cluster_ip string
|
||||
external_ips []string
|
||||
load_balancer_ip string
|
||||
external_name string
|
||||
session_affinity string // None, ClientIP
|
||||
session_affinity_timeout int
|
||||
}
|
||||
|
||||
pub struct ServicePort {
|
||||
pub:
|
||||
name string
|
||||
protocol string = 'TCP'
|
||||
port int
|
||||
target_port string // can be int or string
|
||||
node_port ?int
|
||||
}
|
||||
|
||||
pub struct ServiceStatus {
|
||||
pub:
|
||||
load_balancer ?LoadBalancerStatus
|
||||
}
|
||||
|
||||
pub struct LoadBalancerStatus {
|
||||
pub:
|
||||
ingress []LoadBalancerIngress
|
||||
}
|
||||
|
||||
pub struct LoadBalancerIngress {
|
||||
pub:
|
||||
ip string
|
||||
hostname string
|
||||
}
|
||||
|
||||
// ==================== CONFIGMAP ====================
|
||||
|
||||
@[params]
|
||||
pub struct ConfigMap {
|
||||
pub mut:
|
||||
api_version string = 'v1'
|
||||
kind string = 'ConfigMap'
|
||||
metadata ObjectMeta
|
||||
data map[string]string
|
||||
binary_data map[string][]u8
|
||||
}
|
||||
|
||||
// ==================== SECRET ====================
|
||||
|
||||
@[params]
|
||||
pub struct Secret {
|
||||
pub mut:
|
||||
api_version string = 'v1'
|
||||
kind string = 'Secret'
|
||||
metadata ObjectMeta
|
||||
type_ string = 'Opaque' // Opaque, kubernetes.io/service-account-token, etc.
|
||||
data map[string][]u8
|
||||
string_data map[string]string
|
||||
}
|
||||
|
||||
// ==================== NAMESPACE ====================
|
||||
|
||||
@[params]
|
||||
pub struct Namespace {
|
||||
pub mut:
|
||||
api_version string = 'v1'
|
||||
kind string = 'Namespace'
|
||||
metadata ObjectMeta
|
||||
spec NamespaceSpec
|
||||
status ?NamespaceStatus
|
||||
}
|
||||
|
||||
pub struct NamespaceSpec {
|
||||
pub:
|
||||
finalizers []string
|
||||
}
|
||||
|
||||
pub struct NamespaceStatus {
|
||||
pub:
|
||||
phase string // Active, Terminating
|
||||
}
|
||||
|
||||
// ==================== PERSISTENT VOLUME CLAIM ====================
|
||||
|
||||
@[params]
|
||||
pub struct PersistentVolumeClaim {
|
||||
pub mut:
|
||||
api_version string = 'v1'
|
||||
kind string = 'PersistentVolumeClaim'
|
||||
metadata ObjectMeta
|
||||
spec PersistentVolumeClaimSpec
|
||||
status ?PersistentVolumeClaimStatus
|
||||
}
|
||||
|
||||
@[params]
|
||||
pub struct PersistentVolumeClaimSpec {
|
||||
pub mut:
|
||||
access_modes []string // ReadWriteOnce, ReadOnlyMany, ReadWriteMany
|
||||
resources ResourceRequirements
|
||||
storage_class_name string
|
||||
selector ?LabelSelector
|
||||
volume_name string
|
||||
}
|
||||
|
||||
pub struct PersistentVolumeClaimStatus {
|
||||
pub:
|
||||
phase string // Pending, Bound, Lost
|
||||
access_modes []string
|
||||
capacity map[string]string
|
||||
}
|
||||
|
||||
// ==================== INGRESS ====================
|
||||
|
||||
@[params]
|
||||
pub struct Ingress {
|
||||
pub mut:
|
||||
api_version string = 'networking.k8s.io/v1'
|
||||
kind string = 'Ingress'
|
||||
metadata ObjectMeta
|
||||
spec IngressSpec
|
||||
status ?IngressStatus
|
||||
}
|
||||
|
||||
@[params]
|
||||
pub struct IngressSpec {
|
||||
pub mut:
|
||||
ingress_class_name string
|
||||
rules []IngressRule
|
||||
tls []IngressTLS
|
||||
default_backend ?IngressBackend
|
||||
}
|
||||
|
||||
pub struct IngressRule {
|
||||
pub:
|
||||
host string
|
||||
http ?HTTPIngressRuleValue
|
||||
}
|
||||
|
||||
pub struct HTTPIngressRuleValue {
|
||||
pub:
|
||||
paths []HTTPIngressPath
|
||||
}
|
||||
|
||||
pub struct HTTPIngressPath {
|
||||
pub:
|
||||
path string
|
||||
path_type string // Exact, Prefix
|
||||
backend IngressBackend
|
||||
}
|
||||
|
||||
pub struct IngressBackend {
|
||||
pub:
|
||||
service ?IngressServiceBackend
|
||||
}
|
||||
|
||||
pub struct IngressServiceBackend {
|
||||
pub:
|
||||
name string
|
||||
port IngressServiceBackendPort
|
||||
}
|
||||
|
||||
pub struct IngressServiceBackendPort {
|
||||
pub:
|
||||
number int
|
||||
name string
|
||||
}
|
||||
|
||||
pub struct IngressTLS {
|
||||
pub:
|
||||
hosts []string
|
||||
secret_name string
|
||||
}
|
||||
|
||||
pub struct IngressStatus {
|
||||
pub:
|
||||
load_balancer LoadBalancerStatus
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
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
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
# 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
|
||||
|
||||
```
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
|
||||
|
||||
name: ${cfg.configpath}
|
||||
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: {{.Name}}
|
||||
namespace: {{.Namespace}}
|
||||
data:
|
||||
{{.Key}}: {{.Value}}
|
||||
@@ -1,22 +0,0 @@
|
||||
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}}
|
||||
@@ -1,4 +0,0 @@
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: {{.Name}}
|
||||
@@ -1,13 +0,0 @@
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: {{.Name}}
|
||||
namespace: {{.Namespace}}
|
||||
labels:
|
||||
app: {{.AppLabel}}
|
||||
spec:
|
||||
containers:
|
||||
- name: {{.ContainerName}}
|
||||
image: {{.Image}}
|
||||
ports:
|
||||
- containerPort: {{.ContainerPort}}
|
||||
@@ -1,15 +0,0 @@
|
||||
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}}
|
||||
@@ -1,213 +0,0 @@
|
||||
module kubernetes
|
||||
|
||||
import os
|
||||
import json
|
||||
import net.http
|
||||
import incubaid.herolib.core.pathlib
|
||||
import incubaid.herolib.core.httpconnection
|
||||
import incubaid.herolib.lib.virt.kubernetes
|
||||
import incubaid.herolib.lib.virt.kubernetes.kubernetes_models as km
|
||||
import incubaid.herolib.lib.virt.kubernetes.kubernetes_errors as ke
|
||||
import incubaid.herolib.lib.virt.kubernetes.kubernetes_yaml as ky
|
||||
import incubaid.herolib.lib.virt.kubernetes.kubernetes_config as kc
|
||||
import incubaid.herolib.lib.virt.kubernetes.kubernetes_client as kcl
|
||||
import incubaid.herolib.lib.virt.kubernetes.kubernetes_actions as ka
|
||||
|
||||
// Mock HTTPConnection for testing
|
||||
struct MockHTTPConnection {
|
||||
httpconnection.HTTPConnection
|
||||
pub mut:
|
||||
mock_responses map[string]string // path -> json_response
|
||||
mock_status_codes map[string]int // path -> status_code
|
||||
}
|
||||
|
||||
fn (mut m MockHTTPConnection) get_json_generic[T](prefix string, params map[string]string) !T {
|
||||
if prefix in m.mock_responses {
|
||||
return json.decode[T](m.mock_responses[prefix])!
|
||||
}
|
||||
return error('No mock response for GET ${prefix}')
|
||||
}
|
||||
|
||||
fn (mut m MockHTTPConnection) post_json_generic[T](prefix string, params json.Any, header http.Header) !T {
|
||||
if prefix in m.mock_responses {
|
||||
return json.decode[T](m.mock_responses[prefix])!
|
||||
}
|
||||
return error('No mock response for POST ${prefix}')
|
||||
}
|
||||
|
||||
fn (mut m MockHTTPConnection) put_json_generic[T](prefix string, params json.Any, header http.Header) !T {
|
||||
if prefix in m.mock_responses {
|
||||
return json.decode[T](m.mock_responses[prefix])!
|
||||
}
|
||||
return error('No mock response for PUT ${prefix}')
|
||||
}
|
||||
|
||||
fn (mut m MockHTTPConnection) patch_json_generic[T](prefix string, params map[string]interface{}, header http.Header) !T {
|
||||
if prefix in m.mock_responses {
|
||||
return json.decode[T](m.mock_responses[prefix])!
|
||||
}
|
||||
return error('No mock response for PATCH ${prefix}')
|
||||
}
|
||||
|
||||
fn (mut m MockHTTPConnection) delete(prefix string, params map[string]string) ! {
|
||||
if prefix in m.mock_responses {
|
||||
return
|
||||
}
|
||||
return error('No mock response for DELETE ${prefix}')
|
||||
}
|
||||
|
||||
fn (mut m MockHTTPConnection) get_text(prefix string, params map[string]string) !string {
|
||||
if prefix in m.mock_responses {
|
||||
return m.mock_responses[prefix]
|
||||
}
|
||||
return error('No mock response for GET text ${prefix}')
|
||||
}
|
||||
|
||||
// Helper to create a mock client
|
||||
fn create_mock_client(mock_responses map[string]string) !&kcl.KubernetesClient {
|
||||
mut config := kc.KubernetesConfig{
|
||||
server: 'http://mock-server'
|
||||
insecure_skip_verify: true
|
||||
token: 'mock-token'
|
||||
}
|
||||
mut client := kcl.new(config)!
|
||||
client.http_conn = &MockHTTPConnection{
|
||||
mock_responses: mock_responses
|
||||
}
|
||||
return client
|
||||
}
|
||||
|
||||
// Test cases for kubernetes_errors.v
|
||||
fn test_kubernetes_error() {
|
||||
err := ke.KubernetesError{
|
||||
code: 404
|
||||
reason: 'NotFound'
|
||||
message: 'Pod not found'
|
||||
namespace: 'default'
|
||||
resource: 'my-pod'
|
||||
}
|
||||
assert err.msg() == 'NotFound (404): Pod not found in default/my-pod'
|
||||
assert err.code() == 404
|
||||
}
|
||||
|
||||
fn test_resource_not_found_error() {
|
||||
err := ke.ResourceNotFoundError{
|
||||
resource_type: 'Pod'
|
||||
resource_name: 'non-existent-pod'
|
||||
namespace: 'test-ns'
|
||||
}
|
||||
assert err.resource_type == 'Pod'
|
||||
assert err.resource_name == 'non-existent-pod'
|
||||
assert err.namespace == 'test-ns'
|
||||
}
|
||||
|
||||
// Test cases for kubernetes_models.v (basic serialization/deserialization)
|
||||
fn test_pod_model_yaml_serialization() {
|
||||
mut pod := km.Pod{
|
||||
metadata: km.ObjectMeta{
|
||||
name: 'test-pod'
|
||||
namespace: 'default'
|
||||
}
|
||||
spec: km.PodSpec{
|
||||
containers: [
|
||||
km.Container{
|
||||
name: 'test-container'
|
||||
image: 'nginx:latest'
|
||||
ports: [km.ContainerPort{ container_port: 80 }]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
yaml_str := ky.to_yaml(pod)!
|
||||
assert yaml_str.contains('name: test-pod')
|
||||
assert yaml_str.contains('image: nginx:latest')
|
||||
|
||||
decoded_pod := ky.from_yaml[km.Pod](yaml_str)!
|
||||
assert decoded_pod.metadata.name == 'test-pod'
|
||||
assert decoded_pod.spec.containers[0].image == 'nginx:latest'
|
||||
}
|
||||
|
||||
// Test cases for kubernetes_config.v
|
||||
fn test_config_validation() {
|
||||
// Valid config
|
||||
cfg := kc.KubernetesConfig{
|
||||
server: 'https://localhost:6443'
|
||||
token: 'test-token'
|
||||
insecure_skip_verify: true
|
||||
}
|
||||
cfg.validate()!
|
||||
|
||||
// Invalid config (missing server)
|
||||
mut invalid_cfg := kc.KubernetesConfig{
|
||||
token: 'test-token'
|
||||
}
|
||||
assert invalid_cfg.validate() is error
|
||||
|
||||
// Invalid config (missing auth)
|
||||
mut invalid_cfg2 := kc.KubernetesConfig{
|
||||
server: 'https://localhost:6443'
|
||||
}
|
||||
assert invalid_cfg2.validate() is error
|
||||
}
|
||||
|
||||
// Test cases for kubernetes_client.v
|
||||
fn test_client_get_pod() {
|
||||
mock_responses := {
|
||||
'/api/v1/namespaces/default/pods/my-pod': '{ "kind": "Pod", "apiVersion": "v1", "metadata": { "name": "my-pod", "namespace": "default" }, "spec": {}, "status": {} }'
|
||||
}
|
||||
mut client := create_mock_client(mock_responses)!
|
||||
|
||||
pod := client.get[km.Pod](
|
||||
resource_type: 'pods'
|
||||
name: 'my-pod'
|
||||
namespace: 'default'
|
||||
)!
|
||||
assert pod.metadata.name == 'my-pod'
|
||||
}
|
||||
|
||||
fn test_client_list_deployments() {
|
||||
mock_responses := {
|
||||
'/apis/apps/v1/namespaces/default/deployments': '{ "kind": "DeploymentList", "apiVersion": "apps/v1", "items": [ { "kind": "Deployment", "apiVersion": "apps/v1", "metadata": { "name": "dep1" }, "spec": {}, "status": {} } ] }'
|
||||
}
|
||||
mut client := create_mock_client(mock_responses)!
|
||||
|
||||
// This test will currently fail because the list function in kubernetes_client.v
|
||||
// returns an empty array. The TODO needs to be implemented.
|
||||
// deployments := client.list[km.Deployment](
|
||||
// resource_type: 'deployments'
|
||||
// namespace: 'default'
|
||||
// )!
|
||||
// assert deployments.len == 1
|
||||
// assert deployments[0].metadata.name == 'dep1'
|
||||
}
|
||||
|
||||
// Test cases for kubernetes_actions.v
|
||||
fn test_deploy_new_deployment() {
|
||||
mock_responses := {
|
||||
'/apis/apps/v1/namespaces/default/deployments/new-dep': '{ "kind": "Deployment", "apiVersion": "apps/v1", "metadata": { "name": "new-dep", "namespace": "default", "resourceVersion": "1" }, "spec": { "replicas": 1 }, "status": { "observedGeneration": 1, "updatedReplicas": 1, "readyReplicas": 1 } }',
|
||||
'/apis/apps/v1/namespaces/default/deployments': '{ "kind": "DeploymentList", "apiVersion": "apps/v1", "items": [] }' // No existing deployment
|
||||
}
|
||||
mut client := create_mock_client(mock_responses)!
|
||||
|
||||
mut deployment := km.Deployment{
|
||||
metadata: km.ObjectMeta{
|
||||
name: 'new-dep'
|
||||
namespace: 'default'
|
||||
}
|
||||
spec: km.DeploymentSpec{
|
||||
replicas: 1
|
||||
template: km.PodTemplateSpec{
|
||||
spec: km.PodSpec{
|
||||
containers: [km.Container{ name: 'app', image: 'img' }]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This test will currently fail because the deploy function in kubernetes_actions.v
|
||||
// calls client.get which is mocked to return none, but the match statement expects Deployment.
|
||||
// Also, the wait_for_deployment_ready will loop indefinitely without proper mock for get.
|
||||
// deployed := client.deploy(deployment: deployment, wait: false)!
|
||||
// assert deployed.metadata.name == 'new-dep'
|
||||
}
|
||||
@@ -1,295 +0,0 @@
|
||||
module kubernetes
|
||||
|
||||
import incubaid.herolib.lib.virt.kubernetes.kubernetes_models as km
|
||||
import incubaid.herolib.lib.virt.kubernetes.kubernetes_yaml as ky
|
||||
import incubaid.herolib.data.ourtime
|
||||
|
||||
fn test_object_meta_serialization() {
|
||||
mut meta := km.ObjectMeta{
|
||||
name: 'my-object'
|
||||
namespace: 'default'
|
||||
labels: {
|
||||
'app': 'test'
|
||||
'env': 'dev'
|
||||
}
|
||||
annotations: {
|
||||
'note': 'some annotation'
|
||||
}
|
||||
}
|
||||
|
||||
yaml_str := ky.to_yaml(meta)!
|
||||
assert yaml_str.contains('name: my-object')
|
||||
assert yaml_str.contains('namespace: default')
|
||||
assert yaml_str.contains('app: test')
|
||||
assert yaml_str.contains('note: some annotation')
|
||||
|
||||
decoded_meta := ky.from_yaml[km.ObjectMeta](yaml_str)!
|
||||
assert decoded_meta.name == 'my-object'
|
||||
assert decoded_meta.namespace == 'default'
|
||||
assert decoded_meta.labels['app'] == 'test'
|
||||
assert decoded_meta.annotations['note'] == 'some annotation'
|
||||
}
|
||||
|
||||
fn test_pod_full_serialization() {
|
||||
mut pod := km.Pod{
|
||||
metadata: km.ObjectMeta{
|
||||
name: 'full-pod'
|
||||
namespace: 'test-ns'
|
||||
labels: { 'run': 'full-pod' }
|
||||
}
|
||||
spec: km.PodSpec{
|
||||
containers: [
|
||||
km.Container{
|
||||
name: 'main-container'
|
||||
image: 'ubuntu:latest'
|
||||
ports: [km.ContainerPort{ container_port: 8080, protocol: 'TCP' }]
|
||||
env: [
|
||||
km.EnvVar{ name: 'ENV_VAR_1', value: 'value1' },
|
||||
km.EnvVar{ name: 'ENV_VAR_2', value_from: km.EnvVarSource{ config_map_key_ref: km.ConfigMapKeySelector{ name: 'my-config', key: 'key1' } } }
|
||||
]
|
||||
resources: km.ResourceRequirements{
|
||||
limits: { 'cpu': '100m', 'memory': '128Mi' }
|
||||
requests: { 'cpu': '50m', 'memory': '64Mi' }
|
||||
}
|
||||
liveness_probe: km.Probe{
|
||||
http_get: km.HTTPGetAction{ path: '/healthz', port: 8080 }
|
||||
initial_delay_seconds: 5
|
||||
}
|
||||
}
|
||||
]
|
||||
volumes: [
|
||||
km.Volume{
|
||||
name: 'config-volume'
|
||||
config_map: km.ConfigMapVolumeSource{
|
||||
name: 'my-config'
|
||||
items: [km.KeyToPath{ key: 'config.txt', path: 'config.txt' }]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
yaml_str := ky.to_yaml(pod)!
|
||||
assert yaml_str.contains('name: full-pod')
|
||||
assert yaml_str.contains('image: ubuntu:latest')
|
||||
assert yaml_str.contains('containerPort: 8080')
|
||||
assert yaml_str.contains('ENV_VAR_1')
|
||||
assert yaml_str.contains('cpu: 100m')
|
||||
assert yaml_str.contains('path: /healthz')
|
||||
assert yaml_str.contains('name: config-volume')
|
||||
|
||||
decoded_pod := ky.from_yaml[km.Pod](yaml_str)!
|
||||
assert decoded_pod.metadata.name == 'full-pod'
|
||||
assert decoded_pod.spec.containers[0].name == 'main-container'
|
||||
assert decoded_pod.spec.containers[0].env[0].name == 'ENV_VAR_1'
|
||||
assert decoded_pod.spec.containers[0].resources!.limits['cpu'] == '100m'
|
||||
assert decoded_pod.spec.volumes[0].name == 'config-volume'
|
||||
}
|
||||
|
||||
fn test_deployment_serialization() {
|
||||
mut deployment := km.Deployment{
|
||||
metadata: km.ObjectMeta{
|
||||
name: 'my-deployment'
|
||||
namespace: 'default'
|
||||
}
|
||||
spec: km.DeploymentSpec{
|
||||
replicas: 3
|
||||
selector: km.LabelSelector{
|
||||
match_labels: { 'app': 'my-app' }
|
||||
}
|
||||
template: km.PodTemplateSpec{
|
||||
metadata: km.ObjectMeta{
|
||||
labels: { 'app': 'my-app' }
|
||||
}
|
||||
spec: km.PodSpec{
|
||||
containers: [
|
||||
km.Container{
|
||||
name: 'web'
|
||||
image: 'my-image:1.0'
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
yaml_str := ky.to_yaml(deployment)!
|
||||
assert yaml_str.contains('name: my-deployment')
|
||||
assert yaml_str.contains('replicas: 3')
|
||||
assert yaml_str.contains('app: my-app')
|
||||
assert yaml_str.contains('image: my-image:1.0')
|
||||
|
||||
decoded_deployment := ky.from_yaml[km.Deployment](yaml_str)!
|
||||
assert decoded_deployment.metadata.name == 'my-deployment'
|
||||
assert decoded_deployment.spec.replicas == 3
|
||||
assert decoded_deployment.spec.selector!.match_labels['app'] == 'my-app'
|
||||
}
|
||||
|
||||
fn test_service_serialization() {
|
||||
mut service := km.Service{
|
||||
metadata: km.ObjectMeta{
|
||||
name: 'my-service'
|
||||
namespace: 'default'
|
||||
}
|
||||
spec: km.ServiceSpec{
|
||||
service_type: 'NodePort'
|
||||
selector: { 'app': 'my-app' }
|
||||
ports: [
|
||||
km.ServicePort{
|
||||
port: 80
|
||||
target_port: '8080'
|
||||
node_port: 30000
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
yaml_str := ky.to_yaml(service)!
|
||||
assert yaml_str.contains('name: my-service')
|
||||
assert yaml_str.contains('type: NodePort')
|
||||
assert yaml_str.contains('port: 80')
|
||||
assert yaml_str.contains('targetPort: "8080"')
|
||||
assert yaml_str.contains('nodePort: 30000')
|
||||
|
||||
decoded_service := ky.from_yaml[km.Service](yaml_str)!
|
||||
assert decoded_service.metadata.name == 'my-service'
|
||||
assert decoded_service.spec.service_type == 'NodePort'
|
||||
assert decoded_service.spec.ports[0].port == 80
|
||||
assert decoded_service.spec.ports[0].target_port == '8080'
|
||||
assert decoded_service.spec.ports[0].node_port == 30000
|
||||
}
|
||||
|
||||
fn test_configmap_serialization() {
|
||||
mut configmap := km.ConfigMap{
|
||||
metadata: km.ObjectMeta{
|
||||
name: 'my-configmap'
|
||||
namespace: 'default'
|
||||
}
|
||||
data: {
|
||||
'key1': 'value1'
|
||||
'key2': 'value2'
|
||||
}
|
||||
}
|
||||
|
||||
yaml_str := ky.to_yaml(configmap)!
|
||||
assert yaml_str.contains('name: my-configmap')
|
||||
assert yaml_str.contains('key1: value1')
|
||||
|
||||
decoded_configmap := ky.from_yaml[km.ConfigMap](yaml_str)!
|
||||
assert decoded_configmap.metadata.name == 'my-configmap'
|
||||
assert decoded_configmap.data['key1'] == 'value1'
|
||||
}
|
||||
|
||||
fn test_secret_serialization() {
|
||||
mut secret := km.Secret{
|
||||
metadata: km.ObjectMeta{
|
||||
name: 'my-secret'
|
||||
namespace: 'default'
|
||||
}
|
||||
string_data: {
|
||||
'username': 'admin'
|
||||
'password': 'password123'
|
||||
}
|
||||
}
|
||||
|
||||
yaml_str := ky.to_yaml(secret)!
|
||||
assert yaml_str.contains('name: my-secret')
|
||||
assert yaml_str.contains('username: admin')
|
||||
|
||||
decoded_secret := ky.from_yaml[km.Secret](yaml_str)!
|
||||
assert decoded_secret.metadata.name == 'my-secret'
|
||||
assert decoded_secret.string_data['username'] == 'admin'
|
||||
}
|
||||
|
||||
fn test_namespace_serialization() {
|
||||
mut namespace := km.Namespace{
|
||||
metadata: km.ObjectMeta{
|
||||
name: 'my-namespace'
|
||||
}
|
||||
}
|
||||
|
||||
yaml_str := ky.to_yaml(namespace)!
|
||||
assert yaml_str.contains('name: my-namespace')
|
||||
|
||||
decoded_namespace := ky.from_yaml[km.Namespace](yaml_str)!
|
||||
assert decoded_namespace.metadata.name == 'my-namespace'
|
||||
}
|
||||
|
||||
fn test_persistent_volume_claim_serialization() {
|
||||
mut pvc := km.PersistentVolumeClaim{
|
||||
metadata: km.ObjectMeta{
|
||||
name: 'my-pvc'
|
||||
namespace: 'default'
|
||||
}
|
||||
spec: km.PersistentVolumeClaimSpec{
|
||||
access_modes: ['ReadWriteOnce']
|
||||
resources: km.ResourceRequirements{
|
||||
requests: { 'storage': '1Gi' }
|
||||
}
|
||||
storage_class_name: 'standard'
|
||||
}
|
||||
}
|
||||
|
||||
yaml_str := ky.to_yaml(pvc)!
|
||||
assert yaml_str.contains('name: my-pvc')
|
||||
assert yaml_str.contains('accessModes:')
|
||||
assert yaml_str.contains('- ReadWriteOnce')
|
||||
assert yaml_str.contains('storage: 1Gi')
|
||||
|
||||
decoded_pvc := ky.from_yaml[km.PersistentVolumeClaim](yaml_str)!
|
||||
assert decoded_pvc.metadata.name == 'my-pvc'
|
||||
assert decoded_pvc.spec.access_modes[0] == 'ReadWriteOnce'
|
||||
assert decoded_pvc.spec.resources.requests['storage'] == '1Gi'
|
||||
}
|
||||
|
||||
fn test_ingress_serialization() {
|
||||
mut ingress := km.Ingress{
|
||||
metadata: km.ObjectMeta{
|
||||
name: 'my-ingress'
|
||||
namespace: 'default'
|
||||
}
|
||||
spec: km.IngressSpec{
|
||||
ingress_class_name: 'nginx'
|
||||
rules: [
|
||||
km.IngressRule{
|
||||
host: 'example.com'
|
||||
http: km.HTTPIngressRuleValue{
|
||||
paths: [
|
||||
km.HTTPIngressPath{
|
||||
path: '/'
|
||||
path_type: 'Prefix'
|
||||
backend: km.IngressBackend{
|
||||
service: km.IngressServiceBackend{
|
||||
name: 'my-service'
|
||||
port: km.IngressServiceBackendPort{ number: 80 }
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
tls: [
|
||||
km.IngressTLS{
|
||||
hosts: ['example.com']
|
||||
secret_name: 'example-tls'
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
yaml_str := ky.to_yaml(ingress)!
|
||||
assert yaml_str.contains('name: my-ingress')
|
||||
assert yaml_str.contains('host: example.com')
|
||||
assert yaml_str.contains('path: /')
|
||||
assert yaml_str.contains('service:')
|
||||
assert yaml_str.contains('name: my-service')
|
||||
assert yaml_str.contains('number: 80')
|
||||
assert yaml_str.contains('secretName: example-tls')
|
||||
|
||||
decoded_ingress := ky.from_yaml[km.Ingress](yaml_str)!
|
||||
assert decoded_ingress.metadata.name == 'my-ingress'
|
||||
assert decoded_ingress.spec.rules[0].host == 'example.com'
|
||||
assert decoded_ingress.spec.rules[0].http!.paths[0].backend.service!.name == 'my-service'
|
||||
assert decoded_ingress.spec.tls[0].secret_name == 'example-tls'
|
||||
}
|
||||
Reference in New Issue
Block a user