common/apiserver/fromexe.go (233 lines of code) (raw):
package apiserver
import (
"encoding/json"
"errors"
"os"
"path/filepath"
"sync"
model "github.com/aliyun/aliyun_assist_client/common/apiserver/fromexemodel"
"github.com/aliyun/aliyun_assist_client/common/pathutil"
"github.com/aliyun/aliyun_assist_client/common/requester"
"github.com/aliyun/aliyun_assist_client/thirdparty/sirupsen/logrus"
)
type ExternalExecutableProvider struct {
rwLock sync.RWMutex
executablePath *string
pemCerts *[]byte
pemCertsError string
serverDomain *string
serverDomainError string
extraHTTPheaders *map[string]string
regionId *string
regionIdError string
}
func (p *ExternalExecutableProvider) Name() string {
p.rwLock.RLock()
defer p.rwLock.RUnlock()
if p.executablePath == nil {
return "<UnknownExternalExecutableProvider>"
}
if *p.executablePath == "" {
return "<NoExternalExecutableProvider>"
}
return *p.executablePath
}
func (p *ExternalExecutableProvider) CACertificate(logger logrus.FieldLogger, refresh bool) ([]byte, error) {
logger = logger.WithField("refresh", refresh)
if refresh {
p.rwLock.Lock()
defer p.rwLock.Unlock()
p.unsafeProvision(logger)
return p.unsafeGetCACertificate()
}
p.rwLock.RLock()
if p.executablePath != nil {
defer p.rwLock.RUnlock()
return p.unsafeGetCACertificate()
}
p.rwLock.RUnlock()
p.rwLock.Lock()
defer p.rwLock.Unlock()
if p.executablePath == nil {
p.unsafeProvision(logger)
}
return p.unsafeGetCACertificate()
}
func (p *ExternalExecutableProvider) unsafeGetCACertificate() ([]byte, error) {
if p.executablePath == nil {
return nil, requester.ErrNotProvided
}
if *p.executablePath == "" {
return nil, requester.ErrNotProvided
}
if p.pemCerts == nil {
return nil, requester.ErrNotProvided
}
if p.pemCertsError != "" {
return nil, errors.New(p.pemCertsError)
}
return *p.pemCerts, nil
}
func (p *ExternalExecutableProvider) ServerDomain(logger logrus.FieldLogger) (string, error) {
p.rwLock.RLock()
if p.executablePath != nil {
defer p.rwLock.RUnlock()
return p.unsafeGetServerDomain()
}
p.rwLock.RUnlock()
p.rwLock.Lock()
defer p.rwLock.Unlock()
if p.executablePath == nil {
p.unsafeProvision(logger)
}
return p.unsafeGetServerDomain()
}
func (p *ExternalExecutableProvider) unsafeGetServerDomain() (string, error) {
if p.executablePath == nil {
return "", requester.ErrNotProvided
}
if *p.executablePath == "" {
return "", requester.ErrNotProvided
}
if p.serverDomain == nil {
return "", requester.ErrNotProvided
}
if p.serverDomainError != "" {
return "", errors.New(p.serverDomainError)
}
return *p.serverDomain, nil
}
func (p *ExternalExecutableProvider) ExtraHTTPHeaders(logger logrus.FieldLogger) (map[string]string, error) {
p.rwLock.RLock()
if p.executablePath != nil {
defer p.rwLock.RUnlock()
return p.unsafeGetExtraHTTPHeaders()
}
p.rwLock.RUnlock()
p.rwLock.Lock()
defer p.rwLock.Unlock()
if p.executablePath == nil {
p.unsafeProvision(logger)
}
return p.unsafeGetExtraHTTPHeaders()
}
func (p *ExternalExecutableProvider) unsafeGetExtraHTTPHeaders() (map[string]string, error) {
if p.executablePath == nil {
return nil, requester.ErrNotProvided
}
if *p.executablePath == "" {
return nil, requester.ErrNotProvided
}
if p.extraHTTPheaders == nil {
return nil, requester.ErrNotProvided
}
return *p.extraHTTPheaders, nil
}
func (p *ExternalExecutableProvider) RegionId(logger logrus.FieldLogger) (string, error) {
p.rwLock.RLock()
if p.executablePath != nil {
defer p.rwLock.RUnlock()
return p.unsafeGetRegionId()
}
p.rwLock.RUnlock()
p.rwLock.Lock()
defer p.rwLock.Unlock()
if p.executablePath == nil {
p.unsafeProvision(logger)
}
return p.unsafeGetRegionId()
}
func (p *ExternalExecutableProvider) unsafeGetRegionId() (string, error) {
if p.executablePath == nil {
return "", requester.ErrNotProvided
}
if *p.executablePath == "" {
return "", requester.ErrNotProvided
}
if p.regionId == nil {
return "", requester.ErrNotProvided
}
if p.regionIdError != "" {
return "", errors.New(p.regionIdError)
}
return *p.regionId, nil
}
func (p *ExternalExecutableProvider) unsafeProvision(logger logrus.FieldLogger) {
configDir, err := pathutil.GetConfigPath()
if err != nil {
configDir = ""
logger.WithError(err).Error("Get config path failed")
}
crossVersionConfigDir, err := pathutil.GetCrossVersionConfigPath()
if err != nil {
crossVersionConfigDir = ""
logger.WithError(err).Error("Get cross version config path failed")
}
if configDir == "" && crossVersionConfigDir == "" {
return
}
for _, candidateName := range candidateExternalExecutableProviderNames {
candidatePath := filepath.Join(configDir, candidateName)
if _, err := os.Stat(candidatePath); !os.IsNotExist(err) {
p.executablePath = &candidatePath
break
}
}
if p.executablePath == nil {
for _, candidateName := range candidateExternalExecutableProviderNames {
candidatePath := filepath.Join(crossVersionConfigDir, candidateName)
if _, err := os.Stat(candidatePath); !os.IsNotExist(err) {
p.executablePath = &candidatePath
break
}
}
}
if p.executablePath == nil {
noExecutablePath := ""
p.executablePath = &noExecutablePath
return
}
stdout, stderr, err := runExternalProvider(*p.executablePath)
logger = logger.WithFields(logrus.Fields{
"path": *p.executablePath,
"stdout": stdout,
"stderr": stderr,
})
if err != nil {
logger.WithError(err).Error("Failed to run external API server provider")
return
}
var provision model.ProvisionOutputV1
if err := json.Unmarshal([]byte(stdout), &provision); err != nil {
logger.WithError(err).Error("Mal-formatted stdout from external provider")
return
}
if provision.SchemaVersion != "1.0" {
logger.WithError(errors.New("unknown schema version")).Errorf("Failed to parse provider stdout of schema version %s", provision.SchemaVersion)
return
}
var provisionErr model.ErrorOutputV1
if err := json.Unmarshal([]byte(stderr), &provisionErr); err != nil {
logger.WithError(err).Error("Mal-formatted stderr from external provider")
}
if provisionErr.SchemaVersion != "1.0" {
logger.WithError(errors.New("unknown schema version")).Errorf("Failed to parse provider stderr of schema version %s", provisionErr.SchemaVersion)
return
}
pemCerts := []byte(provision.Result.CACertificate)
p.pemCerts = &pemCerts
p.serverDomain = &provision.Result.ServerDomain
p.extraHTTPheaders = &provision.Result.ExtraHTTPHeaders
p.regionId = &provision.Result.RegionId
for _, errmsg := range provisionErr.Error {
switch errmsg.Code {
case model.CodeGetServerDomainError:
p.serverDomainError = errmsg.Message
case model.CodeGetCACertificateError:
p.pemCertsError = errmsg.Message
case model.CodeRegionIdError:
p.regionIdError = errmsg.Message
}
}
logger.WithField("provision", p).Info("Provisioned API server information with external provider")
}