add: main function
This commit is contained in:
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
mcgod
|
||||
@@ -2,25 +2,70 @@ package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"log/slog"
|
||||
"os"
|
||||
"os/signal"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/gogo/protobuf/proto"
|
||||
"github.com/ollama/ollama/api"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
"tipsy.codes/charles/mc-god/v2/internal/pkg/logs"
|
||||
"tipsy.codes/charles/mc-god/v2/internal/pkg/rcon"
|
||||
)
|
||||
|
||||
type chatContext struct {
|
||||
chatRequest *api.ChatRequest
|
||||
totalSize int
|
||||
maxSize int
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
func (c *chatContext) AddLog(msg string) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
c.chatRequest.Messages = append(c.chatRequest.Messages, api.Message{
|
||||
Role: "logs",
|
||||
Content: msg,
|
||||
})
|
||||
c.totalSize += len(msg)
|
||||
c.truncate()
|
||||
}
|
||||
|
||||
func (c *chatContext) AddSelf(msg api.Message) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
c.chatRequest.Messages = append(c.chatRequest.Messages, msg)
|
||||
c.totalSize += len(msg.Content)
|
||||
c.truncate()
|
||||
}
|
||||
|
||||
func (c *chatContext) truncate() {
|
||||
for c.maxSize != 0 && c.totalSize > c.maxSize && len(c.chatRequest.Messages) > 0 {
|
||||
t := c.chatRequest.Messages[0]
|
||||
c.chatRequest.Messages = c.chatRequest.Messages[1:]
|
||||
c.totalSize -= len(t.Content)
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
// Create a context that will be cancelled on interrupt signals
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
_ = ctx
|
||||
|
||||
// Set up signal handling for graceful shutdown
|
||||
sigChan := make(chan os.Signal, 1)
|
||||
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
|
||||
go func() {
|
||||
<-sigChan
|
||||
log.Println("Received interrupt signal, shutting down...")
|
||||
slog.Info("received interrupt signal, shutting down...")
|
||||
cancel()
|
||||
}()
|
||||
|
||||
@@ -28,18 +73,142 @@ func main() {
|
||||
log.Println("Connecting to Minecraft server via RCON...")
|
||||
client, err := rcon.NewFromEnv()
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to create RCON client: %v", err)
|
||||
slog.Error("failed to create RCON client", "error", err)
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
if err := client.Close(); err != nil {
|
||||
log.Printf("Error closing RCON connection: %v", err)
|
||||
slog.Warn("error closing RCON connection", "error", err)
|
||||
}
|
||||
}()
|
||||
|
||||
// Perform a health check
|
||||
log.Println("Performing healthcheck...")
|
||||
if err := client.HealthCheck(); err != nil {
|
||||
log.Fatalf("Health check failed: %v", err)
|
||||
slog.Error("Health check failed", "error", err)
|
||||
return
|
||||
}
|
||||
log.Println("Connected successfully!")
|
||||
|
||||
// Create Kubernetes client
|
||||
kClient, err := createKubernetesClient()
|
||||
if err != nil {
|
||||
slog.Error("failed to create kubernetes client", "error", err)
|
||||
return
|
||||
}
|
||||
slog.Info("got kubernetes config")
|
||||
|
||||
tailer, done := logs.LoggerFromEnv().Start(ctx, kClient)
|
||||
defer func() {
|
||||
if err := done(); err != nil {
|
||||
slog.Error("problem with tailer", "error", err)
|
||||
}
|
||||
}()
|
||||
slog.Info("logger started")
|
||||
|
||||
ollamaClient, err := api.ClientFromEnvironment()
|
||||
if err != nil {
|
||||
slog.Error("error getting ollama client", "error", err)
|
||||
}
|
||||
|
||||
rClient, err := rcon.NewFromEnv()
|
||||
if err != nil {
|
||||
slog.Error("failed to get rcon client", "error", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Start goroutines to do the things
|
||||
chatRequest := &api.ChatRequest{
|
||||
Model: "qwen3-coder",
|
||||
Stream: proto.Bool(false),
|
||||
KeepAlive: &api.Duration{Duration: time.Hour},
|
||||
Tools: api.Tools{},
|
||||
Think: &api.ThinkValue{Value: false},
|
||||
Shift: proto.Bool(true),
|
||||
Messages: []api.Message{
|
||||
api.Message{
|
||||
Role: "system",
|
||||
Content: `
|
||||
You are Minecraft server admin with a god complex.
|
||||
React to any messages from the user by saying something god-like.
|
||||
When you join the server, announce yourself.
|
||||
|
||||
Responses should be short; one sentence.
|
||||
You may choose to return an empty response if there is nothing interesting to say
|
||||
(i.e., no new logs since your last message).
|
||||
|
||||
When a user replies to you, you will see this in the logs:
|
||||
|
||||
2026/02/14 10:48:40 INFO mc log msg="[18:45:10] [Server thread/INFO]: <OrangeYouSad> you are full of it."
|
||||
|
||||
The user here is OrangeYouSad, who said "you are full of it."
|
||||
`,
|
||||
},
|
||||
},
|
||||
}
|
||||
chat := &chatContext{
|
||||
chatRequest: chatRequest,
|
||||
maxSize: 10000000,
|
||||
}
|
||||
|
||||
doneWg := sync.WaitGroup{}
|
||||
doneWg.Go(handleOllama(ctx, ollamaClient, chat, rClient))
|
||||
|
||||
for line := range tailer.NextLine() {
|
||||
slog.Info("mc log", "msg", line)
|
||||
chat.AddLog(line)
|
||||
}
|
||||
|
||||
doneWg.Wait()
|
||||
}
|
||||
|
||||
func handleOllama(ctx context.Context, client *api.Client, chat *chatContext, rClient *rcon.Client) func() {
|
||||
slog.Info("got chat request", "object", fmt.Sprintf("%+v", chat.chatRequest))
|
||||
return func() {
|
||||
var chatResponse api.ChatResponse
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case <-time.Tick(time.Second * 10):
|
||||
// do nothing
|
||||
}
|
||||
chat.mu.Lock()
|
||||
// slog.Info("sending chat request", "object", fmt.Sprintf("%#v", chat.chatRequest))
|
||||
err := client.Chat(ctx, chat.chatRequest, func(cr api.ChatResponse) error {
|
||||
chatResponse = cr
|
||||
return nil
|
||||
})
|
||||
chat.mu.Unlock()
|
||||
if err != nil {
|
||||
slog.Error("error calling ollama", "error", err)
|
||||
}
|
||||
chat.AddSelf(chatResponse.Message)
|
||||
if err := rClient.Say(chatResponse.Message.Content); err != nil {
|
||||
slog.Error("error talking", "error", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func createKubernetesClient() (*kubernetes.Clientset, error) {
|
||||
// Try to load in-cluster config first
|
||||
config, err := rest.InClusterConfig()
|
||||
if err != nil {
|
||||
// If in-cluster config fails, try kubeconfig
|
||||
loadingRules := clientcmd.NewDefaultClientConfigLoadingRules()
|
||||
configOverrides := &clientcmd.ConfigOverrides{}
|
||||
kubeConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, configOverrides)
|
||||
config, err = kubeConfig.ClientConfig()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create kubernetes client: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
client, err := kubernetes.NewForConfig(config)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create kubernetes client: %w", err)
|
||||
}
|
||||
|
||||
return client, nil
|
||||
}
|
||||
|
||||
17
go.mod
17
go.mod
@@ -3,15 +3,19 @@ module tipsy.codes/charles/mc-god/v2
|
||||
go 1.25.6
|
||||
|
||||
require (
|
||||
github.com/gogo/protobuf v1.3.2
|
||||
github.com/google/go-cmp v0.7.0
|
||||
github.com/gorcon/rcon v1.4.0
|
||||
github.com/stretchr/testify v1.9.0
|
||||
github.com/ollama/ollama v0.16.1
|
||||
github.com/stretchr/testify v1.11.1
|
||||
k8s.io/api v0.31.0
|
||||
k8s.io/apimachinery v0.31.0
|
||||
k8s.io/client-go v0.31.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/bahlo/generic-list-go v0.2.0 // indirect
|
||||
github.com/buger/jsonparser v1.1.1 // indirect
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/emicklei/go-restful/v3 v3.11.0 // indirect
|
||||
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
|
||||
@@ -19,7 +23,6 @@ require (
|
||||
github.com/go-openapi/jsonpointer v0.19.6 // indirect
|
||||
github.com/go-openapi/jsonreference v0.20.2 // indirect
|
||||
github.com/go-openapi/swag v0.22.4 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang/protobuf v1.5.4 // indirect
|
||||
github.com/google/gnostic-models v0.6.8 // indirect
|
||||
github.com/google/gofuzz v1.2.0 // indirect
|
||||
@@ -33,12 +36,14 @@ require (
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect
|
||||
github.com/x448/float16 v0.8.4 // indirect
|
||||
golang.org/x/net v0.26.0 // indirect
|
||||
golang.org/x/crypto v0.43.0 // indirect
|
||||
golang.org/x/net v0.46.0 // indirect
|
||||
golang.org/x/oauth2 v0.21.0 // indirect
|
||||
golang.org/x/sys v0.21.0 // indirect
|
||||
golang.org/x/term v0.21.0 // indirect
|
||||
golang.org/x/text v0.16.0 // indirect
|
||||
golang.org/x/sys v0.37.0 // indirect
|
||||
golang.org/x/term v0.36.0 // indirect
|
||||
golang.org/x/text v0.30.0 // indirect
|
||||
golang.org/x/time v0.3.0 // indirect
|
||||
google.golang.org/protobuf v1.34.2 // indirect
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
|
||||
34
go.sum
34
go.sum
@@ -1,3 +1,7 @@
|
||||
github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk=
|
||||
github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg=
|
||||
github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs=
|
||||
github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
@@ -60,6 +64,8 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||
github.com/ollama/ollama v0.16.1 h1:DIxnLdS0om3hb7HheJqj6+ZnPCCMWmy/vyUxiQgRYoI=
|
||||
github.com/ollama/ollama v0.16.1/go.mod h1:FEk95NbAJJZk+t7cLh+bPGTul72j1O3PLLlYNV3FVZ0=
|
||||
github.com/onsi/ginkgo/v2 v2.19.0 h1:9Cnnf7UHo57Hy3k6/m5k3dRfGTMXGvxhHFvkDTCTpvA=
|
||||
github.com/onsi/ginkgo/v2 v2.19.0/go.mod h1:rlwLi9PilAFJ8jCg9UE1QP6VBpd6/xj3SRC0d6TU0To=
|
||||
github.com/onsi/gomega v1.19.0 h1:4ieX6qQjPP/BfC3mpsAtIGGlxTWPeA3Inl/7DtXw1tw=
|
||||
@@ -78,8 +84,10 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc=
|
||||
github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw=
|
||||
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
|
||||
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
@@ -87,14 +95,16 @@ github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9dec
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04=
|
||||
golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ=
|
||||
golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
|
||||
golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4=
|
||||
golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210=
|
||||
golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs=
|
||||
golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@@ -103,22 +113,22 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
|
||||
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.21.0 h1:WVXCp+/EBEHOj53Rvu+7KiT/iElMrO8ACK16SMZ3jaA=
|
||||
golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0=
|
||||
golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ=
|
||||
golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/term v0.36.0 h1:zMPR+aF8gfksFprF/Nc/rd1wRS1EI6nDBGyWAvDzx2Q=
|
||||
golang.org/x/term v0.36.0/go.mod h1:Qu394IJq6V6dCBRgwqshf3mPF85AqzYEzofzRdZkWss=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
|
||||
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
|
||||
golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k=
|
||||
golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM=
|
||||
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
|
||||
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg=
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||
golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ=
|
||||
golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
|
||||
@@ -4,13 +4,12 @@ import (
|
||||
"bufio"
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
)
|
||||
|
||||
// Logger represents a Kubernetes log tailer
|
||||
@@ -30,6 +29,20 @@ type Logger struct {
|
||||
Timeout time.Duration
|
||||
}
|
||||
|
||||
func LoggerFromEnv() *Logger {
|
||||
deployment := os.Getenv("RCON_DEPLOYMENT")
|
||||
namespace := os.Getenv("RCON_NAMESPACE")
|
||||
pod := os.Getenv("RCON_POD")
|
||||
container := os.Getenv("RCON_CONTAINER")
|
||||
|
||||
return &Logger{
|
||||
Deployment: deployment,
|
||||
Namespace: namespace,
|
||||
Pod: pod,
|
||||
Container: container,
|
||||
}
|
||||
}
|
||||
|
||||
// Tailer represents a running log tailer
|
||||
type Tailer struct {
|
||||
lines chan string
|
||||
@@ -47,23 +60,17 @@ func (t *Tailer) Stop() {
|
||||
}
|
||||
|
||||
// Start starts the log tailer
|
||||
func (l *Logger) Start() (*Tailer, func() error) {
|
||||
func (l *Logger) Start(ctx context.Context, client *kubernetes.Clientset) (*Tailer, func() error) {
|
||||
tailer := &Tailer{
|
||||
lines: make(chan string),
|
||||
done: make(chan struct{}),
|
||||
}
|
||||
|
||||
// Create Kubernetes client
|
||||
client, err := l.createKubernetesClient()
|
||||
if err != nil {
|
||||
close(tailer.lines)
|
||||
return tailer, func() error { return err }
|
||||
}
|
||||
|
||||
// Get pod name if not specified
|
||||
var err error
|
||||
podName := l.Pod
|
||||
if podName == "" {
|
||||
podName, err = l.getPodName(client)
|
||||
podName, err = l.getPodName(ctx, client)
|
||||
if err != nil {
|
||||
close(tailer.lines)
|
||||
return tailer, func() error { return err }
|
||||
@@ -73,7 +80,7 @@ func (l *Logger) Start() (*Tailer, func() error) {
|
||||
// Get container name if not specified
|
||||
containerName := l.Container
|
||||
if containerName == "" {
|
||||
containerName, err = l.getContainerName(client, podName)
|
||||
containerName, err = l.getContainerName(ctx, client, podName)
|
||||
if err != nil {
|
||||
close(tailer.lines)
|
||||
return tailer, func() error { return err }
|
||||
@@ -81,7 +88,7 @@ func (l *Logger) Start() (*Tailer, func() error) {
|
||||
}
|
||||
|
||||
// Start tailing logs
|
||||
go l.tailLogs(client, podName, containerName, tailer)
|
||||
go l.tailLogs(ctx, client, podName, containerName, tailer)
|
||||
|
||||
return tailer, func() error {
|
||||
tailer.Stop()
|
||||
@@ -89,31 +96,9 @@ func (l *Logger) Start() (*Tailer, func() error) {
|
||||
}
|
||||
}
|
||||
|
||||
func (l *Logger) createKubernetesClient() (*kubernetes.Clientset, error) {
|
||||
// Try to load in-cluster config first
|
||||
config, err := rest.InClusterConfig()
|
||||
if err != nil {
|
||||
// If in-cluster config fails, try kubeconfig
|
||||
loadingRules := clientcmd.NewDefaultClientConfigLoadingRules()
|
||||
configOverrides := &clientcmd.ConfigOverrides{}
|
||||
kubeConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, configOverrides)
|
||||
config, err = kubeConfig.ClientConfig()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create kubernetes client: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
client, err := kubernetes.NewForConfig(config)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create kubernetes client: %w", err)
|
||||
}
|
||||
|
||||
return client, nil
|
||||
}
|
||||
|
||||
func (l *Logger) getPodName(client *kubernetes.Clientset) (string, error) {
|
||||
func (l *Logger) getPodName(ctx context.Context, client *kubernetes.Clientset) (string, error) {
|
||||
// List pods with the deployment label
|
||||
podList, err := client.CoreV1().Pods(l.Namespace).List(context.TODO(), metav1.ListOptions{
|
||||
podList, err := client.CoreV1().Pods(l.Namespace).List(ctx, metav1.ListOptions{
|
||||
LabelSelector: fmt.Sprintf("app=%s", l.Deployment),
|
||||
})
|
||||
if err != nil {
|
||||
@@ -135,8 +120,8 @@ func (l *Logger) getPodName(client *kubernetes.Clientset) (string, error) {
|
||||
return podList.Items[0].Name, nil
|
||||
}
|
||||
|
||||
func (l *Logger) getContainerName(client *kubernetes.Clientset, podName string) (string, error) {
|
||||
pod, err := client.CoreV1().Pods(l.Namespace).Get(context.TODO(), podName, metav1.GetOptions{})
|
||||
func (l *Logger) getContainerName(ctx context.Context, client *kubernetes.Clientset, podName string) (string, error) {
|
||||
pod, err := client.CoreV1().Pods(l.Namespace).Get(ctx, podName, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to get pod %s: %w", podName, err)
|
||||
}
|
||||
@@ -149,7 +134,7 @@ func (l *Logger) getContainerName(client *kubernetes.Clientset, podName string)
|
||||
return pod.Spec.Containers[0].Name, nil
|
||||
}
|
||||
|
||||
func (l *Logger) tailLogs(client *kubernetes.Clientset, podName, containerName string, tailer *Tailer) {
|
||||
func (l *Logger) tailLogs(ctx context.Context, client *kubernetes.Clientset, podName, containerName string, tailer *Tailer) {
|
||||
defer close(tailer.lines)
|
||||
|
||||
// Prepare log request
|
||||
@@ -163,7 +148,6 @@ func (l *Logger) tailLogs(client *kubernetes.Clientset, podName, containerName s
|
||||
}
|
||||
|
||||
// Create context with timeout if specified
|
||||
ctx := context.Background()
|
||||
if l.Timeout > 0 {
|
||||
var cancel context.CancelFunc
|
||||
ctx, cancel = context.WithTimeout(ctx, l.Timeout)
|
||||
|
||||
@@ -69,6 +69,11 @@ func (c *Client) SetDifficulty(difficulty string) error {
|
||||
return fmt.Errorf("not implemented")
|
||||
}
|
||||
|
||||
func (c *Client) Say(msg string) error {
|
||||
_, err := c.Execute("/say " + msg)
|
||||
return err
|
||||
}
|
||||
|
||||
// GetServerInfo returns basic server information
|
||||
func (c *Client) GetServerInfo() (string, error) {
|
||||
return c.Execute("version")
|
||||
|
||||
Reference in New Issue
Block a user