chore: add ansible deploy simple logic, upgrade admin auth logic and docs
ci-agent / build (push) Failing after 1m55s
ci-agent / build (push) Failing after 1m55s
This commit is contained in:
@@ -0,0 +1,172 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"gitea.d3m0k1d.ru/d3m0k1d/HellreigN/backend/internal/ansible"
|
||||
"gitea.d3m0k1d.ru/d3m0k1d/HellreigN/backend/internal/repository"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type AgentDeployGroup struct {
|
||||
*Handlers
|
||||
executor *ansible.Executor
|
||||
}
|
||||
|
||||
func NewAgentDeployGroup(h *Handlers) *AgentDeployGroup {
|
||||
workDir := os.Getenv("ANSIBLE_WORK_DIR")
|
||||
if workDir == "" {
|
||||
workDir = "/tmp/hellreign/ansible"
|
||||
}
|
||||
|
||||
grpcPort := os.Getenv("GRPC_PORT")
|
||||
if grpcPort == "" {
|
||||
grpcPort = "9001"
|
||||
}
|
||||
|
||||
backendURL := os.Getenv("BACKEND_URL")
|
||||
if backendURL == "" {
|
||||
backendURL = "http://localhost:8080"
|
||||
}
|
||||
|
||||
exec := ansible.NewExecutor(ansible.ExecutorConfig{
|
||||
WorkDir: workDir,
|
||||
GRPCServerHost: "0.0.0.0", // TODO: make configurable
|
||||
GRPCServerPort: grpcPort,
|
||||
BackendURL: backendURL,
|
||||
})
|
||||
|
||||
// Write playbooks on init
|
||||
if err := exec.WriteAllPlaybooks(); err != nil {
|
||||
// Log but don't fail - playbooks can be written later
|
||||
_ = err
|
||||
}
|
||||
|
||||
return &AgentDeployGroup{
|
||||
Handlers: h,
|
||||
executor: exec,
|
||||
}
|
||||
}
|
||||
|
||||
// DeployAgents deploys agents to multiple servers
|
||||
// @Summary Deploy agents to multiple servers via Ansible
|
||||
// @Description Deploy HellreigN agents to multiple servers using Ansible playbooks. Supports Docker and Binary deployment types.
|
||||
// @Tags agents
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param request body repository.DeployAgentsRequest true "Deployment configuration for servers"
|
||||
// @Success 200 {object} repository.DeployResponse "Deployment results with tokens for each server"
|
||||
// @Failure 400 {object} map[string]string "Invalid request"
|
||||
// @Failure 500 {object} map[string]string "Internal server error"
|
||||
// @Security Bearer
|
||||
// @Router /agents/deploy [post]
|
||||
func (adg *AgentDeployGroup) DeployAgents(c *gin.Context) {
|
||||
var req repository.DeployAgentsRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
// Create work directory
|
||||
workDir := adg.executor.WorkDir()
|
||||
if err := os.MkdirAll(workDir, 0755); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to create work directory"})
|
||||
return
|
||||
}
|
||||
|
||||
// Generate registration tokens for each server
|
||||
results := make([]repository.DeployResult, 0, len(req.Servers))
|
||||
timestamp := time.Now().UnixMilli()
|
||||
|
||||
ctx, cancel := context.WithTimeout(c.Request.Context(), 10*time.Minute)
|
||||
defer cancel()
|
||||
|
||||
for i, server := range req.Servers {
|
||||
// Create registration token
|
||||
token, err := adg.Repo.CreateRegistrationToken(server.AgentLabel)
|
||||
if err != nil {
|
||||
results = append(results, repository.DeployResult{
|
||||
IP: server.IP,
|
||||
AgentLabel: server.AgentLabel,
|
||||
Success: false,
|
||||
Error: fmt.Sprintf("failed to create token: %v", err),
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
// Set default port
|
||||
port := server.Port
|
||||
if port == 0 {
|
||||
port = 22
|
||||
}
|
||||
|
||||
// Generate inventory for this single server
|
||||
inventoryHosts := []ansible.InventoryHost{
|
||||
{
|
||||
Name: server.AgentLabel,
|
||||
IP: server.IP,
|
||||
Port: port,
|
||||
User: server.User,
|
||||
AuthMethod: string(server.AuthMethod),
|
||||
SSHKey: server.SSHKey,
|
||||
Password: server.Password,
|
||||
DeployType: string(server.DeployType),
|
||||
Token: token,
|
||||
},
|
||||
}
|
||||
|
||||
inventoryPath := filepath.Join(workDir, fmt.Sprintf("inventory_%d_%d", timestamp, i))
|
||||
if err := ansible.GenerateInventory(inventoryHosts, inventoryPath); err != nil {
|
||||
results = append(results, repository.DeployResult{
|
||||
IP: server.IP,
|
||||
AgentLabel: server.AgentLabel,
|
||||
Token: token,
|
||||
Success: false,
|
||||
Error: fmt.Sprintf("failed to generate inventory: %v", err),
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
// Run Ansible playbook for this server
|
||||
deployResults, err := adg.executor.Deploy(ctx, inventoryPath, string(server.DeployType))
|
||||
|
||||
// Clean up inventory file
|
||||
os.Remove(inventoryPath)
|
||||
|
||||
if err != nil {
|
||||
results = append(results, repository.DeployResult{
|
||||
IP: server.IP,
|
||||
AgentLabel: server.AgentLabel,
|
||||
Token: token,
|
||||
Success: false,
|
||||
Error: fmt.Sprintf("deployment failed: %v", err),
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
success := true
|
||||
errMsg := ""
|
||||
if len(deployResults) > 0 && !deployResults[0].Success {
|
||||
success = false
|
||||
errMsg = deployResults[0].Stderr
|
||||
}
|
||||
|
||||
results = append(results, repository.DeployResult{
|
||||
IP: server.IP,
|
||||
AgentLabel: server.AgentLabel,
|
||||
Token: token,
|
||||
Success: success,
|
||||
Error: errMsg,
|
||||
})
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, repository.DeployResponse{
|
||||
Message: "Deployment completed",
|
||||
Results: results,
|
||||
})
|
||||
}
|
||||
@@ -23,6 +23,7 @@ type AuthGroup struct {
|
||||
// @Success 200 {object} repository.LoginResponse
|
||||
// @Failure 400 {object} map[string]string
|
||||
// @Failure 401 {object} map[string]string
|
||||
// @Failure 403 {object} map[string]string
|
||||
// @Router /auth/login [post]
|
||||
func (ag *AuthGroup) Login(c *gin.Context) {
|
||||
var req repository.LoginRequest
|
||||
@@ -37,6 +38,10 @@ func (ag *AuthGroup) Login(c *gin.Context) {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid credentials"})
|
||||
return
|
||||
}
|
||||
if errors.Is(err, repository.ErrAccountInactive) {
|
||||
c.JSON(http.StatusForbidden, gin.H{"error": "account is not activated by admin"})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to authenticate"})
|
||||
return
|
||||
}
|
||||
@@ -168,6 +173,223 @@ func (ag *AuthGroup) DeleteMyToken(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{"message": "account deleted"})
|
||||
}
|
||||
|
||||
// ActivateUser activates a user by login.
|
||||
// @Summary Activate user
|
||||
// @Description Activates a user account by login (admin only)
|
||||
// @Tags auth
|
||||
// @Param login path string true "Login of the user to activate"
|
||||
// @Success 200 {object} map[string]string
|
||||
// @Failure 400 {object} map[string]string
|
||||
// @Failure 404 {object} map[string]string
|
||||
// @Failure 500 {object} map[string]string
|
||||
// @Router /auth/users/:login/activate [post]
|
||||
func (ag *AuthGroup) ActivateUser(c *gin.Context) {
|
||||
login := c.Param("login")
|
||||
if login == "" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "login required"})
|
||||
return
|
||||
}
|
||||
|
||||
if err := ag.Repo.ActivateUserByLogin(login); err != nil {
|
||||
if errors.Is(err, repository.ErrNotFound) {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "user not found"})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to activate user"})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "user activated"})
|
||||
}
|
||||
|
||||
// DeactivateUser deactivates a user by login.
|
||||
// @Summary Deactivate user
|
||||
// @Description Deactivates a user account by login (admin only)
|
||||
// @Tags auth
|
||||
// @Param login path string true "Login of the user to deactivate"
|
||||
// @Success 200 {object} map[string]string
|
||||
// @Failure 400 {object} map[string]string
|
||||
// @Failure 404 {object} map[string]string
|
||||
// @Failure 500 {object} map[string]string
|
||||
// @Router /auth/users/:login/deactivate [post]
|
||||
func (ag *AuthGroup) DeactivateUser(c *gin.Context) {
|
||||
login := c.Param("login")
|
||||
if login == "" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "login required"})
|
||||
return
|
||||
}
|
||||
|
||||
if err := ag.Repo.DeactivateUserByLogin(login); err != nil {
|
||||
if errors.Is(err, repository.ErrNotFound) {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "user not found"})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to deactivate user"})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "user deactivated"})
|
||||
}
|
||||
|
||||
// ListInactiveUsers returns all users that are not activated.
|
||||
// @Summary List inactive users
|
||||
// @Description Returns list of all users waiting for activation
|
||||
// @Tags auth
|
||||
// @Produce json
|
||||
// @Success 200 {array} repository.Tokens
|
||||
// @Failure 500 {object} map[string]string
|
||||
// @Router /auth/users/inactive [get]
|
||||
func (ag *AuthGroup) ListInactiveUsers(c *gin.Context) {
|
||||
tokens, err := ag.Repo.ListInactiveTokens()
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to list inactive users"})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, tokens)
|
||||
}
|
||||
|
||||
// GetUser returns a user by login.
|
||||
// @Summary Get user by login
|
||||
// @Description Returns a user by their login (admin only)
|
||||
// @Tags auth
|
||||
// @Produce json
|
||||
// @Param login path string true "Login of the user"
|
||||
// @Success 200 {object} repository.Tokens
|
||||
// @Failure 400 {object} map[string]string
|
||||
// @Failure 404 {object} map[string]string
|
||||
// @Failure 500 {object} map[string]string
|
||||
// @Router /auth/users/:login [get]
|
||||
func (ag *AuthGroup) GetUser(c *gin.Context) {
|
||||
login := c.Param("login")
|
||||
if login == "" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "login required"})
|
||||
return
|
||||
}
|
||||
|
||||
user, err := ag.Repo.GetTokenByLogin(login)
|
||||
if err != nil {
|
||||
if errors.Is(err, repository.ErrNotFound) {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "user not found"})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to get user"})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, user)
|
||||
}
|
||||
|
||||
// UpdateUser updates user's name and last name.
|
||||
// @Summary Update user
|
||||
// @Description Updates a user's name and last name (admin only)
|
||||
// @Tags auth
|
||||
// @Accept json
|
||||
// @Param login path string true "Login of the user"
|
||||
// @Param request body repository.TokenUpdate true "User data to update"
|
||||
// @Success 200 {object} map[string]string
|
||||
// @Failure 400 {object} map[string]string
|
||||
// @Failure 404 {object} map[string]string
|
||||
// @Failure 500 {object} map[string]string
|
||||
// @Router /auth/users/:login [put]
|
||||
func (ag *AuthGroup) UpdateUser(c *gin.Context) {
|
||||
login := c.Param("login")
|
||||
if login == "" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "login required"})
|
||||
return
|
||||
}
|
||||
|
||||
var update repository.TokenUpdate
|
||||
if err := c.ShouldBindJSON(&update); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request body"})
|
||||
return
|
||||
}
|
||||
|
||||
if err := ag.Repo.UpdateToken(login, update); err != nil {
|
||||
if errors.Is(err, repository.ErrNotFound) {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "user not found"})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to update user"})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "user updated"})
|
||||
}
|
||||
|
||||
// UpdateUserPermissions updates user's permissions and activation status.
|
||||
// @Summary Update user permissions
|
||||
// @Description Updates a user's permissions and activation status (admin only)
|
||||
// @Tags auth
|
||||
// @Accept json
|
||||
// @Param login path string true "Login of the user"
|
||||
// @Param request body repository.TokenUpdatePermissions true "Permissions to update"
|
||||
// @Success 200 {object} map[string]string
|
||||
// @Failure 400 {object} map[string]string
|
||||
// @Failure 404 {object} map[string]string
|
||||
// @Failure 500 {object} map[string]string
|
||||
// @Router /auth/users/:login/permissions [put]
|
||||
func (ag *AuthGroup) UpdateUserPermissions(c *gin.Context) {
|
||||
login := c.Param("login")
|
||||
if login == "" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "login required"})
|
||||
return
|
||||
}
|
||||
|
||||
var update repository.TokenUpdatePermissions
|
||||
if err := c.ShouldBindJSON(&update); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request body"})
|
||||
return
|
||||
}
|
||||
|
||||
if err := ag.Repo.UpdatePermissions(login, update); err != nil {
|
||||
if errors.Is(err, repository.ErrNotFound) {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "user not found"})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to update permissions"})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "permissions updated"})
|
||||
}
|
||||
|
||||
// ResetUserPassword resets a user's password.
|
||||
// @Summary Reset user password
|
||||
// @Description Resets a user's password to a new value (admin only)
|
||||
// @Tags auth
|
||||
// @Accept json
|
||||
// @Param login path string true "Login of the user"
|
||||
// @Param request body repository.TokenPasswordReset true "New password"
|
||||
// @Success 200 {object} map[string]string
|
||||
// @Failure 400 {object} map[string]string
|
||||
// @Failure 404 {object} map[string]string
|
||||
// @Failure 500 {object} map[string]string
|
||||
// @Router /auth/users/:login/password [put]
|
||||
func (ag *AuthGroup) ResetUserPassword(c *gin.Context) {
|
||||
login := c.Param("login")
|
||||
if login == "" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "login required"})
|
||||
return
|
||||
}
|
||||
|
||||
var req repository.TokenPasswordReset
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request body"})
|
||||
return
|
||||
}
|
||||
|
||||
if err := ag.Repo.UpdatePassword(login, req.NewPassword); err != nil {
|
||||
if errors.Is(err, repository.ErrNotFound) {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "user not found"})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to reset password"})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "password reset"})
|
||||
}
|
||||
|
||||
// getTokenFromHeader extracts the Bearer token from the Authorization header.
|
||||
func getTokenFromHeader(c *gin.Context) string {
|
||||
auth := c.GetHeader("Authorization")
|
||||
|
||||
@@ -0,0 +1,202 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"gitea.d3m0k1d.ru/d3m0k1d/HellreigN/backend/internal/storage"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// GetMockLogs returns 100 mock log entries for frontend development
|
||||
// @Summary Get mock logs
|
||||
// @Description Returns 100 mock log entries for frontend development (no ClickHouse required)
|
||||
// @Tags logs
|
||||
// @Produce json
|
||||
// @Param level query string false "Filter by level"
|
||||
// @Param service query string false "Filter by service"
|
||||
// @Param agent query string false "Filter by agent"
|
||||
// @Param limit query int false "Limit results" default(100)
|
||||
// @Param offset query int false "Offset results" default(0)
|
||||
// @Success 200 {array} storage.LogEntry
|
||||
// @Router /logs/mock [get]
|
||||
func (lh *LogHandlers) GetMockLogs(c *gin.Context) {
|
||||
levelFilter := c.Query("level")
|
||||
serviceFilter := c.Query("service")
|
||||
agentFilter := c.Query("agent")
|
||||
|
||||
limit := 100
|
||||
offset := 0
|
||||
|
||||
if l := c.Query("limit"); l != "" {
|
||||
if parsed, err := strconv.Atoi(l); err == nil && parsed > 0 {
|
||||
limit = parsed
|
||||
}
|
||||
}
|
||||
if o := c.Query("offset"); o != "" {
|
||||
if parsed, err := strconv.Atoi(o); err == nil && parsed >= 0 {
|
||||
offset = parsed
|
||||
}
|
||||
}
|
||||
|
||||
logs := generateMockLogs(100)
|
||||
|
||||
// Apply filters
|
||||
var filtered []storage.LogEntry
|
||||
for _, log := range logs {
|
||||
if levelFilter != "" && log.Level != levelFilter {
|
||||
continue
|
||||
}
|
||||
if serviceFilter != "" && log.Service != serviceFilter {
|
||||
continue
|
||||
}
|
||||
if agentFilter != "" && log.Agent != agentFilter {
|
||||
continue
|
||||
}
|
||||
filtered = append(filtered, log)
|
||||
}
|
||||
|
||||
// Apply pagination
|
||||
end := offset + limit
|
||||
if end > len(filtered) {
|
||||
end = len(filtered)
|
||||
}
|
||||
if offset > len(filtered) {
|
||||
filtered = []storage.LogEntry{}
|
||||
} else {
|
||||
filtered = filtered[offset:end]
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, filtered)
|
||||
}
|
||||
|
||||
func generateMockLogs(count int) []storage.LogEntry {
|
||||
services := []string{
|
||||
"auth-service",
|
||||
"user-service",
|
||||
"agent-service",
|
||||
"gateway",
|
||||
"scheduler",
|
||||
"notification-service",
|
||||
"metrics-collector",
|
||||
"deployment-service",
|
||||
}
|
||||
|
||||
agents := []string{
|
||||
"agent-prod-01",
|
||||
"agent-prod-02",
|
||||
"agent-staging-01",
|
||||
"agent-dev-01",
|
||||
"agent-dev-02",
|
||||
"agent-monitoring-01",
|
||||
"agent-backup-01",
|
||||
"agent-ci-runner-01",
|
||||
}
|
||||
|
||||
levels := []string{"INFO", "WARNING", "ERROR", "FATAL", "DEBUG"}
|
||||
levelWeights := []int{50, 20, 15, 5, 10} // weighted distribution
|
||||
|
||||
messages := map[string][]string{
|
||||
"INFO": {
|
||||
"Service started successfully",
|
||||
"Health check passed",
|
||||
"Configuration loaded",
|
||||
"Connection established to database",
|
||||
"Cache refreshed successfully",
|
||||
"Request processed in 45ms",
|
||||
"User login successful",
|
||||
"Agent registered successfully",
|
||||
"Deployment completed for 3 servers",
|
||||
"Metrics exported to storage",
|
||||
"Backup completed successfully",
|
||||
"SSL certificate valid for 89 days",
|
||||
"Task scheduled: cleanup-temp-files",
|
||||
"Webhook delivered successfully",
|
||||
"Session created for user admin",
|
||||
},
|
||||
"WARNING": {
|
||||
"High memory usage detected: 85%",
|
||||
"Slow query detected: 2.3s",
|
||||
"Rate limit approaching for client 192.168.1.50",
|
||||
"Certificate expires in 7 days",
|
||||
"Retry attempt 2/3 for request",
|
||||
"Disk usage above threshold: 78%",
|
||||
"Connection pool nearly exhausted: 45/50",
|
||||
"Deprecated API endpoint called: /api/v1/legacy",
|
||||
"Response time exceeded SLA: 1.2s > 1s",
|
||||
"Agent heartbeat delayed by 5s",
|
||||
},
|
||||
"ERROR": {
|
||||
"Failed to connect to database: timeout after 30s",
|
||||
"Authentication failed for user test_user",
|
||||
"Agent deployment failed: SSH connection refused",
|
||||
"Failed to send notification: SMTP server unavailable",
|
||||
"Request failed with status 500",
|
||||
"File not found: /etc/hellreign/config.yml",
|
||||
"Invalid token provided",
|
||||
"Permission denied for user viewer",
|
||||
"Failed to parse configuration: invalid YAML",
|
||||
"Agent unreachable: connection timeout",
|
||||
},
|
||||
"FATAL": {
|
||||
"Out of memory: cannot allocate 512MB",
|
||||
"Database connection lost, all retries exhausted",
|
||||
"Critical: SSL certificate expired",
|
||||
"Unrecoverable error: data corruption detected",
|
||||
"Service crashed: segmentation fault",
|
||||
},
|
||||
"DEBUG": {
|
||||
"Processing request payload: 2.3KB",
|
||||
"Cache hit ratio: 78%",
|
||||
"Executing query: SELECT * FROM logs WHERE...",
|
||||
"HTTP request headers: {Content-Type: application/json}",
|
||||
"Agent status check: 8 agents online",
|
||||
"Memory allocation: 256MB used of 1024MB",
|
||||
"Thread pool size: 12 active, 4 idle",
|
||||
"GC pause: 15ms",
|
||||
},
|
||||
}
|
||||
|
||||
r := rand.New(rand.NewSource(42)) // fixed seed for reproducibility
|
||||
|
||||
var logs []storage.LogEntry
|
||||
now := time.Now()
|
||||
|
||||
for i := 0; i < count; i++ {
|
||||
level := weightedRandom(r, levels, levelWeights)
|
||||
service := services[r.Intn(len(services))]
|
||||
agent := agents[r.Intn(len(agents))]
|
||||
msgs := messages[level]
|
||||
message := msgs[r.Intn(len(msgs))]
|
||||
|
||||
// Spread logs over the last 24 hours
|
||||
timestamp := now.Add(-time.Duration(count-i) * time.Minute * 15)
|
||||
|
||||
logs = append(logs, storage.LogEntry{
|
||||
Timestamp: timestamp,
|
||||
Level: level,
|
||||
Service: service,
|
||||
Agent: agent,
|
||||
Message: message,
|
||||
})
|
||||
}
|
||||
|
||||
return logs
|
||||
}
|
||||
|
||||
func weightedRandom(r *rand.Rand, items []string, weights []int) string {
|
||||
total := 0
|
||||
for _, w := range weights {
|
||||
total += w
|
||||
}
|
||||
n := r.Intn(total)
|
||||
for i, w := range weights {
|
||||
n -= w
|
||||
if n < 0 {
|
||||
return items[i]
|
||||
}
|
||||
}
|
||||
return items[len(items)-1]
|
||||
}
|
||||
Reference in New Issue
Block a user