Skip to content

Commit

Permalink
Merge remote-tracking branch 'woa-bk-bcs/master'
Browse files Browse the repository at this point in the history
* woa-bk-bcs/master:
  --story=119821235 [集群管理] Azure系统盘支持 50 - 2048GB大小 (merge request !2035)
  fix: import template file (merge request !2034)
  --story=119603556 视图管理自定义视图前端路由 (merge request !2031)
  --story=119489982 【GitOps】脏处理:通过判断应用及应用组的特征,避免一次 Git 提交在多来源场景下出发 2 次同步 (merge request !2033)
  --story=1070014198119023225 【Gitops】增加对应用的禁止同步 (merge request !2027)
  --story=119110055 集群操作记录支持搜索 (merge request !2026)
  --story=115284411 【节点列表】暂无数据的提示应该在可视区居中?目前看来是视作内容区的一部分进行的居中? (merge request !2030)
  --story=119762027 删除日志采集列表提示 (merge request !2032)
  • Loading branch information
evanlixin committed Sep 23, 2024
2 parents 28a3fc4 + f572262 commit e98282e
Show file tree
Hide file tree
Showing 22 changed files with 322 additions and 118 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import (

"github.com/Tencent/bk-bcs/bcs-scenarios/bcs-gitops-manager/cmd/gitgenerator-webhook/options"
"github.com/Tencent/bk-bcs/bcs-scenarios/bcs-gitops-manager/internal/dao"
"github.com/Tencent/bk-bcs/bcs-scenarios/bcs-gitops-manager/pkg/common"
"github.com/Tencent/bk-bcs/bcs-scenarios/bcs-gitops-manager/pkg/store"
)

