mirror of
https://github.com/TECHNOFAB11/powerproto.git
synced 2026-02-02 09:25:07 +01:00
init
Signed-off-by: storyicon <yuanchao@bilibili.com>
This commit is contained in:
commit
9aac714c32
47 changed files with 5480 additions and 0 deletions
43
pkg/component/pluginmanager/errors.go
Normal file
43
pkg/component/pluginmanager/errors.go
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
// Copyright 2021 storyicon@foxmail.com
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package pluginmanager
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/storyicon/powerproto/pkg/util/command"
|
||||
)
|
||||
|
||||
// ErrGoInstall defines the go install error
|
||||
type ErrGoInstall struct {
|
||||
*command.ErrCommandExec
|
||||
}
|
||||
|
||||
// ErrGoList defines the go list error
|
||||
type ErrGoList struct {
|
||||
*command.ErrCommandExec
|
||||
}
|
||||
|
||||
// ErrHTTPDownload defines the download error
|
||||
type ErrHTTPDownload struct {
|
||||
Url string
|
||||
Err error
|
||||
Code int
|
||||
}
|
||||
|
||||
// Error implements the error interface
|
||||
func (err *ErrHTTPDownload) Error() string {
|
||||
return fmt.Sprintf("failed to download %s, code: %d, err: %s", err.Url, err.Code, err.Err)
|
||||
}
|
||||
215
pkg/component/pluginmanager/manager.go
Normal file
215
pkg/component/pluginmanager/manager.go
Normal file
|
|
@ -0,0 +1,215 @@
|
|||
// Copyright 2021 storyicon@foxmail.com
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package pluginmanager
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"io/fs"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/storyicon/powerproto/pkg/consts"
|
||||
"github.com/storyicon/powerproto/pkg/util"
|
||||
"github.com/storyicon/powerproto/pkg/util/logger"
|
||||
)
|
||||
|
||||
var defaultExecuteTimeout = time.Second * 60
|
||||
|
||||
// PluginManager is used to manage plugins
|
||||
type PluginManager interface {
|
||||
// GetPluginLatestVersion is used to get the latest version of plugin
|
||||
GetPluginLatestVersion(ctx context.Context, path string) (string, error)
|
||||
// ListPluginVersions is used to list the versions of plugin
|
||||
ListPluginVersions(ctx context.Context, path string) ([]string, error)
|
||||
// IsPluginInstalled is used to check whether the plugin is installed
|
||||
IsPluginInstalled(ctx context.Context, path string, version string) (bool, string, error)
|
||||
// InstallPlugin is used to install plugin
|
||||
InstallPlugin(ctx context.Context, path string, version string) (local string, err error)
|
||||
|
||||
// GetProtocLatestVersion is used to geet the latest version of protoc
|
||||
GetProtocLatestVersion(ctx context.Context) (string, error)
|
||||
// ListProtocVersions is used to list protoc version
|
||||
ListProtocVersions(ctx context.Context) ([]string, error)
|
||||
// IsProtocInstalled is used to check whether the protoc is installed
|
||||
IsProtocInstalled(ctx context.Context, version string) (bool, string, error)
|
||||
// InstallProtoc is used to install protoc of specified version
|
||||
InstallProtoc(ctx context.Context, version string) (local string, err error)
|
||||
// IncludePath returns the default include path
|
||||
IncludePath(ctx context.Context) string
|
||||
}
|
||||
|
||||
// Config defines the config of PluginManager
|
||||
type Config struct {
|
||||
StorageDir string `json:"storage"`
|
||||
}
|
||||
|
||||
// NewConfig is used to create config
|
||||
func NewConfig() *Config {
|
||||
return &Config{
|
||||
StorageDir: consts.GetHomeDir(),
|
||||
}
|
||||
}
|
||||
|
||||
// NewPluginManager is used to create PluginManager
|
||||
func NewPluginManager(cfg *Config, log logger.Logger) (PluginManager, error) {
|
||||
return NewBasicPluginManager(cfg.StorageDir, log)
|
||||
}
|
||||
|
||||
// BasicPluginManager is the basic implement of PluginManager
|
||||
type BasicPluginManager struct {
|
||||
logger.Logger
|
||||
storageDir string
|
||||
versions map[string][]string
|
||||
versionsLock sync.RWMutex
|
||||
}
|
||||
|
||||
// NewBasicPluginManager is used to create basic PluginManager
|
||||
func NewBasicPluginManager(storageDir string, log logger.Logger) (*BasicPluginManager, error) {
|
||||
return &BasicPluginManager{
|
||||
Logger: log.NewLogger("pluginmanager"),
|
||||
storageDir: storageDir,
|
||||
versions: map[string][]string{},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetPluginLatestVersion is used to get the latest version of plugin
|
||||
func (b *BasicPluginManager) GetPluginLatestVersion(ctx context.Context, path string) (string, error) {
|
||||
versions, err := b.ListPluginVersions(ctx, path)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if len(versions) == 0 {
|
||||
return "", errors.New("no version list")
|
||||
}
|
||||
return versions[len(versions)-1], nil
|
||||
}
|
||||
|
||||
// ListPluginVersions is used to list the versions of plugin
|
||||
func (b *BasicPluginManager) ListPluginVersions(ctx context.Context, path string) ([]string, error) {
|
||||
ctx, cancel := context.WithTimeout(ctx, defaultExecuteTimeout)
|
||||
defer cancel()
|
||||
|
||||
b.versionsLock.RLock()
|
||||
versions, ok := b.versions[path]
|
||||
b.versionsLock.RUnlock()
|
||||
if ok {
|
||||
return versions, nil
|
||||
}
|
||||
versions, err := ListsGoPackageVersionsAmbiguously(ctx, b.Logger, path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
b.versionsLock.Lock()
|
||||
b.versions[path] = versions
|
||||
b.versionsLock.Unlock()
|
||||
return versions, nil
|
||||
}
|
||||
|
||||
// IsPluginInstalled is used to check whether the plugin is installed
|
||||
func (b *BasicPluginManager) IsPluginInstalled(ctx context.Context, path string, version string) (bool, string, error) {
|
||||
return IsPluginInstalled(ctx, b.storageDir, path, version)
|
||||
}
|
||||
|
||||
// InstallPlugin is used to install plugin
|
||||
func (b *BasicPluginManager) InstallPlugin(ctx context.Context, path string, version string) (local string, err error) {
|
||||
return InstallPluginUsingGo(ctx, b.Logger, b.storageDir, path, version)
|
||||
}
|
||||
|
||||
// GetProtocLatestVersion is used to geet the latest version of protoc
|
||||
func (b *BasicPluginManager) GetProtocLatestVersion(ctx context.Context) (string, error) {
|
||||
versions, err := b.ListProtocVersions(ctx)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if len(versions) == 0 {
|
||||
return "", errors.New("no version list")
|
||||
}
|
||||
return versions[len(versions)-1], nil
|
||||
}
|
||||
|
||||
// ListProtocVersions is used to list protoc version
|
||||
func (b *BasicPluginManager) ListProtocVersions(ctx context.Context) ([]string, error) {
|
||||
ctx, cancel := context.WithTimeout(ctx, defaultExecuteTimeout)
|
||||
defer cancel()
|
||||
|
||||
b.versionsLock.RLock()
|
||||
versions, ok := b.versions["protoc"]
|
||||
b.versionsLock.RUnlock()
|
||||
if ok {
|
||||
return versions, nil
|
||||
}
|
||||
versions, err := ListGitTags(ctx, b.Logger, consts.ProtobufRepository)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
b.versionsLock.Lock()
|
||||
b.versions["protoc"] = versions
|
||||
b.versionsLock.Unlock()
|
||||
return versions, nil
|
||||
}
|
||||
|
||||
// IsProtocInstalled is used to check whether the protoc is installed
|
||||
func (b *BasicPluginManager) IsProtocInstalled(ctx context.Context, version string) (bool, string, error) {
|
||||
if strings.HasPrefix(version, "v") {
|
||||
version = strings.TrimPrefix(version, "v")
|
||||
}
|
||||
return IsProtocInstalled(ctx, b.storageDir, version)
|
||||
}
|
||||
|
||||
// InstallProtoc is used to install protoc of specified version
|
||||
func (b *BasicPluginManager) InstallProtoc(ctx context.Context, version string) (string, error) {
|
||||
ctx, cancel := context.WithTimeout(ctx, defaultExecuteTimeout)
|
||||
defer cancel()
|
||||
|
||||
if strings.HasPrefix(version, "v") {
|
||||
version = strings.TrimPrefix(version, "v")
|
||||
}
|
||||
local := PathForProtoc(b.storageDir, version)
|
||||
exists, err := util.IsFileExists(local)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if exists {
|
||||
return local, nil
|
||||
}
|
||||
|
||||
release, err := GetProtocRelease(ctx, version)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer release.Clear()
|
||||
// merge include files
|
||||
includeDir := PathForInclude(b.storageDir)
|
||||
if err := util.CopyDirectory(release.GetIncludePath(), includeDir); err != nil {
|
||||
return "", err
|
||||
}
|
||||
// download protoc file
|
||||
if err := util.CopyFile(release.GetProtocPath(), local); err != nil {
|
||||
return "", err
|
||||
}
|
||||
// * it is required on unix system
|
||||
if err := os.Chmod(local, fs.ModePerm); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return local, nil
|
||||
}
|
||||
|
||||
// IncludePath returns the default include path
|
||||
func (b *BasicPluginManager) IncludePath(ctx context.Context) string {
|
||||
return PathForInclude(b.storageDir)
|
||||
}
|
||||
92
pkg/component/pluginmanager/paths.go
Normal file
92
pkg/component/pluginmanager/paths.go
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
// Copyright 2021 storyicon@foxmail.com
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package pluginmanager
|
||||
|
||||
import (
|
||||
"path"
|
||||
"path/filepath"
|
||||
|
||||
"golang.org/x/mod/module"
|
||||
|
||||
"github.com/storyicon/powerproto/pkg/util"
|
||||
)
|
||||
|
||||
|
||||
// PathForInclude is used to get the local directory of include files
|
||||
func PathForInclude(storageDir string) string {
|
||||
return filepath.Join(storageDir, "include")
|
||||
}
|
||||
|
||||
// PathForProtoc is used to get the local binary location where the specified version protoc should be stored
|
||||
func PathForProtoc(storageDir string, version string) string {
|
||||
return filepath.Join(storageDir, "protoc", version, util.GetBinaryFileName("protoc"))
|
||||
}
|
||||
|
||||
// GetPluginPath is used to get the plugin path
|
||||
func GetPluginPath(path string, version string) (string, error) {
|
||||
enc, err := module.EscapePath(path)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
encVer, err := module.EscapeVersion(version)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return filepath.Join(enc + "@" + encVer), nil
|
||||
}
|
||||
|
||||
// PathForPluginDir is used to get the local directory where the specified version plug-in should be stored
|
||||
func PathForPluginDir(storageDir string, path string, version string) (string, error) {
|
||||
pluginPath, err := GetPluginPath(path, version)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return filepath.Join(storageDir, "plugins", pluginPath), nil
|
||||
}
|
||||
|
||||
// PathForPlugin is used to get the binary path of plugin
|
||||
func PathForPlugin(storageDir string, path string, version string) (string, error) {
|
||||
name := GetGoPkgExecName(path)
|
||||
dir, err := PathForPluginDir(storageDir, path, version)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return filepath.Join(dir, util.GetBinaryFileName(name)), nil
|
||||
}
|
||||
|
||||
// isVersionElement reports whether s is a well-formed path version element:
|
||||
// v2, v3, v10, etc, but not v0, v05, v1.
|
||||
// `src\cmd\go\internal\load\pkg.go:1209`
|
||||
func isVersionElement(s string) bool {
|
||||
if len(s) < 2 || s[0] != 'v' || s[1] == '0' || s[1] == '1' && len(s) == 2 {
|
||||
return false
|
||||
}
|
||||
for i := 1; i < len(s); i++ {
|
||||
if s[i] < '0' || '9' < s[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// GetGoPkgExecName is used to parse binary name from pkg uri
|
||||
// `src\cmd\go\internal\load\pkg.go:1595`
|
||||
func GetGoPkgExecName(pkgPath string) string {
|
||||
_, elem := path.Split(pkgPath)
|
||||
if elem != pkgPath && isVersionElement(elem) {
|
||||
_, elem = path.Split(path.Dir(pkgPath))
|
||||
}
|
||||
return elem
|
||||
}
|
||||
174
pkg/component/pluginmanager/plugin.go
Normal file
174
pkg/component/pluginmanager/plugin.go
Normal file
|
|
@ -0,0 +1,174 @@
|
|||
// Copyright 2021 storyicon@foxmail.com
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package pluginmanager
|
||||
|
||||
import (
|
||||
"context"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/go-multierror"
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
|
||||
"github.com/storyicon/powerproto/pkg/util"
|
||||
"github.com/storyicon/powerproto/pkg/util/command"
|
||||
"github.com/storyicon/powerproto/pkg/util/logger"
|
||||
)
|
||||
|
||||
// IsPluginInstalled is used to check whether a plugin is installed
|
||||
func IsPluginInstalled(ctx context.Context,
|
||||
storageDir string,
|
||||
path string, version string) (bool, string, error) {
|
||||
local, err := PathForPlugin(storageDir, path, version)
|
||||
if err != nil {
|
||||
return false, "", err
|
||||
}
|
||||
exists, err := util.IsFileExists(local)
|
||||
if err != nil {
|
||||
return false, "", err
|
||||
}
|
||||
if exists {
|
||||
return true, local, nil
|
||||
}
|
||||
return false, "", nil
|
||||
}
|
||||
|
||||
// InstallPluginUsingGo is used to install plugin using golang
|
||||
func InstallPluginUsingGo(ctx context.Context,
|
||||
log logger.Logger,
|
||||
storageDir string,
|
||||
path string, version string) (string, error) {
|
||||
exists, local, err := IsPluginInstalled(ctx, storageDir, path, version)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if exists {
|
||||
return local, nil
|
||||
}
|
||||
|
||||
local, err = PathForPlugin(storageDir, path, version)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
dir := filepath.Dir(local)
|
||||
|
||||
uri := util.JoinGoPackageVersion(path, version)
|
||||
_, err2 := command.Execute(ctx, log, "", "go", []string{
|
||||
"install", uri,
|
||||
}, []string{"GOBIN=" + dir, "GO111MODULE=on"})
|
||||
if err2 != nil {
|
||||
return "", &ErrGoInstall{
|
||||
ErrCommandExec: err2,
|
||||
}
|
||||
}
|
||||
return local, nil
|
||||
}
|
||||
|
||||
// ///////////////// Version Control /////////////////
|
||||
|
||||
// Module defines the model of go list data
|
||||
type Module struct {
|
||||
Path string // module path
|
||||
Version string // module version
|
||||
Versions []string // available module versions (with -versions)
|
||||
Replace *Module // replaced by this module
|
||||
Time *time.Time // time version was created
|
||||
Update *Module // available update, if any (with -u)
|
||||
Main bool // is this the main module?
|
||||
Indirect bool // is this module only an indirect dependency of main module?
|
||||
Dir string // directory holding files for this module, if any
|
||||
GoMod string // path to go.mod file used when loading this module, if any
|
||||
GoVersion string // go version used in module
|
||||
Retracted string // retraction information, if any (with -retracted or -u)
|
||||
Error *ModuleError // error loading module
|
||||
}
|
||||
|
||||
// ModuleError defines the module error
|
||||
type ModuleError struct {
|
||||
Err string // the error itself
|
||||
}
|
||||
|
||||
// ListGoPackageVersions is list go package versions
|
||||
func ListGoPackageVersions(ctx context.Context, log logger.Logger, path string) ([]string, error) {
|
||||
// query from latest version
|
||||
// If latest is not specified here, the queried version
|
||||
// may be restricted to the current project go.mod/go.sum
|
||||
pkg := util.JoinGoPackageVersion(path, "latest")
|
||||
data, err := command.Execute(ctx, log, "", "go", []string{
|
||||
"list", "-m", "-json", "-versions", pkg,
|
||||
}, []string{
|
||||
"GO111MODULE=on",
|
||||
})
|
||||
if err != nil {
|
||||
return nil, &ErrGoList{
|
||||
ErrCommandExec: err,
|
||||
}
|
||||
}
|
||||
var module Module
|
||||
if err := jsoniter.Unmarshal(data, &module); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(module.Versions) != 0 {
|
||||
return module.Versions, nil
|
||||
}
|
||||
return []string{module.Version}, nil
|
||||
}
|
||||
|
||||
// ListsGoPackageVersionsAmbiguously is used to list go package versions ambiguously
|
||||
func ListsGoPackageVersionsAmbiguously(ctx context.Context, log logger.Logger, pkg string) ([]string, error) {
|
||||
type Result struct {
|
||||
err error
|
||||
pkg string
|
||||
versions []string
|
||||
}
|
||||
items := strings.Split(pkg, "/")
|
||||
dataMap := make([]*Result, len(items))
|
||||
notify := make(chan struct{}, 1)
|
||||
maxIndex := len(items) - 1
|
||||
for i := maxIndex; i >= 1; i-- {
|
||||
go func(i int) {
|
||||
pkg := strings.Join(items[0:i+1], "/")
|
||||
versions, err := ListGoPackageVersions(context.TODO(), log, pkg)
|
||||
dataMap[maxIndex-i] = &Result{
|
||||
pkg: pkg,
|
||||
versions: versions,
|
||||
err: err,
|
||||
}
|
||||
notify <- struct{}{}
|
||||
}(i)
|
||||
}
|
||||
OutLoop:
|
||||
for {
|
||||
select {
|
||||
case <-notify:
|
||||
var errs error
|
||||
for _, data := range dataMap {
|
||||
if data == nil {
|
||||
continue OutLoop
|
||||
}
|
||||
if data.err != nil {
|
||||
errs = multierror.Append(errs, data.err)
|
||||
}
|
||||
if data.versions != nil {
|
||||
return data.versions, nil
|
||||
}
|
||||
}
|
||||
return nil, errs
|
||||
case <-ctx.Done():
|
||||
return nil, ctx.Err()
|
||||
}
|
||||
}
|
||||
}
|
||||
27
pkg/component/pluginmanager/pluginmanager_suite_test.go
Normal file
27
pkg/component/pluginmanager/pluginmanager_suite_test.go
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
// Copyright 2021 storyicon@foxmail.com
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package pluginmanager_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
func TestPluginmanager(t *testing.T) {
|
||||
RegisterFailHandler(Fail)
|
||||
RunSpecs(t, "Pluginmanager Suite")
|
||||
}
|
||||
73
pkg/component/pluginmanager/pluginmanager_test.go
Normal file
73
pkg/component/pluginmanager/pluginmanager_test.go
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
// Copyright 2021 storyicon@foxmail.com
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package pluginmanager_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"path/filepath"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
|
||||
"github.com/storyicon/powerproto/pkg/component/pluginmanager"
|
||||
"github.com/storyicon/powerproto/pkg/util/logger"
|
||||
)
|
||||
|
||||
var _ = Describe("Pluginmanager", func() {
|
||||
cfg := pluginmanager.NewConfig()
|
||||
cfg.StorageDir, _ = filepath.Abs("./tests")
|
||||
|
||||
const pluginPkg = "google.golang.org/protobuf/cmd/protoc-gen-go"
|
||||
var manager pluginmanager.PluginManager
|
||||
It("should able to init", func() {
|
||||
pluginManager, err := pluginmanager.NewPluginManager(cfg, logger.NewDefault("pluginmanager"))
|
||||
Expect(err).To(BeNil())
|
||||
Expect(pluginManager).To(Not(BeNil()))
|
||||
manager = pluginManager
|
||||
})
|
||||
It("should able to install protoc", func() {
|
||||
versions, err := manager.ListProtocVersions(context.TODO())
|
||||
Expect(err).To(BeNil())
|
||||
Expect(len(versions) > 0).To(BeTrue())
|
||||
latestVersion, err := manager.GetProtocLatestVersion(context.TODO())
|
||||
Expect(err).To(BeNil())
|
||||
Expect(latestVersion).To(Equal(versions[len(versions)-1]))
|
||||
|
||||
local, err := manager.InstallProtoc(context.TODO(), latestVersion)
|
||||
Expect(err).To(BeNil())
|
||||
exists, local, err := manager.IsProtocInstalled(context.TODO(), latestVersion)
|
||||
Expect(err).To(BeNil())
|
||||
Expect(exists).To(BeTrue())
|
||||
Expect(len(local) != 0).To(BeTrue())
|
||||
})
|
||||
It("should able to install plugin", func() {
|
||||
versions, err := manager.ListPluginVersions(context.TODO(), pluginPkg)
|
||||
Expect(err).To(BeNil())
|
||||
Expect(len(versions) > 0).To(BeTrue())
|
||||
|
||||
latestVersion, err := manager.GetPluginLatestVersion(context.TODO(), pluginPkg)
|
||||
Expect(err).To(BeNil())
|
||||
Expect(latestVersion).To(Equal(versions[len(versions)-1]))
|
||||
|
||||
local, err := manager.InstallPlugin(context.TODO(), pluginPkg, latestVersion)
|
||||
Expect(err).To(BeNil())
|
||||
Expect(len(local) > 0).To(BeTrue())
|
||||
|
||||
exists, local, err := manager.IsPluginInstalled(context.TODO(), pluginPkg, latestVersion)
|
||||
Expect(err).To(BeNil())
|
||||
Expect(exists).To(BeTrue())
|
||||
Expect(len(local) != 0).To(BeTrue())
|
||||
})
|
||||
})
|
||||
186
pkg/component/pluginmanager/protoc.go
Normal file
186
pkg/component/pluginmanager/protoc.go
Normal file
|
|
@ -0,0 +1,186 @@
|
|||
// Copyright 2021 storyicon@foxmail.com
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package pluginmanager
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/fs"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/mholt/archiver"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/storyicon/powerproto/pkg/util"
|
||||
"github.com/storyicon/powerproto/pkg/util/command"
|
||||
"github.com/storyicon/powerproto/pkg/util/logger"
|
||||
)
|
||||
|
||||
// ProtocRelease defines the release of protoc
|
||||
type ProtocRelease struct {
|
||||
workspace string
|
||||
}
|
||||
|
||||
// GetIncludePath is used to get the include path
|
||||
func (p *ProtocRelease) GetIncludePath() string {
|
||||
return filepath.Join(p.workspace, "include")
|
||||
}
|
||||
|
||||
// GetProtocPath is used to get the protoc path
|
||||
func (p *ProtocRelease) GetProtocPath() string {
|
||||
return filepath.Join(p.workspace, "bin", util.GetBinaryFileName("protoc"))
|
||||
}
|
||||
|
||||
// Clear is used to clear the workspace
|
||||
func (p *ProtocRelease) Clear() error {
|
||||
return os.RemoveAll(p.workspace)
|
||||
}
|
||||
|
||||
// GetProtocRelease is used to download protoc release
|
||||
func GetProtocRelease(ctx context.Context, version string) (*ProtocRelease, error) {
|
||||
workspace, err := os.MkdirTemp("", "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
suffix, err := inferProtocReleaseSuffix()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
filename := fmt.Sprintf("protoc-%s-%s.zip", version, suffix)
|
||||
url := fmt.Sprintf("https://github.com/protocolbuffers/protobuf/"+
|
||||
"releases/download/v%s/%s", version, filename)
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
|
||||
if err != nil {
|
||||
return nil, &ErrHTTPDownload{
|
||||
Url: url,
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, &ErrHTTPDownload{
|
||||
Url: url,
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
zipFilePath := filepath.Join(workspace, filename)
|
||||
if err := downloadFile(resp, zipFilePath); err != nil {
|
||||
return nil, &ErrHTTPDownload{
|
||||
Url: url,
|
||||
Err: err,
|
||||
Code: resp.StatusCode,
|
||||
}
|
||||
}
|
||||
zip := archiver.NewZip()
|
||||
if err := zip.Unarchive(zipFilePath, workspace); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &ProtocRelease{
|
||||
workspace: workspace,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// IsProtocInstalled is used to check whether the protoc version is installed
|
||||
func IsProtocInstalled(ctx context.Context, storageDir string, version string) (bool, string, error) {
|
||||
local := PathForProtoc(storageDir, version)
|
||||
exists, err := util.IsFileExists(local)
|
||||
if err != nil {
|
||||
return false, "", err
|
||||
}
|
||||
return exists, local, nil
|
||||
}
|
||||
|
||||
// ErrGitList defines the git list error
|
||||
type ErrGitList struct {
|
||||
*command.ErrCommandExec
|
||||
}
|
||||
|
||||
// ListGitTags is used to list the git tags of specified repository
|
||||
func ListGitTags(ctx context.Context, log logger.Logger, repo string) ([]string, error) {
|
||||
data, err := command.Execute(ctx, log, "", "git", []string{
|
||||
"ls-remote", "--tags", "--refs", "--sort", "version:refname", repo,
|
||||
}, nil)
|
||||
if err != nil {
|
||||
return nil, &ErrGitList{
|
||||
ErrCommandExec: err,
|
||||
}
|
||||
}
|
||||
var tags []string
|
||||
for _, line := range strings.Split(string(data), "\n") {
|
||||
f := strings.Fields(line)
|
||||
if len(f) != 2 {
|
||||
continue
|
||||
}
|
||||
if strings.HasPrefix(f[1], "refs/tags/") {
|
||||
tags = append(tags, strings.TrimPrefix(f[1], "refs/tags/"))
|
||||
}
|
||||
}
|
||||
return tags, nil
|
||||
}
|
||||
|
||||
func inferProtocReleaseSuffix() (string, error) {
|
||||
goos := strings.ToLower(runtime.GOOS)
|
||||
arch := strings.ToLower(runtime.GOARCH)
|
||||
switch goos {
|
||||
case "linux":
|
||||
switch arch {
|
||||
case "arm64":
|
||||
return "linux-aarch_64", nil
|
||||
case "ppc64le":
|
||||
return "linux-ppcle_64", nil
|
||||
case "s390x":
|
||||
return "linux-s390_64", nil
|
||||
case "386":
|
||||
return "linux-x86_32", nil
|
||||
case "amd64":
|
||||
return "linux-x86_64", nil
|
||||
}
|
||||
case "darwin":
|
||||
return "osx-x86_64", nil
|
||||
case "windows":
|
||||
switch arch {
|
||||
case "386":
|
||||
return "win32", nil
|
||||
case "amd64":
|
||||
return "win64", nil
|
||||
}
|
||||
}
|
||||
return "", errors.New("protoc did not release on this platform")
|
||||
}
|
||||
|
||||
func downloadFile(resp *http.Response, destination string) error {
|
||||
if err := os.MkdirAll(filepath.Dir(destination), fs.ModePerm); err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode != 200 {
|
||||
return errors.Errorf("unexpected code %d for url: %s", resp.StatusCode, resp.Request.URL.String())
|
||||
}
|
||||
file, err := os.OpenFile(destination, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, fs.ModePerm)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
_, err = io.Copy(file, resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue