-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathdocker.go
247 lines (218 loc) · 5.83 KB
/
docker.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
package ichiran
import (
"bufio"
"bytes"
"context"
"encoding/binary"
"encoding/json"
"fmt"
"io"
"sync"
"time"
"github.com/gookit/color"
"github.com/k0kubun/pp"
"github.com/rs/zerolog"
"github.com/tassa-yoniso-manasi-karoto/dockerutil"
)
/*
Libraries:
https://pkg.go.dev/github.com/docker/compose/v2@v2.32.1/pkg/compose#NewComposeService
https://pkg.go.dev/github.com/docker/compose/v2@v2.32.1/pkg/api#Service
https://pkg.go.dev/github.com/docker/cli@v27.4.1+incompatible/cli/command
https://pkg.go.dev/github.com/docker/cli@v27.4.1+incompatible/cli/flags
*/
const (
remote = "https://github.com/tshatrov/ichiran.git"
projectName = "ichiran"
containerName = "ichiran-main-1"
)
var (
instance *docker
once sync.Once
mu sync.Mutex
Ctx = context.Background()
QueryTimeout = 45 * time.Minute
DockerLogLevel = zerolog.TraceLevel
)
type docker struct {
docker *dockerutil.DockerManager
logger *dockerutil.ContainerLogConsumer
}
// newDocker creates or returns an existing docker instance
func newDocker() (*docker, error) {
mu.Lock()
defer mu.Unlock()
var initErr error
once.Do(func() {
logConfig := dockerutil.LogConfig{
Prefix: projectName,
ShowService: true,
ShowType: true,
LogLevel: DockerLogLevel,
InitMessage: "All set, awaiting commands",
}
logger := dockerutil.NewContainerLogConsumer(logConfig)
cfg := dockerutil.Config{
ProjectName: projectName,
ComposeFile: "docker-compose.yml",
RemoteRepo: remote,
RequiredServices: []string{"main", "pg"},
LogConsumer: logger,
Timeout: dockerutil.Timeout{
Create: 200 * time.Second,
Recreate: 25 * time.Minute,
Start: 60 * time.Second,
},
}
manager, err := dockerutil.NewDockerManager(Ctx, cfg)
if err != nil {
initErr = err
return
}
instance = &docker{
docker: manager,
logger: logger,
}
})
if initErr != nil {
return nil, initErr
}
return instance, nil
}
// Init initializes the docker service
func Init() error {
if instance == nil {
if _, err := newDocker(); err != nil {
return err
}
}
return instance.docker.Init()
}
// InitQuiet initializes the docker service with reduced logging
func InitQuiet() error {
if instance == nil {
if _, err := newDocker(); err != nil {
return err
}
}
return instance.docker.InitQuiet()
}
// InitRecreate remove existing containers (if noCache is true, downloads the lastest
// version of dependencies ignoring local cache), then builds and up the containers
func InitRecreate(noCache bool) error {
if instance == nil {
if _, err := newDocker(); err != nil {
return err
}
}
if noCache {
return instance.docker.InitRecreateNoCache()
}
return instance.docker.InitRecreate()
}
func MustInit() {
if instance == nil {
newDocker()
}
instance.docker.InitRecreateNoCache()
}
// Stop stops the ichiran service
func Stop() error {
if instance == nil {
return fmt.Errorf("docker instance not initialized")
}
return instance.docker.Stop()
}
// Close implements io.Closer. It is just a convenience wrapper for Stop().
func Close() error {
if instance != nil {
instance.logger.Close()
return instance.docker.Close()
}
return nil
}
func Status() (string, error) {
if instance == nil {
return "", fmt.Errorf("docker instance not initialized")
}
return instance.docker.Status()
}
// readDockerOutput reads and processes multiplexed output from Docker.
func readDockerOutput(reader io.Reader) ([]byte, error) {
var output bytes.Buffer
header := make([]byte, 8)
for {
_, err := io.ReadFull(reader, header)
if err != nil {
if err == io.EOF {
break
}
return nil, fmt.Errorf("failed to read header: %w", err)
}
// Get the payload size from the header
payloadSize := binary.BigEndian.Uint32(header[4:])
if payloadSize == 0 {
continue
}
// Read the payload
payload := make([]byte, payloadSize)
_, err = io.ReadFull(reader, payload)
if err != nil {
return nil, fmt.Errorf("failed to read payload: %w", err)
}
// Append to output buffer
output.Write(payload)
}
return bytes.TrimSpace(output.Bytes()), nil
}
// extractJSONFromDockerOutput combines reading Docker output and extracting JSON
func extractJSONFromDockerOutput(reader io.Reader) ([]byte, error) {
// First, read the Docker multiplexed output.
rawOutput, err := readDockerOutput(reader)
if err != nil {
return nil, fmt.Errorf("error reading docker output: %w", err)
}
// Print raw output for debugging
//color.Redln("RAW ICHIRAN-CLI OUTPUT:")
//fmt.Println(string(rawOutput))
// Use bufio.Reader so we can read arbitrarily long lines.
r := bufio.NewReader(bytes.NewReader(rawOutput))
for {
line, err := r.ReadBytes('\n')
// Trim any extra whitespace.
line = bytes.TrimSpace(line)
if len(line) > 0 {
// Check if it's a JSON string wrapped in quotes (common for Lisp output)
if line[0] == '"' && line[len(line)-1] == '"' && len(line) > 2 {
// The content might have escaped quotes and backslashes
var unescaped string
if err := json.Unmarshal(line, &unescaped); err == nil {
// Now try to parse the unescaped content as JSON
var tmp interface{}
if err := json.Unmarshal([]byte(unescaped), &tmp); err == nil {
return []byte(unescaped), nil
}
}
}
// Regular JSON check - if the line starts with a JSON array or object.
if line[0] == '[' || line[0] == '{' {
var tmp interface{}
// Validate that it's actually JSON.
if err := json.Unmarshal(line, &tmp); err == nil {
return line, nil
}
}
}
if err == io.EOF {
break
} else if err != nil {
return nil, fmt.Errorf("error reading line: %w", err)
}
}
return nil, errNoJSONFound
}
func placeholder3456543() {
fmt.Print("")
color.Redln(" 𝒻*** 𝓎ℴ𝓊 𝒸ℴ𝓂𝓅𝒾𝓁ℯ𝓇")
pp.Println("𝓯*** 𝔂𝓸𝓾 𝓬𝓸𝓶𝓹𝓲𝓵𝓮𝓻")
}