Expand Down Expand Up @@ -160,6 +161,12 @@ func (s *AdmissionWebhookServer) check(ctx *gin.Context) {
return
}
case v1.Update:
if err := s.interceptAppSyncWithForbid(ctx, req); err != nil {
blog.Errorf("UID: %s, intercept application sync with forbidden flag failed: %s",
req.UID, err.Error())
s.webhookAllow(ctx, false, req.UID, err.Error())
return
}
if err := s.interceptApplicationSync(ctx, req); err != nil {
blog.Errorf("UID: %s, intercept application sync failed: %s", req.UID, err.Error())
s.webhookAllow(ctx, false, req.UID, err.Error())
Expand Down Expand Up @@ -207,31 +214,8 @@ func (s *AdmissionWebhookServer) checkApplication(ctx context.Context, bs []byte
if argoProj == nil {
return errors.Errorf("project '%s' not exist", proj)
}

// check app whether belong to appset
var repoBelong bool
var repoProj string
if app.Spec.HasMultipleSources() {
for i := range app.Spec.Sources {
appSource := app.Spec.Sources[i]
repoUrl := appSource.RepoURL
repoProj, repoBelong, err = s.checkRepositoryBelongProject(ctx, repoUrl, proj)
if err != nil {
return errors.Wrapf(err, "check repo '%s' belong to project '%s' failed", repoUrl, repoProj)
}
if !repoBelong {
return errors.Errorf("repo '%s' project is '%s', not same as '%s'", repoUrl, repoProj, proj)
}
}
} else {
repoUrl := app.Spec.Source.RepoURL
repoProj, repoBelong, err = s.checkRepositoryBelongProject(ctx, repoUrl, proj)
if err != nil {
return errors.Wrapf(err, "check repo '%s' belong to project '%s' failed", repoUrl, repoProj)
}
if !repoBelong {
return errors.Errorf("repo '%s' project is '%s', not same as '%s'", repoUrl, repoProj, proj)
}
if err = s.checkAppRepoBelongProject(ctx, proj, app); err != nil {
return err
}

// check dest server is legal
Expand Down Expand Up @@ -266,6 +250,32 @@ func (s *AdmissionWebhookServer) checkApplication(ctx context.Context, bs []byte
return nil
}

func (s *AdmissionWebhookServer) checkAppRepoBelongProject(ctx context.Context, proj string,
app *v1alpha1.Application) error {
if !app.Spec.HasMultipleSources() {
repoUrl := app.Spec.Source.RepoURL
repoProj, repoBelong, err := s.checkRepositoryBelongProject(ctx, repoUrl, proj)
if err != nil {
return errors.Wrapf(err, "check repo '%s' belong to project '%s' failed", repoUrl, repoProj)
}
if !repoBelong {
return errors.Errorf("repo '%s' project is '%s', not same as '%s'", repoUrl, repoProj, proj)
}
}
for i := range app.Spec.Sources {
appSource := app.Spec.Sources[i]
repoUrl := appSource.RepoURL
repoProj, repoBelong, err := s.checkRepositoryBelongProject(ctx, repoUrl, proj)
if err != nil {
return errors.Wrapf(err, "check repo '%s' belong to project '%s' failed", repoUrl, repoProj)
}
if !repoBelong {
return errors.Errorf("repo '%s' project is '%s', not same as '%s'", repoUrl, repoProj, proj)
}
}
return nil
}

func (s *AdmissionWebhookServer) checkRepositoryBelongProject(ctx context.Context, repoUrl,
project string) (string, bool, error) {
repo, err := s.argoStore.GetRepository(ctx, repoUrl)
Expand All @@ -282,10 +292,18 @@ func (s *AdmissionWebhookServer) checkRepositoryBelongProject(ctx context.Contex
return repo.Project, belong, nil
}

func (s *AdmissionWebhookServer) needInterceptAppSync(req *v1.AdmissionRequest) *v1alpha1.Application {
func (s *AdmissionWebhookServer) unmarshalAppFromReq(req *v1.AdmissionRequest) (*v1alpha1.Application, error) {
app := new(v1alpha1.Application)
if err := json.Unmarshal(req.Object.Raw, app); err != nil {
blog.Errorf("unmarshal application failed: %s", err.Error())
return nil, errors.Wrap(err, "unmarshal application failed")
}
return app, nil
}

func (s *AdmissionWebhookServer) needInterceptAppSync(req *v1.AdmissionRequest) *v1alpha1.Application {
app, err := s.unmarshalAppFromReq(req)
if err != nil {
blog.Errorf("not need intercept because unmarshal app error")
return nil
}
if len(s.cfg.InterceptSyncProjects) == 0 {
Expand All @@ -294,13 +312,13 @@ func (s *AdmissionWebhookServer) needInterceptAppSync(req *v1.AdmissionRequest)
if !slices.Contains(s.cfg.InterceptSyncProjects, app.Spec.Project) {
return nil
}
blog.Infof("Received request. UID: %s, Name: %s, Operation: %s, Kind: %v.", req.UID, req.Name,
req.Operation, req.Kind)
state := app.Status.OperationState
// 如果应用状态非 Running, 则表示不在 Sync 进程中,直接返回
if state == nil || state.Phase != synccommon.OperationRunning {
return nil
}
blog.Infof("Received request(intercept sync). UID: %s, Name: %s, Operation: %s, Kind: %v.", req.UID, req.Name,
req.Operation, req.Kind)
return app
}

Expand Down Expand Up @@ -331,6 +349,38 @@ func (s *AdmissionWebhookServer) checkApplicationBelongAppSet(ctx context.Contex
return appSet
}

func (s *AdmissionWebhookServer) interceptAppSyncWithForbid(ctx context.Context,
req *v1.AdmissionRequest) error {
// 如果设置了禁止同步标记,就直接拒绝同步
reqApp, err := s.unmarshalAppFromReq(req)
if err != nil {
blog.Errorf("not need intercept with forbidden flag because unmarshal application request error")
return nil
}
state := reqApp.Status.OperationState
if state == nil || state.Phase != synccommon.OperationRunning {
return nil
}
app, err := s.argoStore.GetApplication(ctx, req.Name)
if err != nil {
blog.Errorf("not need intercept with forbidden flag because get application error")
return nil
}
// 如果要更新common.ApplicationSyncForbidden就不拒绝
if _, reqExistForbidden := reqApp.Annotations[common.ApplicationSyncForbidden]; !reqExistForbidden {
return nil
}
_, existForbidden := app.Annotations[common.ApplicationSyncForbidden]
if existForbidden {
return errors.Errorf(
`reject the app_sync, because current application.metadata.annotations has %s,
req annotations is '%v'`,
common.ApplicationSyncForbidden, reqApp.Annotations)
}
return nil
}

// interceptApplicationSync 用于处理appset中使用动态targetrevision引起产生两次同步的缺陷
func (s *AdmissionWebhookServer) interceptApplicationSync(ctx context.Context, req *v1.AdmissionRequest) error {
app := s.needInterceptAppSync(req)
if app == nil {
Expand All @@ -354,13 +404,14 @@ func (s *AdmissionWebhookServer) interceptApplicationSync(ctx context.Context, r
hasDynamicRevision = true
}
if !hasDynamicRevision {
blog.Infof("intercept application '%s' sync, it's appset '%s' not have dynamic revision",
blog.Infof("skip intercept application '%s' sync, it's appset '%s' not have dynamic revision",
app.Name, appSet.Name)
return nil
}
blog.Infof("intercept application '%s' sync, it's appset '%s' has dynamic revision", app.Name, appSet.Name)
apps, err := s.argoStore.ApplicationSetDryRun(appSet)
if err != nil {
blog.Errorf("intercept application '%s' sync, it's appset '%s' dry-run failed: %s",
blog.Errorf("skip intercept application '%s' sync, it's appset '%s' dry-run failed: %s",
app.Name, appSet.Name, err.Error())
return nil
}
Expand All @@ -373,9 +424,11 @@ func (s *AdmissionWebhookServer) interceptApplicationSync(ctx context.Context, r
break
}
if foundApp == nil {
blog.Warnf("intercept application '%s' sync, it's appset '%s' dry-run not have app", app.Name, appSet.Name)
blog.Warnf("skip intercept application '%s' sync, it's appset '%s' dry-run not have app",
app.Name, appSet.Name)
return nil
}
blog.Infof("intercept application '%s' sync, found the application after appset dry-run", app.Name)

// 对比 AppSet dry-run 出来的应用和当前应用的 revision 是否一致
if foundApp.Spec.HasMultipleSources() {
Expand All @@ -397,7 +450,7 @@ func (s *AdmissionWebhookServer) interceptApplicationSync(ctx context.Context, r
foundApp.Spec.Source.TargetRevision)
}
}
blog.Infof("intercept application '%s' sync, the revision generate by appset is same", app.Name)
blog.Infof("skip intercept application '%s' sync, the revision generate by appset is same", app.Name)
return nil
}

Expand Down
3 changes: 3 additions & 0 deletions bcs-scenarios/bcs-gitops-manager/pkg/common/const.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ const (
// ApplicationCollectAnnotation defines the application whether collected
ApplicationCollectAnnotation = "bkbcs.tencent.com/application_collect"

// ApplicationSyncForbidden if this annotation exists, then the app should be disabled from synchronizing
ApplicationSyncForbidden = "bkbcs.tencent.com/application_sync_forbidden"

// ClusterAliaName defines the alia's name for project
ClusterAliaName = "bkbcs.tencent.com/clusterAliaName"
// ClusterEnv defines the cluster env
Expand Down
76 changes: 58 additions & 18 deletions bcs-scenarios/bcs-gitops-manager/pkg/proxy/argocd/application.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import (
"github.com/pkg/errors"

"github.com/Tencent/bk-bcs/bcs-scenarios/bcs-gitops-manager/internal/dao"
"github.com/Tencent/bk-bcs/bcs-scenarios/bcs-gitops-manager/pkg/common"
mw "github.com/Tencent/bk-bcs/bcs-scenarios/bcs-gitops-manager/pkg/proxy/argocd/middleware"
"github.com/Tencent/bk-bcs/bcs-scenarios/bcs-gitops-manager/pkg/proxy/argocd/middleware/ctxutils"
"github.com/Tencent/bk-bcs/bcs-scenarios/bcs-gitops-manager/pkg/proxy/argocd/permitcheck"
Expand Down Expand Up @@ -129,6 +130,8 @@ func (plugin *AppPlugin) Init() error {
Handler(plugin.middleware.HttpWrapper(plugin.applicationWorkloadReplicasZero))
appRouter.Path("/sync_refresh").Methods("GET").
Handler(plugin.middleware.HttpWrapper(plugin.syncRefresh))
appRouter.Path("/forbid_sync").Methods("POST").
Handler(plugin.middleware.HttpWrapper(plugin.forbidAppSync))

appRouter.PathPrefix("").Methods("PUT", "POST", "DELETE", "PATCH").
Handler(plugin.middleware.HttpWrapper(plugin.applicationEditHandler))
Expand Down Expand Up @@ -417,13 +420,32 @@ func (plugin *AppPlugin) customRevisionsMetadata(r *http.Request) (*http.Request
fmt.Errorf("query parameter 'revisions' has empty value"))
}
}
repos, err := plugin.checkRepos(argoApp, revisions)
if err != nil {
return r, mw.ReturnErrorResponse(http.StatusBadRequest, err)
}

revisionsMetadata, err := plugin.storage.GetApplicationRevisionsMetadata(r.Context(), repos, revisions)
if err != nil {
return r, mw.ReturnErrorResponse(http.StatusInternalServerError,
fmt.Errorf("get application revisions metadata failed: %s", err.Error()))
}
return r, mw.ReturnJSONResponse(&ApplicationRevisionsMetadata{
Code: 0,
RequestID: ctxutils.RequestID(r.Context()),
Data: revisionsMetadata,
})
}

func (plugin *AppPlugin) checkRepos(argoApp *v1alpha1.Application, revisions []string) ([]string, error) {
repos := make([]string, 0)
if argoApp.Spec.HasMultipleSources() && len(argoApp.Spec.Sources) == len(revisions) {
for _, source := range argoApp.Spec.Sources {
repos = append(repos, source.RepoURL)
}
} else if argoApp.Spec.HasMultipleSources() && len(argoApp.Spec.Sources) != len(revisions) {
return repos, nil
}
if argoApp.Spec.HasMultipleSources() && len(argoApp.Spec.Sources) != len(revisions) {
// 兼容应用从 SingleSource 与 MultipleSource 互相转换的情况
found := false
for i := range argoApp.Status.History {
Expand All @@ -449,26 +471,16 @@ func (plugin *AppPlugin) customRevisionsMetadata(r *http.Request) (*http.Request
}
}
if !found {
return r, mw.ReturnErrorResponse(http.StatusBadRequest, fmt.Errorf("application has multiple(%d) "+
"sources, not same as query param 'revisions'", len(argoApp.Spec.Sources)))
}
} else {
if len(revisions) != 1 {
return r, mw.ReturnErrorResponse(http.StatusBadRequest, fmt.Errorf("application has single source"))
return nil, fmt.Errorf("application has multiple(%d) "+"sources, not same as query param 'revisions'",
len(argoApp.Spec.Sources))
}
repos = append(repos, argoApp.Spec.Source.RepoURL)
return repos, nil
}

revisionsMetadata, err := plugin.storage.GetApplicationRevisionsMetadata(r.Context(), repos, revisions)
if err != nil {
return r, mw.ReturnErrorResponse(http.StatusInternalServerError,
fmt.Errorf("get application revisions metadata failed: %s", err.Error()))
if len(revisions) != 1 {
return nil, fmt.Errorf("application has single source")
}
return r, mw.ReturnJSONResponse(&ApplicationRevisionsMetadata{
Code: 0,
RequestID: ctxutils.RequestID(r.Context()),
Data: revisionsMetadata,
})
repos = append(repos, argoApp.Spec.Source.RepoURL)
return repos, nil
}

type parameterAction string
Expand Down Expand Up @@ -813,3 +825,31 @@ func filterAppsByTargetRevision(appList *v1alpha1.ApplicationList, target string
}
appList.Items = filterApps
}

func (plugin *AppPlugin) forbidAppSync(r *http.Request) (*http.Request, *mw.HttpResponse) {
appName := mux.Vars(r)["name"]
if appName == "" {
return r, mw.ReturnErrorResponse(http.StatusBadRequest,
fmt.Errorf("request application name cannot be empty"))
}
forbid := r.URL.Query()["forbid"]
if len(forbid) == 0 {
return r, mw.ReturnErrorResponse(http.StatusBadRequest,
fmt.Errorf("param forbid must not empty"))
}
app, err := plugin.storage.GetApplication(r.Context(), appName)
if err != nil {
return r, mw.ReturnErrorResponse(http.StatusInternalServerError, err)
}
annotations := make(map[string]interface{})
if forbid[0] == "false" {
annotations[common.ApplicationSyncForbidden] = nil
} else {
annotations[common.ApplicationSyncForbidden] = "true"
}
if err := plugin.storage.PatchApplicationAnnotation(
r.Context(), app.Name, app.Namespace, annotations); err != nil {
return r, mw.ReturnErrorResponse(http.StatusInternalServerError, err)
}
return r, mw.ReturnJSONResponse(map[string]string{"forbid": forbid[0]})
}
Loading

0 comments on commit e98282e

Please sign in to comment.