Skip to content

Commit 967ec53

Browse files
authored
feat: support to start extension via http (#664)
* feat: support to start extension via http * fix the extension address checking --------- Co-authored-by: rick <[email protected]>
1 parent 4a26a8c commit 967ec53

File tree

2 files changed

+146
-134
lines changed

2 files changed

+146
-134
lines changed

cmd/server.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
Copyright 2023-2024 API Testing Authors.
2+
Copyright 2023-2025 API Testing Authors.
33
44
Licensed under the Apache License, Version 2.0 (the "License");
55
you may not use this file except in compliance with the License.
@@ -424,7 +424,7 @@ func startPlugins(storeExtMgr server.ExtManager, kinds *server.StoreKinds) (err
424424
const socketPrefix = "unix://"
425425

426426
for _, kind := range kinds.Data {
427-
if kind.Enabled && strings.HasPrefix(kind.Url, socketPrefix) {
427+
if kind.Enabled && (strings.HasPrefix(kind.Url, socketPrefix) || strings.Contains(kind.Url, ":")) {
428428
err = errors.Join(err, storeExtMgr.Start(kind.Name, kind.Url))
429429
}
430430
}

pkg/server/store_ext_manager.go

Lines changed: 144 additions & 132 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
Copyright 2023-2024 API Testing Authors.
2+
Copyright 2023-2025 API Testing Authors.
33
44
Licensed under the Apache License, Version 2.0 (the "License");
55
you may not use this file except in compliance with the License.
@@ -16,160 +16,172 @@ limitations under the License.
1616
package server
1717

1818
import (
19-
"context"
20-
"errors"
21-
"fmt"
22-
"io"
23-
"net/http"
24-
"os"
25-
"path/filepath"
26-
"strings"
27-
sync "sync"
28-
"syscall"
29-
"time"
30-
31-
"github.com/linuxsuren/api-testing/pkg/util/home"
32-
33-
"github.com/linuxsuren/api-testing/pkg/downloader"
34-
"github.com/linuxsuren/api-testing/pkg/logging"
35-
36-
fakeruntime "github.com/linuxsuren/go-fake-runtime"
19+
"context"
20+
"errors"
21+
"fmt"
22+
"io"
23+
"net/http"
24+
"os"
25+
"path/filepath"
26+
"strings"
27+
sync "sync"
28+
"syscall"
29+
"time"
30+
31+
"github.com/linuxsuren/api-testing/pkg/util/home"
32+
33+
"github.com/linuxsuren/api-testing/pkg/downloader"
34+
"github.com/linuxsuren/api-testing/pkg/logging"
35+
36+
fakeruntime "github.com/linuxsuren/go-fake-runtime"
3737
)
3838

3939
var (
40-
serverLogger = logging.DefaultLogger(logging.LogLevelInfo).WithName("server")
40+
serverLogger = logging.DefaultLogger(logging.LogLevelInfo).WithName("server")
4141
)
4242

4343
type ExtManager interface {
44-
Start(name, socket string) (err error)
45-
StopAll() (err error)
46-
WithDownloader(downloader.PlatformAwareOCIDownloader)
44+
Start(name, socket string) (err error)
45+
StopAll() (err error)
46+
WithDownloader(downloader.PlatformAwareOCIDownloader)
4747
}
4848

4949
type storeExtManager struct {
50-
execer fakeruntime.Execer
51-
ociDownloader downloader.PlatformAwareOCIDownloader
52-
socketPrefix string
53-
filesNeedToBeRemoved []string
54-
extStatusMap map[string]bool
55-
processs []fakeruntime.Process
56-
processChan chan fakeruntime.Process
57-
stopSingal chan struct{}
58-
lock *sync.RWMutex
50+
execer fakeruntime.Execer
51+
ociDownloader downloader.PlatformAwareOCIDownloader
52+
socketPrefix string
53+
filesNeedToBeRemoved []string
54+
extStatusMap map[string]bool
55+
processs []fakeruntime.Process
56+
processChan chan fakeruntime.Process
57+
stopSingal chan struct{}
58+
lock *sync.RWMutex
5959
}
6060

6161
var ss *storeExtManager
6262

6363
func NewStoreExtManager(execer fakeruntime.Execer) ExtManager {
64-
if ss == nil {
65-
ss = &storeExtManager{
66-
processChan: make(chan fakeruntime.Process),
67-
stopSingal: make(chan struct{}, 1),
68-
lock: &sync.RWMutex{},
69-
}
70-
ss.execer = execer
71-
ss.socketPrefix = "unix://"
72-
ss.extStatusMap = map[string]bool{}
73-
ss.processCollect()
74-
ss.WithDownloader(&nonDownloader{})
75-
}
76-
return ss
64+
if ss == nil {
65+
ss = &storeExtManager{
66+
processChan: make(chan fakeruntime.Process),
67+
stopSingal: make(chan struct{}, 1),
68+
lock: &sync.RWMutex{},
69+
}
70+
ss.execer = execer
71+
ss.socketPrefix = "unix://"
72+
ss.extStatusMap = map[string]bool{}
73+
ss.processCollect()
74+
ss.WithDownloader(&nonDownloader{})
75+
}
76+
return ss
7777
}
7878

7979
func NewStoreExtManagerInstance(execer fakeruntime.Execer) ExtManager {
80-
ss = &storeExtManager{
81-
processChan: make(chan fakeruntime.Process),
82-
stopSingal: make(chan struct{}, 1),
83-
lock: &sync.RWMutex{},
84-
}
85-
ss.execer = execer
86-
ss.socketPrefix = "unix://"
87-
ss.extStatusMap = map[string]bool{}
88-
ss.processCollect()
89-
ss.WithDownloader(&nonDownloader{})
90-
return ss
80+
ss = &storeExtManager{
81+
processChan: make(chan fakeruntime.Process),
82+
stopSingal: make(chan struct{}, 1),
83+
lock: &sync.RWMutex{},
84+
}
85+
ss.execer = execer
86+
ss.socketPrefix = "unix://"
87+
ss.extStatusMap = map[string]bool{}
88+
ss.processCollect()
89+
ss.WithDownloader(&nonDownloader{})
90+
return ss
9191
}
9292

9393
func (s *storeExtManager) Start(name, socket string) (err error) {
94-
if v, ok := s.extStatusMap[name]; ok && v {
95-
return
96-
}
97-
targetDir := home.GetUserBinDir()
98-
targetBinaryFile := filepath.Join(targetDir, name)
99-
100-
var binaryPath string
101-
if _, err = os.Stat(targetBinaryFile); err == nil {
102-
binaryPath = targetBinaryFile
103-
} else {
104-
binaryPath, err = s.execer.LookPath(name)
105-
if err != nil {
106-
err = fmt.Errorf("not found extension, try to download it, error: %v", err)
107-
go func() {
108-
reader, dErr := s.ociDownloader.Download(name, "", "")
109-
if dErr != nil {
110-
serverLogger.Error(dErr, "failed to download extension", "name", name)
111-
} else {
112-
extFile := s.ociDownloader.GetTargetFile()
113-
114-
targetFile := filepath.Base(extFile)
115-
if dErr = downloader.WriteTo(reader, targetDir, targetFile); dErr == nil {
116-
binaryPath = filepath.Join(targetDir, targetFile)
117-
s.startPlugin(socket, binaryPath, name)
118-
} else {
119-
serverLogger.Error(dErr, "failed to save extension", "targetFile", targetFile)
120-
}
121-
}
122-
}()
123-
}
124-
}
125-
126-
if err == nil {
127-
go s.startPlugin(socket, binaryPath, name)
128-
}
129-
return
94+
if v, ok := s.extStatusMap[name]; ok && v {
95+
return
96+
}
97+
targetDir := home.GetUserBinDir()
98+
targetBinaryFile := filepath.Join(targetDir, name)
99+
100+
var binaryPath string
101+
if _, err = os.Stat(targetBinaryFile); err == nil {
102+
binaryPath = targetBinaryFile
103+
} else {
104+
binaryPath, err = s.execer.LookPath(name)
105+
if err != nil {
106+
err = fmt.Errorf("not found extension, try to download it, error: %v", err)
107+
go func() {
108+
reader, dErr := s.ociDownloader.Download(name, "", "")
109+
if dErr != nil {
110+
serverLogger.Error(dErr, "failed to download extension", "name", name)
111+
} else {
112+
extFile := s.ociDownloader.GetTargetFile()
113+
114+
targetFile := filepath.Base(extFile)
115+
if dErr = downloader.WriteTo(reader, targetDir, targetFile); dErr == nil {
116+
binaryPath = filepath.Join(targetDir, targetFile)
117+
s.startPlugin(socket, binaryPath, name)
118+
} else {
119+
serverLogger.Error(dErr, "failed to save extension", "targetFile", targetFile)
120+
}
121+
}
122+
}()
123+
}
124+
}
125+
126+
if err == nil {
127+
go s.startPlugin(socket, binaryPath, name)
128+
}
129+
return
130130
}
131131

132132
func (s *storeExtManager) startPlugin(socketURL, plugin, pluginName string) (err error) {
133-
socketFile := strings.TrimPrefix(socketURL, s.socketPrefix)
134-
_ = os.RemoveAll(socketFile) // always deleting the socket file to avoid start failing
133+
if strings.Contains(socketURL, ":") && !strings.HasPrefix(socketURL, s.socketPrefix) {
134+
err = s.startPluginViaHTTP(socketURL, plugin, pluginName)
135+
return
136+
}
137+
socketFile := strings.TrimPrefix(socketURL, s.socketPrefix)
138+
_ = os.RemoveAll(socketFile) // always deleting the socket file to avoid start failing
135139

136-
s.lock.Lock()
137-
s.filesNeedToBeRemoved = append(s.filesNeedToBeRemoved, socketFile)
138-
s.extStatusMap[pluginName] = true
139-
s.lock.Unlock()
140+
s.lock.Lock()
141+
s.filesNeedToBeRemoved = append(s.filesNeedToBeRemoved, socketFile)
142+
s.extStatusMap[pluginName] = true
143+
s.lock.Unlock()
140144

141-
if err = s.execer.RunCommandWithIO(plugin, "", os.Stdout, os.Stderr, s.processChan, "--socket", socketFile); err != nil {
142-
serverLogger.Info("failed to start ext manager", "socket", socketURL, "error: ", err.Error())
143-
}
144-
return
145+
if err = s.execer.RunCommandWithIO(plugin, "", os.Stdout, os.Stderr, s.processChan, "--socket", socketFile); err != nil {
146+
serverLogger.Info("failed to start ext manager", "socket", socketURL, "error: ", err.Error())
147+
}
148+
return
149+
}
150+
151+
func (s *storeExtManager) startPluginViaHTTP(httpURL, plugin, pluginName string) (err error) {
152+
port := strings.Split(httpURL, ":")[1]
153+
if err = s.execer.RunCommandWithIO(plugin, "", os.Stdout, os.Stderr, s.processChan, "--port", port); err != nil {
154+
serverLogger.Info("failed to start ext manager", "port", port, "error: ", err.Error())
155+
}
156+
return
145157
}
146158

147159
func (s *storeExtManager) StopAll() error {
148-
serverLogger.Info("stop", "extensions", len(s.processs))
149-
for _, p := range s.processs {
150-
if p != nil {
151-
p.Signal(syscall.SIGTERM)
152-
}
153-
}
154-
s.stopSingal <- struct{}{}
155-
return nil
160+
serverLogger.Info("stop", "extensions", len(s.processs))
161+
for _, p := range s.processs {
162+
if p != nil {
163+
p.Signal(syscall.SIGTERM)
164+
}
165+
}
166+
s.stopSingal <- struct{}{}
167+
return nil
156168
}
157169

158170
func (s *storeExtManager) WithDownloader(ociDownloader downloader.PlatformAwareOCIDownloader) {
159-
s.ociDownloader = ociDownloader
171+
s.ociDownloader = ociDownloader
160172
}
161173

162174
func (s *storeExtManager) processCollect() {
163-
go func() {
164-
for {
165-
select {
166-
case p := <-s.processChan:
167-
s.processs = append(s.processs, p)
168-
case <-s.stopSingal:
169-
return
170-
}
171-
}
172-
}()
175+
go func() {
176+
for {
177+
select {
178+
case p := <-s.processChan:
179+
s.processs = append(s.processs, p)
180+
case <-s.stopSingal:
181+
return
182+
}
183+
}
184+
}()
173185
}
174186

175187
var ErrDownloadNotSupport = errors.New("no support")
@@ -179,44 +191,44 @@ type nonDownloader struct{}
179191
var _ downloader.PlatformAwareOCIDownloader = &nonDownloader{}
180192

181193
func (n *nonDownloader) WithBasicAuth(username string, password string) {
182-
// Do nothing because this is an empty implementation
194+
// Do nothing because this is an empty implementation
183195
}
184196

185197
func (n *nonDownloader) Download(image, tag, file string) (reader io.Reader, err error) {
186-
err = ErrDownloadNotSupport
187-
return
198+
err = ErrDownloadNotSupport
199+
return
188200
}
189201

190202
func (n *nonDownloader) WithOS(string) {
191-
// Do nothing because this is an empty implementation
203+
// Do nothing because this is an empty implementation
192204
}
193205

194206
func (n *nonDownloader) WithArch(string) {
195-
// Do nothing because this is an empty implementation
207+
// Do nothing because this is an empty implementation
196208
}
197209

198210
func (n *nonDownloader) WithRegistry(string) {
199-
// Do nothing because this is an empty implementation
211+
// Do nothing because this is an empty implementation
200212
}
201213

202214
func (n *nonDownloader) WithKind(string) {
203-
// Do nothing because this is an empty implementation
215+
// Do nothing because this is an empty implementation
204216
}
205217

206218
func (n *nonDownloader) WithImagePrefix(imagePrefix string) {
207-
// Do nothing because this is an empty implementation
219+
// Do nothing because this is an empty implementation
208220
}
209221
func (d *nonDownloader) WithRoundTripper(rt http.RoundTripper) {
210-
// Do nothing because this is an empty implementation
222+
// Do nothing because this is an empty implementation
211223
}
212224

213225
func (d *nonDownloader) WithInsecure(bool) {
214-
// Do nothing because this is an empty implementation
226+
// Do nothing because this is an empty implementation
215227
}
216228

217229
func (d *nonDownloader) WithTimeout(time.Duration) {}
218230
func (d *nonDownloader) WithContext(context.Context) {}
219231

220232
func (n *nonDownloader) GetTargetFile() string {
221-
return ""
233+
return ""
222234
}

0 commit comments

Comments
 (0)