From e1fbdb0a78197bad844148e49f2c077c794dbecf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B1=88=E8=BD=A9?= Date: Thu, 22 Dec 2022 18:42:13 +0800 Subject: [PATCH] fix(region): tags for project mapping (#15605) --- pkg/apis/compute/cloudaccount.go | 11 +++ pkg/compute/models/cloudaccounts.go | 15 +++- pkg/compute/models/cloudproviders.go | 86 ++++++++++++++++--- pkg/compute/models/cloudsync.go | 2 +- pkg/compute/models/external_projects.go | 23 +++++ .../models/project_mapping_resource.go | 21 ++++- pkg/mcclient/options/cloudaccounts.go | 8 +- 7 files changed, 148 insertions(+), 18 deletions(-) diff --git a/pkg/apis/compute/cloudaccount.go b/pkg/apis/compute/cloudaccount.go index fe906b44ec..4791a23504 100644 --- a/pkg/apis/compute/cloudaccount.go +++ b/pkg/apis/compute/cloudaccount.go @@ -206,6 +206,14 @@ type CloudaccountCreateInput struct { SubAccounts *cloudprovider.SubAccounts ReadOnly bool `json:"read_only"` + + SProjectMappingResourceInput +} + +type SProjectMappingResourceInput struct { + ProjectMappingId string `json:"project_mapping_id"` + EnableProjectSync bool `json:"enable_project_sync"` + EnableResourceSync bool `json:"enable_resource_sync"` } type CloudaccountShareModeInput struct { @@ -505,6 +513,9 @@ type CloudaccountProjectMappingInput struct { // 同步策略Id, 若不传此参数则解绑 // 绑定同步策略要求当前云账号此刻未绑定其他同步策略 ProjectMappingId string `json:"project_mapping_id"` + + EnableProjectSync *bool `json:"enable_project_sync"` + EnableResourceSync *bool `json:"enable_resource_sync"` } type SyncRangeInput struct { diff --git a/pkg/compute/models/cloudaccounts.go b/pkg/compute/models/cloudaccounts.go index bd5c8d8613..c1d1878137 100644 --- a/pkg/compute/models/cloudaccounts.go +++ b/pkg/compute/models/cloudaccounts.go @@ -354,6 +354,11 @@ func (manager *SCloudaccountManager) ValidateCreateData( return input, err } + input.SProjectMappingResourceInput, err = manager.SProjectMappingResourceBaseManager.ValidateCreateData(ctx, userCred, ownerId, query, input.SProjectMappingResourceInput) + if err != nil { + return input, err + } + input.EnabledStatusInfrasResourceBaseCreateInput, err = manager.SEnabledStatusInfrasResourceBaseManager.ValidateCreateData(ctx, userCred, ownerId, query, input.EnabledStatusInfrasResourceBaseCreateInput) if err != nil { return input, errors.Wrap(err, "SEnabledStatusInfrasResourceBaseManager.ValidateCreateData") @@ -2833,12 +2838,14 @@ func (self *SCloudaccount) PerformProjectMapping(ctx context.Context, userCred m return nil, httperrors.NewInputParameterError("account %s has aleady bind project mapping %s", self.Name, self.ProjectMappingId) } } - // no changes - if self.ProjectMappingId == input.ProjectMappingId { - return nil, nil - } _, err := db.Update(self, func() error { self.ProjectMappingId = input.ProjectMappingId + if input.EnableProjectSync != nil { + self.EnableProjectSync = tristate.NewFromBool(*input.EnableProjectSync) + } + if input.EnableResourceSync != nil { + self.EnableResourceSync = tristate.NewFromBool(*input.EnableResourceSync) + } return nil }) if err != nil { diff --git a/pkg/compute/models/cloudproviders.go b/pkg/compute/models/cloudproviders.go index a591edf58a..63572eee47 100644 --- a/pkg/compute/models/cloudproviders.go +++ b/pkg/compute/models/cloudproviders.go @@ -119,20 +119,49 @@ type pmCache struct { CloudaccountId string AccountProjectMappingId string ManagerProjectMappingId string + + AccountEnableProjectSync bool + ManagerEnableProjectSync bool + + AccountEnableResourceSync bool + ManagerEnableResourceSync bool } -func (self *pmCache) GetProjectMapping() (*SProjectMapping, error) { +type sProjectMapping struct { + *SProjectMapping + EnableProjectSync bool + EnableResourceSync bool +} + +func (self *sProjectMapping) IsNeedResourceSync() bool { + return self.EnableResourceSync || !self.EnableProjectSync +} + +func (self *sProjectMapping) IsNeedProjectSync() bool { + return self.EnableProjectSync +} + +func (self *pmCache) GetProjectMapping() (*sProjectMapping, error) { if len(self.ManagerProjectMappingId) > 0 { pm, err := GetRuleMapping(self.ManagerProjectMappingId) if err != nil { return nil, errors.Wrapf(err, "GetRuleMapping(%s)", self.ManagerProjectMappingId) } - if pm.Enabled.IsTrue() { - return pm, nil + ret := &sProjectMapping{ + SProjectMapping: pm, + EnableProjectSync: self.ManagerEnableProjectSync, + EnableResourceSync: self.ManagerEnableResourceSync, } + return ret, nil } if len(self.AccountProjectMappingId) > 0 { - return GetRuleMapping(self.AccountProjectMappingId) + ret := &sProjectMapping{ + EnableProjectSync: self.AccountEnableProjectSync, + EnableResourceSync: self.AccountEnableResourceSync, + } + var err error + ret.SProjectMapping, err = GetRuleMapping(self.AccountProjectMappingId) + return ret, err } return nil, errors.Wrapf(cloudprovider.ErrNotFound, "empty project mapping id") } @@ -141,9 +170,18 @@ var pmCaches map[string]*pmCache = map[string]*pmCache{} func refreshPmCaches() error { q := CloudproviderManager.Query().SubQuery() - providers := q.Query(q.Field("cloudaccount_id"), q.Field("id"), q.Field("project_mapping_id").Label("manager_project_mapping_id")) + providers := q.Query( + q.Field("cloudaccount_id"), + q.Field("id"), + q.Field("project_mapping_id").Label("manager_project_mapping_id"), + q.Field("enable_project_sync").Label("manager_enable_project_sync"), + q.Field("enable_resource_sync").Label("manager_enable_resource_sync"), + ) sq := CloudaccountManager.Query().SubQuery() - mq := providers.LeftJoin(sq, sqlchemy.Equals(q.Field("cloudaccount_id"), sq.Field("id"))).AppendField(sq.Field("project_mapping_id").Label("account_project_mapping_id")) + mq := providers.LeftJoin(sq, sqlchemy.Equals(q.Field("cloudaccount_id"), sq.Field("id"))). + AppendField(sq.Field("project_mapping_id").Label("account_project_mapping_id")). + AppendField(sq.Field("enable_project_sync").Label("account_enable_project_sync")). + AppendField(sq.Field("enable_resource_sync").Label("account_enable_resource_sync")) caches := []pmCache{} err := mq.All(&caches) if err != nil { @@ -155,7 +193,31 @@ func refreshPmCaches() error { return nil } -func (self *SCloudprovider) GetProjectMapping() (*SProjectMapping, error) { +func (self *SCloudaccount) GetProjectMapping() (*sProjectMapping, error) { + cache, err := func() (*pmCache, error) { + for id := range pmCaches { + if pmCaches[id].CloudaccountId == self.Id { + return pmCaches[id], nil + } + } + err := refreshPmCaches() + if err != nil { + return nil, errors.Wrapf(err, "refreshPmCaches") + } + for id := range pmCaches { + if pmCaches[id].CloudaccountId == self.Id { + return pmCaches[id], nil + } + } + return nil, cloudprovider.ErrNotFound + }() + if err != nil { + return nil, errors.Wrapf(err, "get project mapping cache") + } + return cache.GetProjectMapping() +} + +func (self *SCloudprovider) GetProjectMapping() (*sProjectMapping, error) { cache, err := func() (*pmCache, error) { mp, ok := pmCaches[self.Id] if ok { @@ -1959,12 +2021,14 @@ func (self *SCloudprovider) PerformProjectMapping(ctx context.Context, userCred return nil, httperrors.NewInputParameterError("cloudprovider %s has aleady bind project mapping %s", self.Name, self.ProjectMappingId) } } - // no changes - if self.ProjectMappingId == input.ProjectMappingId { - return nil, nil - } _, err := db.Update(self, func() error { self.ProjectMappingId = input.ProjectMappingId + if input.EnableProjectSync != nil { + self.EnableProjectSync = tristate.NewFromBool(*input.EnableProjectSync) + } + if input.EnableResourceSync != nil { + self.EnableResourceSync = tristate.NewFromBool(*input.EnableResourceSync) + } return nil }) if err != nil { diff --git a/pkg/compute/models/cloudsync.go b/pkg/compute/models/cloudsync.go index 2fbcdce638..dabd9946da 100644 --- a/pkg/compute/models/cloudsync.go +++ b/pkg/compute/models/cloudsync.go @@ -2156,7 +2156,7 @@ func SyncCloudProject(userCred mcclient.TokenCredential, model db.IVirtualModel, if err != nil { return nil, errors.Wrapf(err, "GetCloudaccount") } - if rm != nil && rm.Enabled.Bool() { + if rm != nil && rm.Enabled.Bool() && rm.IsNeedResourceSync() { extTags, err := extModel.GetTags() if err != nil { return nil, errors.Wrapf(err, "extModel.GetTags") diff --git a/pkg/compute/models/external_projects.go b/pkg/compute/models/external_projects.go index ae6ea0b66f..d0c0fb3160 100644 --- a/pkg/compute/models/external_projects.go +++ b/pkg/compute/models/external_projects.go @@ -391,9 +391,32 @@ func (manager *SExternalProjectManager) newFromCloudProject(ctx context.Context, project.DomainId = domainId } } + pm, _ := account.GetProjectMapping() if localProject != nil { project.DomainId = localProject.DomainId project.ProjectId = localProject.Id + } else if pm != nil && pm.Enabled.IsTrue() && pm.IsNeedProjectSync() { + extTags, err := extProject.GetTags() + if err != nil { + return nil, errors.Wrapf(err, "extModel.GetTags") + } + if pm.Rules != nil { + for _, rule := range *pm.Rules { + domainId, projectId, newProj, isMatch := rule.IsMatchTags(extTags) + if isMatch && len(newProj) > 0 { + domainId, projectId, err = account.getOrCreateTenant(context.TODO(), newProj, "", "", "auto create from tag") + if err != nil { + log.Errorf("getOrCreateTenant(%s) error: %v", newProj, err) + continue + } + if len(domainId) > 0 && len(projectId) > 0 { + project.DomainId = domainId + project.ProjectId = projectId + break + } + } + } + } } else if account.AutoCreateProject { desc := fmt.Sprintf("auto create from cloud project %s (%s)", project.Name, project.ExternalId) domainId, projectId, err := account.getOrCreateTenant(ctx, project.Name, project.DomainId, "", desc) diff --git a/pkg/compute/models/project_mapping_resource.go b/pkg/compute/models/project_mapping_resource.go index 6330b9782a..8142379958 100644 --- a/pkg/compute/models/project_mapping_resource.go +++ b/pkg/compute/models/project_mapping_resource.go @@ -20,6 +20,7 @@ import ( "yunion.io/x/jsonutils" "yunion.io/x/log" "yunion.io/x/pkg/errors" + "yunion.io/x/pkg/tristate" "yunion.io/x/pkg/util/reflectutils" "yunion.io/x/sqlchemy" @@ -32,11 +33,29 @@ import ( ) type SProjectMappingResourceBase struct { - ProjectMappingId string `width:"36" charset:"ascii" nullable:"false" create:"optional" index:"true" list:"user" json:"project_mapping_id"` + ProjectMappingId string `width:"36" charset:"ascii" nullable:"true" create:"optional" index:"true" list:"user" json:"project_mapping_id"` + EnableProjectSync tristate.TriState `default:"false" list:"user" create:"optional"` + EnableResourceSync tristate.TriState `default:"true" list:"user" create:"optional"` } type SProjectMappingResourceBaseManager struct{} +func (manager *SProjectMappingResourceBaseManager) ValidateCreateData( + ctx context.Context, + userCred mcclient.TokenCredential, + ownerId mcclient.IIdentityProvider, + query jsonutils.JSONObject, + input api.SProjectMappingResourceInput, +) (api.SProjectMappingResourceInput, error) { + if len(input.ProjectMappingId) > 0 { + _, err := validators.ValidateModel(userCred, ProjectMappingManager, &input.ProjectMappingId) + if err != nil { + return input, err + } + } + return input, nil +} + func (self *SProjectMappingResourceBase) GetProjectMapping() (*SProjectMapping, error) { pm, err := ProjectMappingManager.FetchById(self.ProjectMappingId) if err != nil { diff --git a/pkg/mcclient/options/cloudaccounts.go b/pkg/mcclient/options/cloudaccounts.go index 66f5f2e6f9..f5438f11cd 100644 --- a/pkg/mcclient/options/cloudaccounts.go +++ b/pkg/mcclient/options/cloudaccounts.go @@ -130,6 +130,10 @@ type SCloudAccountCreateBaseOptions struct { DryRun bool `help:"test create cloudaccount params"` ShowSubAccounts bool `help:"test and show subaccount info"` ReadOnly bool `help:"Read only account"` + + ProjectMappingId string + EnableProjectSync bool + EnableResourceSync bool } type SVMwareCloudAccountCreateOptions struct { @@ -1116,7 +1120,9 @@ func (opts *SubscriptionCreateOptions) Params() (jsonutils.JSONObject, error) { type ClouaccountProjectMappingOptions struct { SCloudAccountIdOptions - ProjectMappingId string `json:"project_mapping_id" help:"project mapping id"` + ProjectMappingId string `json:"project_mapping_id" help:"project mapping id"` + EnableProjectSync bool + EnableResourceSync bool } func (opts *ClouaccountProjectMappingOptions) Params() (jsonutils.JSONObject, error) {