feat: support starting store extentsion automatically (#246)
This commit is contained in:
parent
82e91207e9
commit
fac641fef9
2
Makefile
2
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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 (
|
||||
|
|
|
@ -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() {
|
|||
<el-input v-model="storeForm.password" type="password" test-id="store-form-password" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('field.pluginName')" prop="pluginName">
|
||||
<el-input v-model="storeForm.kind.name" test-id="store-form-plugin-name" />
|
||||
<el-select
|
||||
v-model="storeForm.kind.name"
|
||||
test-id="store-form-plugin-name"
|
||||
class="m-2"
|
||||
size="middle"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in SupportedExtensions()"
|
||||
:key="item.value"
|
||||
:label="item.key"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('field.pluginURL')" prop="plugin">
|
||||
<el-input v-model="storeForm.kind.url" test-id="store-form-plugin" />
|
||||
|
|
|
@ -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[]
|
||||
}
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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()
|
||||
})
|
||||
}
|
Loading…
Reference in New Issue