model done

This commit is contained in:
2025-01-31 17:42:06 +03:00
parent bb69ee0be9
commit 99ecf1b0d8
12 changed files with 185 additions and 194 deletions

View File

@@ -5,10 +5,10 @@ import freeflowuniverse.herolib.data.ourtime
// Agent represents a service provider that can execute jobs // Agent represents a service provider that can execute jobs
pub struct Agent { pub struct Agent {
pub mut: pub mut:
pubkey string // pubkey using ed25519 pubkey string // pubkey using ed25519
address string // where we can find the agent address string // where we can find the agent
port int // default 9999 port int // default 9999
description string // optional description string // optional
status AgentStatus status AgentStatus
services []AgentService // these are the public services services []AgentService // these are the public services
signature string // signature as done by private key of $address+$port+$description+$status signature string // signature as done by private key of $address+$port+$description+$status
@@ -17,30 +17,30 @@ pub mut:
// AgentStatus represents the current state of an agent // AgentStatus represents the current state of an agent
pub struct AgentStatus { pub struct AgentStatus {
pub mut: pub mut:
guid string // unique id for the job guid string // unique id for the job
timestamp_first ourtime.Time // when agent came online timestamp_first ourtime.OurTime // when agent came online
timestamp_last ourtime.Time // last time agent let us know that he is working timestamp_last ourtime.OurTime // last time agent let us know that he is working
status AgentState // current state of the agent status AgentState // current state of the agent
} }
// AgentService represents a service provided by an agent // AgentService represents a service provided by an agent
pub struct AgentService { pub struct AgentService {
pub mut: pub mut:
actor string // name of the actor providing the service actor string // name of the actor providing the service
actions []AgentServiceAction // available actions for this service actions []AgentServiceAction // available actions for this service
description string // optional description description string // optional description
status AgentServiceState // current state of the service status AgentServiceState // current state of the service
} }
// AgentServiceAction represents an action that can be performed by a service // AgentServiceAction represents an action that can be performed by a service
pub struct AgentServiceAction { pub struct AgentServiceAction {
pub mut: pub mut:
action string // which action action string // which action
description string // optional description description string // optional description
params map[string]string // e.g. name:'name of the vm' ... params map[string]string // e.g. name:'name of the vm' ...
params_example map[string]string // e.g. name:'myvm' params_example map[string]string // e.g. name:'myvm'
status AgentServiceState // current state of the action status AgentServiceState // current state of the action
public bool // if everyone can use then true, if restricted means only certain people can use public bool // if everyone can use then true, if restricted means only certain people can use
} }
// AgentState represents the possible states of an agent // AgentState represents the possible states of an agent

View File

@@ -4,9 +4,7 @@ import freeflowuniverse.herolib.core.redisclient
import freeflowuniverse.herolib.data.ourtime import freeflowuniverse.herolib.data.ourtime
import json import json
const ( const agents_key = 'herorunner:agents' // Redis key for storing agents
agents_key = 'herorunner:agents' // Redis key for storing agents
)
// AgentManager handles all agent-related operations // AgentManager handles all agent-related operations
pub struct AgentManager { pub struct AgentManager {
@@ -17,13 +15,13 @@ mut:
// new creates a new Agent instance // new creates a new Agent instance
pub fn (mut m AgentManager) new() Agent { pub fn (mut m AgentManager) new() Agent {
return Agent{ return Agent{
pubkey: '' // Empty pubkey to be filled by caller pubkey: '' // Empty pubkey to be filled by caller
port: 9999 // Default port port: 9999 // Default port
status: AgentStatus{ status: AgentStatus{
guid: '' guid: ''
timestamp_first: ourtime.Time{} timestamp_first: ourtime.now()
timestamp_last: ourtime.Time{} timestamp_last: ourtime.OurTime{}
status: .ok status: .ok
} }
services: []AgentService{} services: []AgentService{}
} }
@@ -67,7 +65,7 @@ pub fn (mut m AgentManager) delete(pubkey string) ! {
pub fn (mut m AgentManager) update_status(pubkey string, status AgentState) ! { pub fn (mut m AgentManager) update_status(pubkey string, status AgentState) ! {
mut agent := m.get(pubkey)! mut agent := m.get(pubkey)!
agent.status.status = status agent.status.status = status
m.update(agent)! m.set(agent)!
} }
// get_by_service returns all agents that provide a specific service // get_by_service returns all agents that provide a specific service

View File

@@ -4,8 +4,7 @@ import freeflowuniverse.herolib.core.redisclient
import freeflowuniverse.herolib.data.ourtime import freeflowuniverse.herolib.data.ourtime
fn test_agents_model() { fn test_agents_model() {
mut runner := new()!
mut runner:=new()!
// Create a new agent using the manager // Create a new agent using the manager
mut agent := runner.agents.new() mut agent := runner.agents.new()
@@ -15,24 +14,24 @@ fn test_agents_model() {
// Create a service action // Create a service action
mut action := AgentServiceAction{ mut action := AgentServiceAction{
action: 'start' action: 'start'
description: 'Start a VM' description: 'Start a VM'
params: { params: {
'name': 'string' 'name': 'string'
} }
params_example: { params_example: {
'name': 'myvm' 'name': 'myvm'
} }
status: .ok status: .ok
public: true public: true
} }
// Create a service // Create a service
mut service := AgentService{ mut service := AgentService{
actor: 'vm_manager' actor: 'vm_manager'
actions: [action] actions: [action]
description: 'VM Management Service' description: 'VM Management Service'
status: .ok status: .ok
} }
agent.services = [service] agent.services = [service]

View File

@@ -7,10 +7,10 @@ pub struct HeroRunner {
mut: mut:
redis &redisclient.Redis redis &redisclient.Redis
pub mut: pub mut:
jobs &JobManager jobs &JobManager
agents &AgentManager agents &AgentManager
services &ServiceManager services &ServiceManager
groups &GroupManager groups &GroupManager
} }
// new creates a new HeroRunner instance // new creates a new HeroRunner instance
@@ -18,17 +18,17 @@ pub fn new() !&HeroRunner {
mut redis := redisclient.core_get()! mut redis := redisclient.core_get()!
mut hr := &HeroRunner{ mut hr := &HeroRunner{
redis: redis redis: redis
jobs: &JobManager{ jobs: &JobManager{
redis: redis redis: redis
} }
agents: &AgentManager{ agents: &AgentManager{
redis: redis redis: redis
} }
services: &ServiceManager{ services: &ServiceManager{
redis: redis redis: redis
} }
groups: &GroupManager{ groups: &GroupManager{
redis: redis redis: redis
} }
} }

View File

@@ -3,9 +3,7 @@ module model
import freeflowuniverse.herolib.core.redisclient import freeflowuniverse.herolib.core.redisclient
import json import json
const ( const groups_key = 'herorunner:groups' // Redis key for storing groups
groups_key = 'herorunner:groups' // Redis key for storing groups
)
// GroupManager handles all group-related operations // GroupManager handles all group-related operations
pub struct GroupManager { pub struct GroupManager {
@@ -16,7 +14,7 @@ mut:
// new creates a new Group instance // new creates a new Group instance
pub fn (mut m GroupManager) new() Group { pub fn (mut m GroupManager) new() Group {
return Group{ return Group{
guid: '' // Empty GUID to be filled by caller guid: '' // Empty GUID to be filled by caller
members: []string{} members: []string{}
} }
} }
@@ -49,6 +47,7 @@ pub fn (mut m GroupManager) list() ![]Group {
return groups return groups
} }
// delete removes a group by its GUID // delete removes a group by its GUID
pub fn (mut m GroupManager) delete(guid string) ! { pub fn (mut m GroupManager) delete(guid string) ! {
m.redis.hdel(groups_key, guid)! m.redis.hdel(groups_key, guid)!
@@ -59,7 +58,7 @@ pub fn (mut m GroupManager) add_member(guid string, member string) ! {
mut group := m.get(guid)! mut group := m.get(guid)!
if member !in group.members { if member !in group.members {
group.members << member group.members << member
m.update(group)! m.set(group)!
} }
} }
@@ -67,38 +66,34 @@ pub fn (mut m GroupManager) add_member(guid string, member string) ! {
pub fn (mut m GroupManager) remove_member(guid string, member string) ! { pub fn (mut m GroupManager) remove_member(guid string, member string) ! {
mut group := m.get(guid)! mut group := m.get(guid)!
group.members = group.members.filter(it != member) group.members = group.members.filter(it != member)
m.update(group)! m.set(group)!
} }
// get_user_groups returns all groups that a user is a member of (directly or indirectly)
pub fn (mut m GroupManager) get_user_groups(user_pubkey string) ![]Group { pub fn (mut m GroupManager) get_user_groups(user_pubkey string) ![]Group {
mut user_groups := []Group{} mut user_groups := []Group{}
mut checked_groups := map[string]bool{} mut checked_groups := map[string]bool{}
groups := m.list()! groups := m.list()!
// Recursive function to check group membership
fn check_group_membership(group Group, user string, groups []Group, mut checked map[string]bool, mut result []Group) {
if group.guid in checked {
return
}
checked[group.guid] = true
if user in group.members {
result << group
// Check parent groups
for parent_group in groups {
if group.guid in parent_group.members {
check_group_membership(parent_group, user, groups, mut checked, mut result)
}
}
}
}
// Check each group // Check each group
for group in groups { for group in groups {
check_group_membership(group, user_pubkey, groups, mut checked_groups, mut user_groups) check_group_membership(group, user_pubkey, groups, mut checked_groups, mut user_groups)
} }
return user_groups return user_groups
} }
// Recursive function to check group membership
fn check_group_membership(group Group, user string, groups []Group, mut checked map[string]bool, mut result []Group) {
if group.guid in checked {
return
}
checked[group.guid] = true
if user in group.members {
result << group
// Check parent groups
for parent_group in groups {
if group.guid in parent_group.members {
check_group_membership(parent_group, user, groups, mut checked, mut result)
}
}
}
}

View File

@@ -3,7 +3,7 @@ module model
import freeflowuniverse.herolib.core.redisclient import freeflowuniverse.herolib.core.redisclient
fn test_groups() { fn test_groups() {
mut runner:=new()! mut runner := new()!
// Create a new group using the manager // Create a new group using the manager
mut group := runner.groups.new() mut group := runner.groups.new()
@@ -20,7 +20,7 @@ fn test_groups() {
subgroup.name = 'VM Administrators' subgroup.name = 'VM Administrators'
subgroup.description = 'VM management administrators' subgroup.description = 'VM management administrators'
runner.groups.add(subgroup)! runner.groups.set(subgroup)!
// Add subgroup to main group // Add subgroup to main group
runner.groups.add_member(group.guid, subgroup.guid)! runner.groups.add_member(group.guid, subgroup.guid)!

View File

@@ -5,33 +5,33 @@ import freeflowuniverse.herolib.data.ourtime
// Job represents a task to be executed by an agent // Job represents a task to be executed by an agent
pub struct Job { pub struct Job {
pub mut: pub mut:
guid string // unique id for the job guid string // unique id for the job
agents []string // the pub key of the agent(s) which will execute the command, only 1 will execute agents []string // the pub key of the agent(s) which will execute the command, only 1 will execute
source string // pubkey from the agent who asked for the job source string // pubkey from the agent who asked for the job
circle string = 'default' // our digital life is organized in circles circle string = 'default' // our digital life is organized in circles
context string = 'default' // is the high level context in which actors will execute the work inside a circle context string = 'default' // is the high level context in which actors will execute the work inside a circle
actor string // e.g. vm_manager actor string // e.g. vm_manager
action string // e.g. start action string // e.g. start
params map[string]string // e.g. id:10 params map[string]string // e.g. id:10
timeout_schedule u16 = 60 // timeout before its picked up timeout_schedule u16 = 60 // timeout before its picked up
timeout u16 = 3600 // timeout in sec timeout u16 = 3600 // timeout in sec
log bool = true log bool = true
ignore_error bool // means if error will just exit and not raise, there will be no error reporting ignore_error bool // means if error will just exit and not raise, there will be no error reporting
ignore_error_codes []int // of we want to ignore certain error codes ignore_error_codes []int // of we want to ignore certain error codes
debug bool // if debug will get more context debug bool // if debug will get more context
retry int // default there is no debug retry int // default there is no debug
status JobStatus status JobStatus
dependencies []JobDependency // will not execute until other jobs are done dependencies []JobDependency // will not execute until other jobs are done
} }
// JobStatus represents the current state of a job // JobStatus represents the current state of a job
pub struct JobStatus { pub struct JobStatus {
pub mut: pub mut:
guid string // unique id for the job guid string // unique id for the job
created ourtime.Time // when we created the job created ourtime.OurTime // when we created the job
start ourtime.Time // when the job needs to start start ourtime.OurTime // when the job needs to start
end ourtime.Time // when the job ended, can be in error end ourtime.OurTime // when the job ended, can be in error
status Status // current status of the job status Status // current status of the job
} }
// JobDependency represents a dependency on another job // JobDependency represents a dependency on another job

View File

@@ -4,9 +4,7 @@ import freeflowuniverse.herolib.core.redisclient
import freeflowuniverse.herolib.data.ourtime import freeflowuniverse.herolib.data.ourtime
import json import json
const ( const jobs_key = 'herorunner:jobs' // Redis key for storing jobs
jobs_key = 'herorunner:jobs' // Redis key for storing jobs
)
// JobManager handles all job-related operations // JobManager handles all job-related operations
pub struct JobManager { pub struct JobManager {
@@ -17,13 +15,13 @@ mut:
// new creates a new Job instance // new creates a new Job instance
pub fn (mut m JobManager) new() Job { pub fn (mut m JobManager) new() Job {
return Job{ return Job{
guid: '' // Empty GUID to be filled by caller guid: '' // Empty GUID to be filled by caller
status: JobStatus{ status: JobStatus{
guid: '' guid: ''
created: ourtime.Time{} created: ourtime.now()
start: ourtime.Time{} start: ourtime.OurTime{}
end: ourtime.Time{} end: ourtime.OurTime{}
status: .created status: .created
} }
} }
} }
@@ -66,5 +64,5 @@ pub fn (mut m JobManager) delete(guid string) ! {
pub fn (mut m JobManager) update_status(guid string, status Status) ! { pub fn (mut m JobManager) update_status(guid string, status Status) ! {
mut job := m.get(guid)! mut job := m.get(guid)!
job.status.status = status job.status.status = status
m.update(job)! m.set(job)!
} }

View File

@@ -4,7 +4,7 @@ import freeflowuniverse.herolib.core.redisclient
import freeflowuniverse.herolib.data.ourtime import freeflowuniverse.herolib.data.ourtime
fn test_jobs() { fn test_jobs() {
mut runner:=new()! mut runner := new()!
// Create a new job using the manager // Create a new job using the manager
mut job := runner.jobs.new() mut job := runner.jobs.new()

View File

@@ -3,21 +3,21 @@ module model
// Service represents a service that can be provided by agents // Service represents a service that can be provided by agents
pub struct Service { pub struct Service {
pub mut: pub mut:
actor string // name of the actor providing the service actor string // name of the actor providing the service
actions []ServiceAction // available actions for this service actions []ServiceAction // available actions for this service
description string // optional description description string // optional description
status ServiceState // current state of the service status ServiceState // current state of the service
acl ?ACL // access control list for the service acl ?ACL // access control list for the service
} }
// ServiceAction represents an action that can be performed by a service // ServiceAction represents an action that can be performed by a service
pub struct ServiceAction { pub struct ServiceAction {
pub mut: pub mut:
action string // which action action string // which action
description string // optional description description string // optional description
params map[string]string // e.g. name:'name of the vm' ... params map[string]string // e.g. name:'name of the vm' ...
params_example map[string]string // e.g. name:'myvm' params_example map[string]string // e.g. name:'myvm'
acl ?ACL // if not used then everyone can use acl ?ACL // if not used then everyone can use
} }
// ACL represents an access control list // ACL represents an access control list

View File

@@ -3,9 +3,7 @@ module model
import freeflowuniverse.herolib.core.redisclient import freeflowuniverse.herolib.core.redisclient
import json import json
const ( const services_key = 'herorunner:services' // Redis key for storing services
services_key = 'herorunner:services' // Redis key for storing services
)
// ServiceManager handles all service-related operations // ServiceManager handles all service-related operations
pub struct ServiceManager { pub struct ServiceManager {
@@ -16,9 +14,9 @@ mut:
// new creates a new Service instance // new creates a new Service instance
pub fn (mut m ServiceManager) new() Service { pub fn (mut m ServiceManager) new() Service {
return Service{ return Service{
actor: '' // Empty actor name to be filled by caller actor: '' // Empty actor name to be filled by caller
actions: []ServiceAction{} actions: []ServiceAction{}
status: .ok status: .ok
} }
} }
@@ -60,7 +58,7 @@ pub fn (mut m ServiceManager) delete(actor string) ! {
pub fn (mut m ServiceManager) update_status(actor string, status ServiceState) ! { pub fn (mut m ServiceManager) update_status(actor string, status ServiceState) ! {
mut service := m.get(actor)! mut service := m.get(actor)!
service.status = status service.status = status
m.update(service)! m.set(service)!
} }
// get_by_action returns all services that provide a specific action // get_by_action returns all services that provide a specific action

View File

@@ -3,7 +3,7 @@ module model
import freeflowuniverse.herolib.core.redisclient import freeflowuniverse.herolib.core.redisclient
fn test_services() { fn test_services() {
mut runner:=new()! mut runner := new()!
// Create a new service using the manager // Create a new service using the manager
mut service := runner.services.new() mut service := runner.services.new()
@@ -13,26 +13,26 @@ fn test_services() {
// Create an ACL // Create an ACL
mut ace := ACE{ mut ace := ACE{
groups: ['admin-group'] groups: ['admin-group']
users: ['user-1-pubkey'] users: ['user-1-pubkey']
right: 'write' right: 'write'
} }
mut acl := ACL{ mut acl := ACL{
name: 'vm-acl' name: 'vm-acl'
ace: [ace] ace: [ace]
} }
// Create a service action // Create a service action
mut action := ServiceAction{ mut action := ServiceAction{
action: 'start' action: 'start'
description: 'Start a VM' description: 'Start a VM'
params: { params: {
'name': 'string' 'name': 'string'
} }
params_example: { params_example: {
'name': 'myvm' 'name': 'myvm'
} }
acl: acl acl: acl
} }
service.actions = [action] service.actions = [action]
@@ -59,13 +59,16 @@ fn test_services() {
assert services[0].actor == service.actor assert services[0].actor == service.actor
// Test access control // Test access control
has_access := runner.services.check_access(service.actor, 'start', 'user-1-pubkey', [])! has_access := runner.services.check_access(service.actor, 'start', 'user-1-pubkey',
[])!
assert has_access == true assert has_access == true
has_group_access := runner.services.check_access(service.actor, 'start', 'user-2-pubkey', ['admin-group'])! has_group_access := runner.services.check_access(service.actor, 'start', 'user-2-pubkey',
['admin-group'])!
assert has_group_access == true assert has_group_access == true
no_access := runner.services.check_access(service.actor, 'start', 'user-3-pubkey', [])! no_access := runner.services.check_access(service.actor, 'start', 'user-3-pubkey',
[])!
assert no_access == false assert no_access == false
// List all services // List all services