diff --git a/lib/clients/traefik/factory.v b/lib/clients/traefik/factory.v new file mode 100644 index 00000000..2c6a7878 --- /dev/null +++ b/lib/clients/traefik/factory.v @@ -0,0 +1,51 @@ +module traefik + +import freeflowuniverse.herolib.core.texttools +import freeflowuniverse.herolib.core.redisclient +import freeflowuniverse.herolib.osal.traefik as osal_traefik + +__global ( + traefik_managers map[string]&TraefikManager +) + +@[params] +pub struct FactoryArgs { +pub mut: + name string = 'default' + redis_url string = '127.0.0.1:6379' +} + +pub fn new(args FactoryArgs) !&TraefikManager { + name := texttools.name_fix(args.name) + if name in traefik_managers { + return traefik_managers[name] + } + + mut redis := redisclient.core_get(redisclient.get_redis_url(args.redis_url)!)! + + mut manager := &TraefikManager{ + name: name + redis: redis + config: osal_traefik.new_traefik_config() + } + + // Set redis connection in config + manager.config.redis = redis + + traefik_managers[name] = manager + return manager +} + +pub fn get(args FactoryArgs) !&TraefikManager { + name := texttools.name_fix(args.name) + return traefik_managers[name] or { + return error('traefik manager with name "${name}" does not exist') + } +} + +pub fn default() !&TraefikManager { + if traefik_managers.len == 0 { + return new(name: 'default')! + } + return get(name: 'default')! +} \ No newline at end of file diff --git a/lib/clients/traefik/manager.v b/lib/clients/traefik/manager.v new file mode 100644 index 00000000..b5466c02 --- /dev/null +++ b/lib/clients/traefik/manager.v @@ -0,0 +1,154 @@ +module traefik + +import freeflowuniverse.herolib.core.redisclient +import freeflowuniverse.herolib.osal.traefik as osal_traefik +import freeflowuniverse.herolib.core.texttools + +@[heap] +pub struct TraefikManager { +pub mut: + name string + redis &redisclient.Redis + config osal_traefik.TraefikConfig + entrypoints []EntryPointConfig +} + +pub struct EntryPointConfig { +pub mut: + name string @[required] + address string @[required] + tls bool +} + +@[params] +pub struct RouterAddArgs { +pub mut: + name string @[required] + rule string @[required] + service string @[required] + entrypoints []string + middlewares []string + tls bool + priority int +} + +@[params] +pub struct ServiceAddArgs { +pub mut: + name string @[required] + servers []string @[required] + strategy string = 'wrr' // wrr or p2c +} + +@[params] +pub struct MiddlewareAddArgs { +pub mut: + name string @[required] + typ string @[required] + settings map[string]string +} + +@[params] +pub struct EntryPointAddArgs { +pub mut: + name string @[required] + address string @[required] + tls bool +} + +// Add router configuration +pub fn (mut tm TraefikManager) router_add(args RouterAddArgs) ! { + tm.config.add_route( + name: texttools.name_fix(args.name) + rule: args.rule + service: texttools.name_fix(args.service) + middlewares: args.middlewares.map(texttools.name_fix(it)) + priority: args.priority + tls: args.tls + ) +} + +// Add service configuration +pub fn (mut tm TraefikManager) service_add(args ServiceAddArgs) ! { + mut servers := []osal_traefik.ServerConfig{} + for server_url in args.servers { + servers << osal_traefik.ServerConfig{ + url: server_url.trim_space() + } + } + + tm.config.add_service( + name: texttools.name_fix(args.name) + load_balancer: osal_traefik.LoadBalancerConfig{ + servers: servers + } + ) +} + +// Add middleware configuration +pub fn (mut tm TraefikManager) middleware_add(args MiddlewareAddArgs) ! { + tm.config.add_middleware( + name: texttools.name_fix(args.name) + typ: args.typ + settings: args.settings + ) +} + +// Add entrypoint configuration (stored separately as these are typically static config) +pub fn (mut tm TraefikManager) entrypoint_add(args EntryPointAddArgs) ! { + entrypoint := EntryPointConfig{ + name: texttools.name_fix(args.name) + address: args.address + tls: args.tls + } + + // Check if entrypoint already exists + for mut ep in tm.entrypoints { + if ep.name == entrypoint.name { + ep.address = entrypoint.address + ep.tls = entrypoint.tls + return + } + } + + tm.entrypoints << entrypoint +} + +// Apply all configurations to Redis +pub fn (mut tm TraefikManager) apply() ! { + // Apply dynamic configuration (routers, services, middlewares) + tm.config.set()! + + // Store entrypoints separately (these would typically be in static config) + for ep in tm.entrypoints { + tm.redis.hset('traefik:entrypoints', ep.name, '${ep.address}|${ep.tls}')! + } +} + +// Get all entrypoints +pub fn (mut tm TraefikManager) entrypoints_get() ![]EntryPointConfig { + return tm.entrypoints.clone() +} + +// Clear all configurations +pub fn (mut tm TraefikManager) clear() ! { + tm.config = osal_traefik.new_traefik_config() + tm.config.redis = tm.redis + tm.entrypoints = []EntryPointConfig{} + + // Clear Redis keys + keys := tm.redis.keys('traefik/*')! + for key in keys { + tm.redis.del(key)! + } +} + +// Get configuration status +pub fn (mut tm TraefikManager) status() !map[string]int { + return { + 'routers': tm.config.routers.len + 'services': tm.config.services.len + 'middlewares': tm.config.middlewares.len + 'entrypoints': tm.entrypoints.len + } +} \ No newline at end of file diff --git a/lib/clients/traefik/play.v b/lib/clients/traefik/play.v new file mode 100644 index 00000000..991ffc77 --- /dev/null +++ b/lib/clients/traefik/play.v @@ -0,0 +1,168 @@ +module traefik + +import freeflowuniverse.herolib.core.playbook { PlayBook } +import freeflowuniverse.herolib.core.texttools +import freeflowuniverse.herolib.ui.console + +pub fn play(mut plbook PlayBook) ! { + if !plbook.exists(filter: 'traefik.') { + return + } + + // Get or create default traefik manager + mut manager := default()! + + // Process entrypoints first + play_entrypoints(mut plbook, mut manager)! + + // Process services (before routers that might reference them) + play_services(mut plbook, mut manager)! + + // Process middlewares (before routers that might reference them) + play_middlewares(mut plbook, mut manager)! + + // Process routers + play_routers(mut plbook, mut manager)! + + // Apply all configurations to Redis + manager.apply()! + + console.print_debug('Traefik configuration applied successfully') +} + +fn play_entrypoints(mut plbook PlayBook, mut manager TraefikManager) ! { + entrypoint_actions := plbook.find(filter: 'traefik.entrypoint')! + + for mut action in entrypoint_actions { + mut p := action.params + + manager.entrypoint_add( + name: p.get('name')! + address: p.get('address')! + tls: p.get_default_false('tls') + )! + + action.done = true + } +} + +fn play_routers(mut plbook PlayBook, mut manager TraefikManager) ! { + router_actions := plbook.find(filter: 'traefik.router')! + + for mut action in router_actions { + mut p := action.params + + // Parse entrypoints list + mut entrypoints := []string{} + if entrypoints_str := p.get_default('entrypoints', '') { + if entrypoints_str.len > 0 { + entrypoints = entrypoints_str.split(',').map(it.trim_space()) + } + } + + // Parse middlewares list + mut middlewares := []string{} + if middlewares_str := p.get_default('middlewares', '') { + if middlewares_str.len > 0 { + middlewares = middlewares_str.split(',').map(it.trim_space()) + } + } + + manager.router_add( + name: p.get('name')! + rule: p.get('rule')! + service: p.get('service')! + entrypoints: entrypoints + middlewares: middlewares + tls: p.get_default_false('tls') + priority: p.get_int_default('priority', 0) + )! + + action.done = true + } +} + +fn play_services(mut plbook PlayBook, mut manager TraefikManager) ! { + service_actions := plbook.find(filter: 'traefik.service')! + + for mut action in service_actions { + mut p := action.params + + // Parse servers list + servers_str := p.get('servers')! + servers := servers_str.split(',').map(it.trim_space()) + + manager.service_add( + name: p.get('name')! + servers: servers + strategy: p.get_default('strategy', 'wrr')! + )! + + action.done = true + } +} + +fn play_middlewares(mut plbook PlayBook, mut manager TraefikManager) ! { + middleware_actions := plbook.find(filter: 'traefik.middleware')! + + for mut action in middleware_actions { + mut p := action.params + + // Build settings map from remaining parameters + mut settings := map[string]string{} + + middleware_type := p.get('type')! + + // Handle common middleware types + match middleware_type { + 'basicAuth' { + if users := p.get_default('users', '') { + settings['users'] = '["${users}"]' + } + } + 'stripPrefix' { + if prefixes := p.get_default('prefixes', '') { + settings['prefixes'] = '["${prefixes}"]' + } + } + 'addPrefix' { + if prefix := p.get_default('prefix', '') { + settings['prefix'] = prefix + } + } + 'headers' { + if custom_headers := p.get_default('customRequestHeaders', '') { + settings['customRequestHeaders'] = custom_headers + } + if custom_response_headers := p.get_default('customResponseHeaders', '') { + settings['customResponseHeaders'] = custom_response_headers + } + } + 'rateLimit' { + if rate := p.get_default('rate', '') { + settings['rate'] = rate + } + if burst := p.get_default('burst', '') { + settings['burst'] = burst + } + } + else { + // For other middleware types, get all parameters as settings + param_map := p.get_map() + for key, value in param_map { + if key !in ['name', 'type'] { + settings[key] = value + } + } + } + } + + manager.middleware_add( + name: p.get('name')! + typ: middleware_type + settings: settings + )! + + action.done = true + } +} \ No newline at end of file diff --git a/lib/osal/tmux/readme.md b/lib/osal/tmux/readme.md index 53488a75..052dcf74 100644 --- a/lib/osal/tmux/readme.md +++ b/lib/osal/tmux/readme.md @@ -3,6 +3,8 @@ TMUX is a very capable process manager. +> TODO: TTYD, need to integrate with TMUX for exposing TMUX over http + ### Concepts - tmux = is the factory, it represents the tmux process manager, linked to a node