mirror of
https://github.com/APIParkLab/APIPark.git
synced 2026-06-04 10:13:53 +08:00
476 lines
14 KiB
Go
476 lines
14 KiB
Go
package service_diff
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
|
|
"github.com/APIParkLab/APIPark/service/strategy"
|
|
|
|
"github.com/APIParkLab/APIPark/service/service"
|
|
|
|
"github.com/APIParkLab/APIPark/service/api"
|
|
api_doc "github.com/APIParkLab/APIPark/service/api-doc"
|
|
"github.com/APIParkLab/APIPark/service/cluster"
|
|
"github.com/APIParkLab/APIPark/service/release"
|
|
"github.com/APIParkLab/APIPark/service/service_diff"
|
|
"github.com/APIParkLab/APIPark/service/universally/commit"
|
|
"github.com/APIParkLab/APIPark/service/upstream"
|
|
"github.com/eolinker/go-common/utils"
|
|
)
|
|
|
|
type imlServiceDiff struct {
|
|
apiService api.IAPIService `autowired:""`
|
|
serviceService service.IServiceService `autowired:""`
|
|
apiDocService api_doc.IAPIDocService `autowired:""`
|
|
upstreamService upstream.IUpstreamService `autowired:""`
|
|
releaseService release.IReleaseService `autowired:""`
|
|
strategyService strategy.IStrategyService `autowired:""`
|
|
clusterService cluster.IClusterService `autowired:""`
|
|
}
|
|
|
|
func (m *imlServiceDiff) Diff(ctx context.Context, serviceId string, baseRelease, targetRelease string) (*service_diff.Diff, error) {
|
|
if targetRelease == "" {
|
|
return nil, fmt.Errorf("target release is required")
|
|
}
|
|
|
|
var target *projectInfo
|
|
|
|
targetReleaseValue, err := m.releaseService.GetRelease(ctx, targetRelease)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("get target release failed:%w", err)
|
|
}
|
|
if targetReleaseValue.Service != serviceId {
|
|
return nil, errors.New("project not match")
|
|
}
|
|
|
|
target, err = m.getReleaseInfo(ctx, targetRelease)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
base, err := m.getBaseInfo(ctx, serviceId, baseRelease)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
target.id = serviceId
|
|
clusters, err := m.clusterService.List(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
clusterIds := utils.SliceToSlice(clusters, func(i *cluster.Cluster) string {
|
|
return i.Uuid
|
|
})
|
|
diff := m.diff(clusterIds, base, target)
|
|
return diff, nil
|
|
|
|
}
|
|
func (m *imlServiceDiff) getBaseInfo(ctx context.Context, serviceId, baseRelease string) (*projectInfo, error) {
|
|
if baseRelease == "" {
|
|
return &projectInfo{}, nil
|
|
}
|
|
baseReleaseValue, err := m.releaseService.GetRelease(ctx, baseRelease)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("get base release failed:%w", err)
|
|
}
|
|
if baseReleaseValue.Service != serviceId {
|
|
return nil, errors.New("project not match")
|
|
}
|
|
base, err := m.getReleaseInfo(ctx, baseRelease)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("get base release info failed:%w", err)
|
|
}
|
|
|
|
return base, nil
|
|
}
|
|
|
|
func (m *imlServiceDiff) latestStrategyCommits(ctx context.Context, serviceId string) ([]*commit.Commit[strategy.Commit], error) {
|
|
list, err := m.strategyService.All(ctx, 2, serviceId)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("get latest strategy failed:%w", err)
|
|
}
|
|
|
|
return utils.SliceToSlice(list, func(s *strategy.Strategy) *commit.Commit[strategy.Commit] {
|
|
key := fmt.Sprintf("service-%s", s.Id)
|
|
return &commit.Commit[strategy.Commit]{
|
|
Target: s.Id,
|
|
Key: key,
|
|
Data: &strategy.Commit{
|
|
Id: s.Id,
|
|
Name: s.Name,
|
|
Priority: s.Priority,
|
|
Filters: s.Filters,
|
|
Config: s.Config,
|
|
Driver: s.Driver,
|
|
IsStop: s.IsStop,
|
|
Version: s.UpdateAt.Format("20060102150405"),
|
|
},
|
|
}
|
|
}, func(s *strategy.Strategy) bool {
|
|
return !s.IsDelete
|
|
}), nil
|
|
}
|
|
|
|
func (m *imlServiceDiff) DiffForLatest(ctx context.Context, serviceId string, baseRelease string) (*service_diff.Diff, bool, error) {
|
|
serviceInfo, err := m.serviceService.Get(ctx, serviceId)
|
|
if err != nil {
|
|
return nil, false, fmt.Errorf("get service info failed:%w", err)
|
|
}
|
|
apis, err := m.apiService.ListForService(ctx, serviceId)
|
|
if err != nil {
|
|
return nil, false, err
|
|
}
|
|
if len(apis) < 1 {
|
|
return nil, false, fmt.Errorf("api not found")
|
|
}
|
|
|
|
apiIds := utils.SliceToSlice(apis, func(i *api.API) string {
|
|
return i.UUID
|
|
})
|
|
apiInfos, err := m.apiService.ListInfo(ctx, apiIds...)
|
|
if err != nil {
|
|
return nil, false, err
|
|
}
|
|
request := make([]*commit.Commit[api.Request], 0, len(apiInfos))
|
|
for _, apiInfo := range apiInfos {
|
|
request = append(request, &commit.Commit[api.Request]{
|
|
Target: apiInfo.UUID,
|
|
Key: "request",
|
|
Data: &api.Request{
|
|
Name: apiInfo.Name,
|
|
Path: apiInfo.Path,
|
|
Methods: apiInfo.Methods,
|
|
Protocols: apiInfo.Protocols,
|
|
Match: apiInfo.Match,
|
|
Disable: apiInfo.Disable,
|
|
Upstream: apiInfo.Upstream,
|
|
},
|
|
})
|
|
|
|
}
|
|
proxy, err := m.apiService.ListLatestCommitProxy(ctx, apiIds...)
|
|
if err != nil {
|
|
return nil, false, fmt.Errorf("diff for api commit %v", err)
|
|
}
|
|
apiDocCommits, err := m.apiDocService.ListLatestDocCommit(ctx, serviceId)
|
|
if err != nil {
|
|
return nil, false, err
|
|
}
|
|
|
|
upstreamCommits, err := m.upstreamService.ListLatestCommit(ctx, cluster.DefaultClusterID, serviceId)
|
|
if err != nil {
|
|
return nil, false, err
|
|
}
|
|
if len(upstreamCommits) == 0 && serviceInfo.Kind == service.RestService {
|
|
return nil, false, fmt.Errorf("upstream not found")
|
|
}
|
|
|
|
strategyCommits, err := m.latestStrategyCommits(ctx, serviceId)
|
|
if err != nil {
|
|
return nil, false, err
|
|
}
|
|
|
|
base, err := m.getBaseInfo(ctx, serviceId, baseRelease)
|
|
if err != nil {
|
|
return nil, false, err
|
|
}
|
|
target := &projectInfo{
|
|
id: serviceId,
|
|
apiRequestCommits: request,
|
|
apiProxyCommits: proxy,
|
|
apiDocCommits: apiDocCommits,
|
|
upstreamCommits: upstreamCommits,
|
|
strategyCommits: strategyCommits,
|
|
}
|
|
clusters, err := m.clusterService.List(ctx)
|
|
if err != nil {
|
|
return nil, false, err
|
|
}
|
|
clusterIds := utils.SliceToSlice(clusters, func(i *cluster.Cluster) string {
|
|
return i.Uuid
|
|
})
|
|
return m.diff(clusterIds, base, target), true, nil
|
|
}
|
|
func (m *imlServiceDiff) getReleaseInfo(ctx context.Context, releaseId string) (*projectInfo, error) {
|
|
commits, err := m.releaseService.GetCommits(ctx, releaseId)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
apiRequestCommitIds := make([]string, 0)
|
|
apiProxyCommitIds := make([]string, 0)
|
|
apiDocumentCommitIds := make([]string, 0)
|
|
upstreamCommitIds := make([]string, 0)
|
|
strategyCommitIds := make([]string, 0)
|
|
for _, c := range commits {
|
|
switch c.Type {
|
|
case release.CommitApiRequest:
|
|
apiRequestCommitIds = append(apiRequestCommitIds, c.Commit)
|
|
case release.CommitApiProxy:
|
|
apiProxyCommitIds = append(apiProxyCommitIds, c.Commit)
|
|
case release.CommitApiDocument:
|
|
apiDocumentCommitIds = append(apiDocumentCommitIds, c.Commit)
|
|
case release.CommitUpstream:
|
|
upstreamCommitIds = append(upstreamCommitIds, c.Commit)
|
|
case release.CommitStrategy:
|
|
strategyCommitIds = append(strategyCommitIds, c.Commit)
|
|
}
|
|
}
|
|
|
|
var requestCommits []*commit.Commit[api.Request]
|
|
var proxyCommits []*commit.Commit[api.Proxy]
|
|
var documentCommits []*commit.Commit[api_doc.DocCommit]
|
|
if len(apiRequestCommitIds) > 0 {
|
|
requestCommits, err = m.apiService.ListRequestCommit(ctx, apiRequestCommitIds...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
if len(apiProxyCommitIds) > 0 {
|
|
proxyCommits, err = m.apiService.ListProxyCommit(ctx, apiProxyCommitIds...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
if len(apiDocumentCommitIds) > 0 {
|
|
documentCommits, err = m.apiDocService.ListDocCommit(ctx, apiDocumentCommitIds...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
var upstreamCommits []*commit.Commit[upstream.Config]
|
|
if len(upstreamCommitIds) > 0 {
|
|
upstreamCommits, err = m.upstreamService.ListCommit(ctx, upstreamCommitIds...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
var strategyCommits []*commit.Commit[strategy.Commit]
|
|
if len(strategyCommitIds) > 0 {
|
|
strategyCommits, err = m.strategyService.ListStrategyCommit(ctx, strategyCommitIds...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
return &projectInfo{
|
|
apiRequestCommits: requestCommits,
|
|
apiProxyCommits: proxyCommits,
|
|
apiDocCommits: documentCommits,
|
|
upstreamCommits: upstreamCommits,
|
|
strategyCommits: strategyCommits,
|
|
}, nil
|
|
}
|
|
|
|
func (m *imlServiceDiff) diffStrategies(base, target []*commit.Commit[strategy.Commit]) []*service_diff.StrategyDiff {
|
|
baseStrategy := utils.SliceToMap(base, func(i *commit.Commit[strategy.Commit]) string {
|
|
return i.Target
|
|
})
|
|
targetStrategy := utils.SliceToMap(target, func(i *commit.Commit[strategy.Commit]) string {
|
|
return i.Target
|
|
})
|
|
out := make([]*service_diff.StrategyDiff, 0, len(target))
|
|
for _, tc := range targetStrategy {
|
|
|
|
//t := tc.Data
|
|
key := tc.Target
|
|
o := &service_diff.StrategyDiff{
|
|
Strategy: key,
|
|
Name: tc.Data.Name,
|
|
Priority: tc.Data.Priority,
|
|
Change: service_diff.ChangeTypeNone,
|
|
Status: 0,
|
|
}
|
|
b, hasB := baseStrategy[key]
|
|
if !hasB {
|
|
o.Change = service_diff.ChangeTypeNew
|
|
} else if tc.Data.Version != b.Data.Version {
|
|
o.Change = service_diff.ChangeTypeUpdate
|
|
}
|
|
delete(baseStrategy, key)
|
|
out = append(out, o)
|
|
}
|
|
for _, b := range baseStrategy {
|
|
o := &service_diff.StrategyDiff{
|
|
Strategy: b.Target,
|
|
Name: b.Data.Name,
|
|
Priority: b.Data.Priority,
|
|
Change: service_diff.ChangeTypeDelete,
|
|
Status: 0,
|
|
}
|
|
out = append(out, o)
|
|
}
|
|
return out
|
|
}
|
|
func (m *imlServiceDiff) diff(partitions []string, base, target *projectInfo) *service_diff.Diff {
|
|
out := &service_diff.Diff{
|
|
Apis: nil,
|
|
Upstreams: nil,
|
|
}
|
|
baseApis := utils.NewSet(utils.SliceToSlice(base.apiRequestCommits, func(i *commit.Commit[api.Request]) string {
|
|
return i.Target
|
|
})...)
|
|
baseApiProxy := utils.SliceToMap(base.apiProxyCommits, func(i *commit.Commit[api.Proxy]) string {
|
|
return i.Target
|
|
})
|
|
baseAPIDoc := utils.SliceToMap(base.apiDocCommits, func(i *commit.Commit[api_doc.DocCommit]) string {
|
|
return i.Target
|
|
})
|
|
|
|
targetApiProxy := utils.SliceToMap(target.apiProxyCommits, func(i *commit.Commit[api.Proxy]) string {
|
|
return i.Target
|
|
})
|
|
targetAPIDoc := utils.SliceToMap(target.apiDocCommits, func(i *commit.Commit[api_doc.DocCommit]) string {
|
|
return i.Target
|
|
})
|
|
|
|
for _, rc := range target.apiRequestCommits {
|
|
apiId := rc.Target
|
|
a := &service_diff.ApiDiff{
|
|
Name: rc.Data.Name,
|
|
APi: rc.Target,
|
|
Method: rc.Data.Methods,
|
|
Protocol: rc.Data.Protocols,
|
|
Disable: false,
|
|
Path: rc.Data.Path,
|
|
Change: 0,
|
|
Status: service_diff.Status{},
|
|
}
|
|
|
|
pc, hasPc := targetApiProxy[apiId]
|
|
dc, hasDC := targetAPIDoc[apiId]
|
|
if !hasPc {
|
|
// 未设置proxy信息
|
|
a.Status.Proxy = service_diff.StatusUnset
|
|
}
|
|
if !hasDC {
|
|
// 未设置文档
|
|
a.Status.Doc = service_diff.StatusUnset
|
|
}
|
|
|
|
if !baseApis.Has(apiId) {
|
|
a.Change = service_diff.ChangeTypeNew
|
|
} else {
|
|
a.Change = service_diff.ChangeTypeNone
|
|
|
|
baseProxy, hasBaseProxy := baseApiProxy[apiId]
|
|
baseDoc, hasBaseDoc := baseAPIDoc[apiId]
|
|
if hasBaseDoc != hasDC || hasBaseProxy != hasPc {
|
|
// 文档或者proxy变更
|
|
a.Change = service_diff.ChangeTypeUpdate
|
|
} else if (hasPc && pc.UUID != baseProxy.UUID) || (hasDC && dc.UUID != baseDoc.UUID) {
|
|
// 文档 或者 proxy 变更
|
|
a.Change = service_diff.ChangeTypeUpdate
|
|
}
|
|
}
|
|
out.Apis = append(out.Apis, a)
|
|
|
|
}
|
|
baseApis.Remove(utils.SliceToSlice(out.Apis, func(i *service_diff.ApiDiff) string {
|
|
return i.APi
|
|
})...)
|
|
for _, rc := range base.apiRequestCommits {
|
|
apiInfo := rc.Data
|
|
if baseApis.Has(rc.Target) {
|
|
out.Apis = append(out.Apis, &service_diff.ApiDiff{
|
|
Name: apiInfo.Name,
|
|
APi: rc.Target,
|
|
Method: apiInfo.Methods,
|
|
Protocol: apiInfo.Protocols,
|
|
Disable: apiInfo.Disable,
|
|
Path: apiInfo.Path,
|
|
Change: service_diff.ChangeTypeDelete,
|
|
Status: service_diff.Status{},
|
|
})
|
|
}
|
|
|
|
}
|
|
// upstream diff
|
|
targetUpstreamMap := utils.SliceToMap(target.upstreamCommits, func(i *commit.Commit[upstream.Config]) string {
|
|
return fmt.Sprintf("%s-%s", i.Target, i.Key)
|
|
})
|
|
baseUpstreamMap := utils.SliceToMap(base.upstreamCommits, func(i *commit.Commit[upstream.Config]) string {
|
|
return fmt.Sprintf("%s-%s", i.Target, i.Key)
|
|
})
|
|
|
|
for _, partitionId := range partitions {
|
|
key := fmt.Sprintf("%s-%s", target.id, partitionId)
|
|
o := &service_diff.UpstreamDiff{
|
|
Upstream: target.id,
|
|
Data: nil,
|
|
Change: service_diff.ChangeTypeNone,
|
|
Status: 0,
|
|
}
|
|
out.Upstreams = append(out.Upstreams, o)
|
|
bu, hasBu := baseUpstreamMap[key]
|
|
tu, hasTu := targetUpstreamMap[key]
|
|
if hasTu {
|
|
o.Data = tu.Data
|
|
if !hasBu {
|
|
o.Change = service_diff.ChangeTypeNew
|
|
} else if tu.UUID != bu.UUID {
|
|
o.Change = service_diff.ChangeTypeUpdate
|
|
}
|
|
|
|
} else {
|
|
o.Status = service_diff.StatusLoss
|
|
if hasBu {
|
|
o.Change = service_diff.ChangeTypeDelete
|
|
}
|
|
}
|
|
}
|
|
out.Strategies = m.diffStrategies(base.strategyCommits, target.strategyCommits)
|
|
|
|
return out
|
|
}
|
|
|
|
func (m *imlServiceDiff) Out(ctx context.Context, diff *service_diff.Diff) (*DiffOut, error) {
|
|
|
|
clusters, err := m.clusterService.List(ctx, diff.Clusters...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if len(clusters) == 0 {
|
|
return nil, fmt.Errorf("unset gateway for clusters %v", diff.Clusters)
|
|
}
|
|
|
|
out := &DiffOut{}
|
|
out.Routers = utils.SliceToSlice(diff.Apis, func(i *service_diff.ApiDiff) *RouterDiffOut {
|
|
return &RouterDiffOut{
|
|
Name: i.Name,
|
|
Methods: i.Method,
|
|
Path: i.Path,
|
|
Change: i.Change,
|
|
Status: i.Status,
|
|
Protocols: i.Protocol,
|
|
Disable: i.Disable,
|
|
}
|
|
})
|
|
|
|
for _, u := range diff.Upstreams {
|
|
if u.Data == nil {
|
|
continue
|
|
}
|
|
typeValue := u.Data.Type
|
|
|
|
if typeValue == "" {
|
|
typeValue = "static"
|
|
}
|
|
out.Upstreams = append(out.Upstreams, &UpstreamDiffOut{
|
|
Change: u.Change,
|
|
Type: typeValue,
|
|
Status: u.Status,
|
|
Addr: utils.SliceToSlice(u.Data.Nodes, func(i *upstream.NodeConfig) string {
|
|
return i.Address
|
|
}),
|
|
})
|
|
}
|
|
out.Strategies = utils.SliceToSlice(diff.Strategies, func(i *service_diff.StrategyDiff) *StrategyDiffOut {
|
|
return &StrategyDiffOut{
|
|
Name: i.Name,
|
|
Priority: i.Priority,
|
|
Change: i.Change,
|
|
Status: i.Status,
|
|
}
|
|
})
|
|
return out, nil
|
|
}
|