diff --git a/Makefile b/Makefile
index 43bac5c8..f802d864 100644
--- a/Makefile
+++ b/Makefile
@@ -11,6 +11,8 @@ build:
go build ${TOOLEXEC} -a -o bin/atest main.go
build-ext-git:
CGO_ENABLED=0 go build -ldflags "-w -s" -o bin/atest-store-git extensions/store-git/main.go
+build-ext-orm:
+ CGO_ENABLED=0 go build -ldflags "-w -s" -o bin/atest-store-orm extensions/store-orm/main.go
embed-ui:
cd console/atest-ui && npm i && npm run build-only
cp console/atest-ui/dist/index.html cmd/data/index.html
diff --git a/cmd/server.go b/cmd/server.go
index 96912992..5b076b8a 100644
--- a/cmd/server.go
+++ b/cmd/server.go
@@ -119,12 +119,14 @@ func (o *serverOption) runE(cmd *cobra.Command, args []string) (err error) {
template.SetSecretGetter(remote.NewGRPCSecretGetter(secretServer))
}
- remoteServer := server.NewRemoteServer(loader, remote.NewGRPCloaderFromStore(), secretServer, o.configDir)
+ storeExtMgr := server.NewStoreExtManager(o.execer)
+
+ remoteServer := server.NewRemoteServer(loader, remote.NewGRPCloaderFromStore(), secretServer, storeExtMgr, o.configDir)
kinds, storeKindsErr := remoteServer.GetStoreKinds(ctx, nil)
if storeKindsErr != nil {
cmd.PrintErrf("failed to get store kinds, error: %p\n", storeKindsErr)
} else {
- if err = startPlugins(o.execer, kinds); err != nil {
+ if err = startPlugins(storeExtMgr, kinds); err != nil {
return
}
}
@@ -146,11 +148,7 @@ func (o *serverOption) runE(cmd *cobra.Command, args []string) (err error) {
<-clean
_ = lis.Close()
_ = o.httpServer.Shutdown(ctx)
- for _, file := range filesNeedToBeRemoved {
- if err = os.RemoveAll(file); err != nil {
- log.Printf("failed to remove %s, error: %v", file, err)
- }
- }
+ _ = storeExtMgr.StopAll()
}()
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,
}
}
-func startPlugins(execer fakeruntime.Execer, kinds *server.StoreKinds) (err error) {
+func startPlugins(storeExtMgr server.ExtManager, kinds *server.StoreKinds) (err error) {
const socketPrefix = "unix://"
for _, kind := range kinds.Data {
if kind.Enabled && strings.HasPrefix(kind.Url, socketPrefix) {
- binaryPath, lookErr := execer.LookPath(kind.Name)
- if lookErr != nil {
- log.Printf("failed to find %s, error: %v", kind.Name, lookErr)
- } else {
- go func(socketURL, plugin string) {
- socketFile := strings.TrimPrefix(socketURL, socketPrefix)
- filesNeedToBeRemoved = append(filesNeedToBeRemoved, socketFile)
- if err = execer.RunCommand(plugin, "--socket", socketFile); err != nil {
- log.Printf("failed to start %s, error: %v", socketURL, err)
- }
- }(kind.Url, binaryPath)
+ if err = storeExtMgr.Start(kind.Name, kind.Url); err != nil {
+ break
}
}
}
diff --git a/cmd/server_test.go b/cmd/server_test.go
index 813b678b..66707bea 100644
--- a/cmd/server_test.go
+++ b/cmd/server_test.go
@@ -1,3 +1,27 @@
+/**
+MIT License
+
+Copyright (c) 2023 API Testing Authors.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+*/
+
package cmd
import (
diff --git a/console/atest-ui/src/views/StoreManager.vue b/console/atest-ui/src/views/StoreManager.vue
index d9cbe676..1ac0fd38 100644
--- a/console/atest-ui/src/views/StoreManager.vue
+++ b/console/atest-ui/src/views/StoreManager.vue
@@ -4,6 +4,7 @@ import { reactive, ref } from 'vue'
import { Edit, Delete } from '@element-plus/icons-vue'
import type { FormInstance, FormRules } from 'element-plus'
import type { Pair } from './types'
+import { SupportedExtensions } from './store'
import { useI18n } from 'vue-i18n'
const { t } = useI18n()
@@ -266,7 +267,19 @@ function updateKeys() {
-
+
+
+
diff --git a/console/atest-ui/src/views/store.ts b/console/atest-ui/src/views/store.ts
new file mode 100644
index 00000000..81d36687
--- /dev/null
+++ b/console/atest-ui/src/views/store.ts
@@ -0,0 +1,18 @@
+import { Pair } from './types'
+
+export function SupportedExtensions() {
+ return [
+ {
+ value: 'atest-store-git',
+ key: 'atest-store-git'
+ },
+ {
+ value: 'atest-store-s3',
+ key: 'atest-store-s3'
+ },
+ {
+ value: 'atest-store-orm',
+ key: 'atest-store-orm'
+ }
+ ] as Pair[]
+}
\ No newline at end of file
diff --git a/pkg/server/remote_server.go b/pkg/server/remote_server.go
index b7ba972c..6781d125 100644
--- a/pkg/server/remote_server.go
+++ b/pkg/server/remote_server.go
@@ -57,6 +57,7 @@ type server struct {
loader testing.Writer
storeWriterFactory testing.StoreWriterFactory
configDir string
+ storeExtMgr ExtManager
secretServer SecretServiceServer
}
@@ -97,7 +98,7 @@ func (f *fakeSecretServer) UpdateSecret(ctx context.Context, in *Secret) (reply
}
// NewRemoteServer creates a remote server instance
-func NewRemoteServer(loader testing.Writer, storeWriterFactory testing.StoreWriterFactory, secretServer SecretServiceServer, configDir string) RunnerServer {
+func NewRemoteServer(loader testing.Writer, storeWriterFactory testing.StoreWriterFactory, secretServer SecretServiceServer, storeExtMgr ExtManager, configDir string) RunnerServer {
if secretServer == nil {
secretServer = &fakeSecretServer{}
}
@@ -107,6 +108,7 @@ func NewRemoteServer(loader testing.Writer, storeWriterFactory testing.StoreWrit
storeWriterFactory: storeWriterFactory,
configDir: configDir,
secretServer: secretServer,
+ storeExtMgr: storeExtMgr,
}
}
@@ -829,13 +831,19 @@ func (s *server) GetStores(ctx context.Context, in *Empty) (reply *Stores, err e
func (s *server) CreateStore(ctx context.Context, in *Store) (reply *Store, err error) {
reply = &Store{}
storeFactory := testing.NewStoreFactory(s.configDir)
- err = storeFactory.CreateStore(ToNormalStore(in))
+ store := ToNormalStore(in)
+ if err = storeFactory.CreateStore(store); err == nil && s.storeExtMgr != nil {
+ err = s.storeExtMgr.Start(store.Kind.Name, store.Kind.URL)
+ }
return
}
func (s *server) UpdateStore(ctx context.Context, in *Store) (reply *Store, err error) {
reply = &Store{}
storeFactory := testing.NewStoreFactory(s.configDir)
- err = storeFactory.UpdateStore(ToNormalStore(in))
+ store := ToNormalStore(in)
+ if err = storeFactory.UpdateStore(store); err == nil && s.storeExtMgr != nil {
+ err = s.storeExtMgr.Start(store.Kind.Name, store.Kind.URL)
+ }
return
}
func (s *server) DeleteStore(ctx context.Context, in *Store) (reply *Store, err error) {
diff --git a/pkg/server/remote_server_test.go b/pkg/server/remote_server_test.go
index e68bc137..ef001a4b 100644
--- a/pkg/server/remote_server_test.go
+++ b/pkg/server/remote_server_test.go
@@ -54,7 +54,7 @@ func TestRemoteServer(t *testing.T) {
loader := atesting.NewFileWriter("")
loader.Put("testdata/simple.yaml")
- server := NewRemoteServer(loader, nil, nil, "")
+ server := NewRemoteServer(loader, nil, nil, nil, "")
_, err := server.Run(ctx, &TestTask{
Kind: "fake",
})
@@ -138,7 +138,7 @@ func TestRemoteServer(t *testing.T) {
func TestRunTestCase(t *testing.T) {
loader := atesting.NewFileWriter("")
loader.Put("testdata/simple.yaml")
- server := NewRemoteServer(loader, nil, nil, "")
+ server := NewRemoteServer(loader, nil, nil, nil, "")
defer gock.Clean()
gock.New(urlFoo).Get("/").MatchHeader("key", "value").
@@ -313,7 +313,7 @@ func TestUpdateTestCase(t *testing.T) {
assert.NoError(t, err)
ctx := context.Background()
- server := NewRemoteServer(writer, nil, nil, "")
+ server := NewRemoteServer(writer, nil, nil, nil, "")
_, err = server.UpdateTestCase(ctx, &TestCaseWithSuite{
SuiteName: "simple",
Data: &TestCase{
@@ -385,7 +385,7 @@ func TestListTestCase(t *testing.T) {
writer := atesting.NewFileWriter(os.TempDir())
writer.Put(tmpFile.Name())
- server := NewRemoteServer(writer, nil, nil, "")
+ server := NewRemoteServer(writer, nil, nil, nil, "")
ctx := context.Background()
t.Run("get two testcases", func(t *testing.T) {
@@ -813,7 +813,7 @@ func getRemoteServerInTempDir() (server RunnerServer, call func()) {
call = func() { os.RemoveAll(dir) }
writer := atesting.NewFileWriter(dir)
- server = NewRemoteServer(writer, newLocalloaderFromStore(), nil, dir)
+ server = NewRemoteServer(writer, newLocalloaderFromStore(), nil, nil, dir)
return
}
diff --git a/pkg/server/store_ext_manager.go b/pkg/server/store_ext_manager.go
new file mode 100644
index 00000000..3370c514
--- /dev/null
+++ b/pkg/server/store_ext_manager.go
@@ -0,0 +1,89 @@
+/**
+MIT License
+
+Copyright (c) 2023 API Testing Authors.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+*/
+
+package server
+
+import (
+ "fmt"
+ "log"
+ "os"
+ "strings"
+
+ fakeruntime "github.com/linuxsuren/go-fake-runtime"
+)
+
+type ExtManager interface {
+ Start(name, socket string) (err error)
+ StopAll() (err error)
+}
+
+type storeExtManager struct {
+ stopSignal chan struct{}
+ execer fakeruntime.Execer
+ socketPrefix string
+ filesNeedToBeRemoved []string
+ extStatusMap map[string]bool
+}
+
+var s *storeExtManager
+
+func NewStoreExtManager(execer fakeruntime.Execer) ExtManager {
+ if s == nil {
+ s = &storeExtManager{}
+ s.execer = execer
+ s.socketPrefix = "unix://"
+ s.extStatusMap = map[string]bool{}
+ }
+ return s
+}
+
+func (s *storeExtManager) Start(name, socket string) (err error) {
+ if v, ok := s.extStatusMap[name]; ok && v {
+ return
+ }
+
+ binaryPath, lookErr := s.execer.LookPath(name)
+ if lookErr != nil {
+ err = fmt.Errorf("failed to find %s, error: %v", name, lookErr)
+ } else {
+ go func(socketURL, plugin string) {
+ socketFile := strings.TrimPrefix(socketURL, s.socketPrefix)
+ s.filesNeedToBeRemoved = append(s.filesNeedToBeRemoved, socketFile)
+ s.extStatusMap[name] = true
+ if err = s.execer.RunCommand(plugin, "--socket", socketFile); err != nil {
+ log.Printf("failed to start %s, error: %v", socketURL, err)
+ }
+ }(socket, binaryPath)
+ }
+ return
+}
+
+func (s *storeExtManager) StopAll() error {
+ for _, file := range s.filesNeedToBeRemoved {
+ if err := os.RemoveAll(file); err != nil {
+ log.Printf("failed to remove %s, error: %v", file, err)
+ }
+ }
+ return nil
+}
diff --git a/pkg/server/store_ext_manager_test.go b/pkg/server/store_ext_manager_test.go
new file mode 100644
index 00000000..a9d40cec
--- /dev/null
+++ b/pkg/server/store_ext_manager_test.go
@@ -0,0 +1,48 @@
+/**
+MIT License
+
+Copyright (c) 2023 API Testing Authors.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+*/
+
+package server
+
+import (
+ "testing"
+
+ fakeruntime "github.com/linuxsuren/go-fake-runtime"
+ "github.com/stretchr/testify/assert"
+)
+
+func TestStoreExtManager(t *testing.T) {
+ mgr := NewStoreExtManager(fakeruntime.DefaultExecer{})
+
+ t.Run("not found", func(t *testing.T) {
+ err := mgr.Start("fake", "")
+ assert.Error(t, err)
+ })
+
+ t.Run("exist executable file", func(t *testing.T) {
+ err := mgr.Start("go", "")
+ assert.NoError(t, err)
+
+ err = mgr.StopAll()
+ })
+}