Skip to content

Commit fac641f

Browse files
authored
feat: support starting store extentsion automatically (#246)
1 parent 82e9120 commit fac641f

File tree

9 files changed

+219
-28
lines changed

9 files changed

+219
-28
lines changed

Makefile

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ build:
1111
go build ${TOOLEXEC} -a -o bin/atest main.go
1212
build-ext-git:
1313
CGO_ENABLED=0 go build -ldflags "-w -s" -o bin/atest-store-git extensions/store-git/main.go
14+
build-ext-orm:
15+
CGO_ENABLED=0 go build -ldflags "-w -s" -o bin/atest-store-orm extensions/store-orm/main.go
1416
embed-ui:
1517
cd console/atest-ui && npm i && npm run build-only
1618
cp console/atest-ui/dist/index.html cmd/data/index.html

cmd/server.go

Lines changed: 8 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -119,12 +119,14 @@ func (o *serverOption) runE(cmd *cobra.Command, args []string) (err error) {
119119
template.SetSecretGetter(remote.NewGRPCSecretGetter(secretServer))
120120
}
121121

122-
remoteServer := server.NewRemoteServer(loader, remote.NewGRPCloaderFromStore(), secretServer, o.configDir)
122+
storeExtMgr := server.NewStoreExtManager(o.execer)
123+
124+
remoteServer := server.NewRemoteServer(loader, remote.NewGRPCloaderFromStore(), secretServer, storeExtMgr, o.configDir)
123125
kinds, storeKindsErr := remoteServer.GetStoreKinds(ctx, nil)
124126
if storeKindsErr != nil {
125127
cmd.PrintErrf("failed to get store kinds, error: %p\n", storeKindsErr)
126128
} else {
127-
if err = startPlugins(o.execer, kinds); err != nil {
129+
if err = startPlugins(storeExtMgr, kinds); err != nil {
128130
return
129131
}
130132
}
@@ -146,11 +148,7 @@ func (o *serverOption) runE(cmd *cobra.Command, args []string) (err error) {
146148
<-clean
147149
_ = lis.Close()
148150
_ = o.httpServer.Shutdown(ctx)
149-
for _, file := range filesNeedToBeRemoved {
150-
if err = os.RemoveAll(file); err != nil {
151-
log.Printf("failed to remove %s, error: %v", file, err)
152-
}
153-
}
151+
_ = storeExtMgr.StopAll()
154152
}()
155153

156154
mux := runtime.NewServeMux(runtime.WithMetadata(server.MetadataStoreFunc)) // runtime.WithIncomingHeaderMatcher(func(key string) (s string, b bool) {
@@ -200,22 +198,13 @@ func postRequestProxy(proxy string) func(w http.ResponseWriter, r *http.Request,
200198
}
201199
}
202200

203-
func startPlugins(execer fakeruntime.Execer, kinds *server.StoreKinds) (err error) {
201+
func startPlugins(storeExtMgr server.ExtManager, kinds *server.StoreKinds) (err error) {
204202
const socketPrefix = "unix://"
205203

206204
for _, kind := range kinds.Data {
207205
if kind.Enabled && strings.HasPrefix(kind.Url, socketPrefix) {
208-
binaryPath, lookErr := execer.LookPath(kind.Name)
209-
if lookErr != nil {
210-
log.Printf("failed to find %s, error: %v", kind.Name, lookErr)
211-
} else {
212-
go func(socketURL, plugin string) {
213-
socketFile := strings.TrimPrefix(socketURL, socketPrefix)
214-
filesNeedToBeRemoved = append(filesNeedToBeRemoved, socketFile)
215-
if err = execer.RunCommand(plugin, "--socket", socketFile); err != nil {
216-
log.Printf("failed to start %s, error: %v", socketURL, err)
217-
}
218-
}(kind.Url, binaryPath)
206+
if err = storeExtMgr.Start(kind.Name, kind.Url); err != nil {
207+
break
219208
}
220209
}
221210
}

cmd/server_test.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,27 @@
1+
/**
2+
MIT License
3+
4+
Copyright (c) 2023 API Testing Authors.
5+
6+
Permission is hereby granted, free of charge, to any person obtaining a copy
7+
of this software and associated documentation files (the "Software"), to deal
8+
in the Software without restriction, including without limitation the rights
9+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
copies of the Software, and to permit persons to whom the Software is
11+
furnished to do so, subject to the following conditions:
12+
13+
The above copyright notice and this permission notice shall be included in all
14+
copies or substantial portions of the Software.
15+
16+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22+
SOFTWARE.
23+
*/
24+
125
package cmd
226

327
import (

console/atest-ui/src/views/StoreManager.vue

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { reactive, ref } from 'vue'
44
import { Edit, Delete } from '@element-plus/icons-vue'
55
import type { FormInstance, FormRules } from 'element-plus'
66
import type { Pair } from './types'
7+
import { SupportedExtensions } from './store'
78
import { useI18n } from 'vue-i18n'
89
910
const { t } = useI18n()
@@ -266,7 +267,19 @@ function updateKeys() {
266267
<el-input v-model="storeForm.password" type="password" test-id="store-form-password" />
267268
</el-form-item>
268269
<el-form-item :label="t('field.pluginName')" prop="pluginName">
269-
<el-input v-model="storeForm.kind.name" test-id="store-form-plugin-name" />
270+
<el-select
271+
v-model="storeForm.kind.name"
272+
test-id="store-form-plugin-name"
273+
class="m-2"
274+
size="middle"
275+
>
276+
<el-option
277+
v-for="item in SupportedExtensions()"
278+
:key="item.value"
279+
:label="item.key"
280+
:value="item.value"
281+
/>
282+
</el-select>
270283
</el-form-item>
271284
<el-form-item :label="t('field.pluginURL')" prop="plugin">
272285
<el-input v-model="storeForm.kind.url" test-id="store-form-plugin" />
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { Pair } from './types'
2+
3+
export function SupportedExtensions() {
4+
return [
5+
{
6+
value: 'atest-store-git',
7+
key: 'atest-store-git'
8+
},
9+
{
10+
value: 'atest-store-s3',
11+
key: 'atest-store-s3'
12+
},
13+
{
14+
value: 'atest-store-orm',
15+
key: 'atest-store-orm'
16+
}
17+
] as Pair[]
18+
}

pkg/server/remote_server.go

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ type server struct {
5757
loader testing.Writer
5858
storeWriterFactory testing.StoreWriterFactory
5959
configDir string
60+
storeExtMgr ExtManager
6061

6162
secretServer SecretServiceServer
6263
}
@@ -97,7 +98,7 @@ func (f *fakeSecretServer) UpdateSecret(ctx context.Context, in *Secret) (reply
9798
}
9899

99100
// NewRemoteServer creates a remote server instance
100-
func NewRemoteServer(loader testing.Writer, storeWriterFactory testing.StoreWriterFactory, secretServer SecretServiceServer, configDir string) RunnerServer {
101+
func NewRemoteServer(loader testing.Writer, storeWriterFactory testing.StoreWriterFactory, secretServer SecretServiceServer, storeExtMgr ExtManager, configDir string) RunnerServer {
101102
if secretServer == nil {
102103
secretServer = &fakeSecretServer{}
103104
}
@@ -107,6 +108,7 @@ func NewRemoteServer(loader testing.Writer, storeWriterFactory testing.StoreWrit
107108
storeWriterFactory: storeWriterFactory,
108109
configDir: configDir,
109110
secretServer: secretServer,
111+
storeExtMgr: storeExtMgr,
110112
}
111113
}
112114

@@ -829,13 +831,19 @@ func (s *server) GetStores(ctx context.Context, in *Empty) (reply *Stores, err e
829831
func (s *server) CreateStore(ctx context.Context, in *Store) (reply *Store, err error) {
830832
reply = &Store{}
831833
storeFactory := testing.NewStoreFactory(s.configDir)
832-
err = storeFactory.CreateStore(ToNormalStore(in))
834+
store := ToNormalStore(in)
835+
if err = storeFactory.CreateStore(store); err == nil && s.storeExtMgr != nil {
836+
err = s.storeExtMgr.Start(store.Kind.Name, store.Kind.URL)
837+
}
833838
return
834839
}
835840
func (s *server) UpdateStore(ctx context.Context, in *Store) (reply *Store, err error) {
836841
reply = &Store{}
837842
storeFactory := testing.NewStoreFactory(s.configDir)
838-
err = storeFactory.UpdateStore(ToNormalStore(in))
843+
store := ToNormalStore(in)
844+
if err = storeFactory.UpdateStore(store); err == nil && s.storeExtMgr != nil {
845+
err = s.storeExtMgr.Start(store.Kind.Name, store.Kind.URL)
846+
}
839847
return
840848
}
841849
func (s *server) DeleteStore(ctx context.Context, in *Store) (reply *Store, err error) {

pkg/server/remote_server_test.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ func TestRemoteServer(t *testing.T) {
5454

5555
loader := atesting.NewFileWriter("")
5656
loader.Put("testdata/simple.yaml")
57-
server := NewRemoteServer(loader, nil, nil, "")
57+
server := NewRemoteServer(loader, nil, nil, nil, "")
5858
_, err := server.Run(ctx, &TestTask{
5959
Kind: "fake",
6060
})
@@ -138,7 +138,7 @@ func TestRemoteServer(t *testing.T) {
138138
func TestRunTestCase(t *testing.T) {
139139
loader := atesting.NewFileWriter("")
140140
loader.Put("testdata/simple.yaml")
141-
server := NewRemoteServer(loader, nil, nil, "")
141+
server := NewRemoteServer(loader, nil, nil, nil, "")
142142

143143
defer gock.Clean()
144144
gock.New(urlFoo).Get("/").MatchHeader("key", "value").
@@ -313,7 +313,7 @@ func TestUpdateTestCase(t *testing.T) {
313313
assert.NoError(t, err)
314314

315315
ctx := context.Background()
316-
server := NewRemoteServer(writer, nil, nil, "")
316+
server := NewRemoteServer(writer, nil, nil, nil, "")
317317
_, err = server.UpdateTestCase(ctx, &TestCaseWithSuite{
318318
SuiteName: "simple",
319319
Data: &TestCase{
@@ -385,7 +385,7 @@ func TestListTestCase(t *testing.T) {
385385
writer := atesting.NewFileWriter(os.TempDir())
386386
writer.Put(tmpFile.Name())
387387

388-
server := NewRemoteServer(writer, nil, nil, "")
388+
server := NewRemoteServer(writer, nil, nil, nil, "")
389389
ctx := context.Background()
390390

391391
t.Run("get two testcases", func(t *testing.T) {
@@ -813,7 +813,7 @@ func getRemoteServerInTempDir() (server RunnerServer, call func()) {
813813
call = func() { os.RemoveAll(dir) }
814814

815815
writer := atesting.NewFileWriter(dir)
816-
server = NewRemoteServer(writer, newLocalloaderFromStore(), nil, dir)
816+
server = NewRemoteServer(writer, newLocalloaderFromStore(), nil, nil, dir)
817817
return
818818
}
819819

pkg/server/store_ext_manager.go

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
/**
2+
MIT License
3+
4+
Copyright (c) 2023 API Testing Authors.
5+
6+
Permission is hereby granted, free of charge, to any person obtaining a copy
7+
of this software and associated documentation files (the "Software"), to deal
8+
in the Software without restriction, including without limitation the rights
9+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
copies of the Software, and to permit persons to whom the Software is
11+
furnished to do so, subject to the following conditions:
12+
13+
The above copyright notice and this permission notice shall be included in all
14+
copies or substantial portions of the Software.
15+
16+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22+
SOFTWARE.
23+
*/
24+
25+
package server
26+
27+
import (
28+
"fmt"
29+
"log"
30+
"os"
31+
"strings"
32+
33+
fakeruntime "github.com/linuxsuren/go-fake-runtime"
34+
)
35+
36+
type ExtManager interface {
37+
Start(name, socket string) (err error)
38+
StopAll() (err error)
39+
}
40+
41+
type storeExtManager struct {
42+
stopSignal chan struct{}
43+
execer fakeruntime.Execer
44+
socketPrefix string
45+
filesNeedToBeRemoved []string
46+
extStatusMap map[string]bool
47+
}
48+
49+
var s *storeExtManager
50+
51+
func NewStoreExtManager(execer fakeruntime.Execer) ExtManager {
52+
if s == nil {
53+
s = &storeExtManager{}
54+
s.execer = execer
55+
s.socketPrefix = "unix://"
56+
s.extStatusMap = map[string]bool{}
57+
}
58+
return s
59+
}
60+
61+
func (s *storeExtManager) Start(name, socket string) (err error) {
62+
if v, ok := s.extStatusMap[name]; ok && v {
63+
return
64+
}
65+
66+
binaryPath, lookErr := s.execer.LookPath(name)
67+
if lookErr != nil {
68+
err = fmt.Errorf("failed to find %s, error: %v", name, lookErr)
69+
} else {
70+
go func(socketURL, plugin string) {
71+
socketFile := strings.TrimPrefix(socketURL, s.socketPrefix)
72+
s.filesNeedToBeRemoved = append(s.filesNeedToBeRemoved, socketFile)
73+
s.extStatusMap[name] = true
74+
if err = s.execer.RunCommand(plugin, "--socket", socketFile); err != nil {
75+
log.Printf("failed to start %s, error: %v", socketURL, err)
76+
}
77+
}(socket, binaryPath)
78+
}
79+
return
80+
}
81+
82+
func (s *storeExtManager) StopAll() error {
83+
for _, file := range s.filesNeedToBeRemoved {
84+
if err := os.RemoveAll(file); err != nil {
85+
log.Printf("failed to remove %s, error: %v", file, err)
86+
}
87+
}
88+
return nil
89+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/**
2+
MIT License
3+
4+
Copyright (c) 2023 API Testing Authors.
5+
6+
Permission is hereby granted, free of charge, to any person obtaining a copy
7+
of this software and associated documentation files (the "Software"), to deal
8+
in the Software without restriction, including without limitation the rights
9+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
copies of the Software, and to permit persons to whom the Software is
11+
furnished to do so, subject to the following conditions:
12+
13+
The above copyright notice and this permission notice shall be included in all
14+
copies or substantial portions of the Software.
15+
16+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22+
SOFTWARE.
23+
*/
24+
25+
package server
26+
27+
import (
28+
"testing"
29+
30+
fakeruntime "github.com/linuxsuren/go-fake-runtime"
31+
"github.com/stretchr/testify/assert"
32+
)
33+
34+
func TestStoreExtManager(t *testing.T) {
35+
mgr := NewStoreExtManager(fakeruntime.DefaultExecer{})
36+
37+
t.Run("not found", func(t *testing.T) {
38+
err := mgr.Start("fake", "")
39+
assert.Error(t, err)
40+
})
41+
42+
t.Run("exist executable file", func(t *testing.T) {
43+
err := mgr.Start("go", "")
44+
assert.NoError(t, err)
45+
46+
err = mgr.StopAll()
47+
})
48+
}

0 commit comments

Comments
 (0)