From c6a474e036499f3cf944164c7220eeaadeece841 Mon Sep 17 00:00:00 2001 From: Qu Xuan Date: Wed, 1 Apr 2020 15:13:01 +0800 Subject: [PATCH] fix: support azure policy --- cmd/climc/shell/policy_assignment.go | 59 ++++ cmd/climc/shell/policy_definition.go | 62 ++++ pkg/apis/compute/policy_assignment.go | 42 +++ pkg/apis/compute/policy_definition.go | 62 ++++ pkg/apis/compute/zz_generated.model.go | 25 ++ pkg/cloudprovider/cloudprovider.go | 5 + pkg/cloudprovider/resources.go | 8 + pkg/compute/models/guests.go | 71 +++++ pkg/compute/models/policy_assignment.go | 130 ++++++++ pkg/compute/models/policy_definition.go | 294 ++++++++++++++++++ .../models/policy_definition_resource.go | 70 +++++ pkg/compute/models/purge.go | 51 +++ pkg/compute/policy/resources.go | 2 + pkg/compute/service/handlers.go | 2 + .../tasks/cloud_provider_sync_info_task.go | 32 +- .../policy_definition_sync_status_task.go | 75 +++++ pkg/httperrors/consts.go | 4 + pkg/httperrors/errors.go | 4 + .../modules/mod_policy_assignments.go | 33 ++ .../modules/mod_policy_definitions.go | 33 ++ pkg/multicloud/azure/azure.go | 3 + pkg/multicloud/azure/policy.go | 250 +++++++++++++++ pkg/multicloud/azure/provider/provider.go | 4 + pkg/multicloud/azure/shell/policy.go | 73 +++++ 24 files changed, 1379 insertions(+), 15 deletions(-) create mode 100644 cmd/climc/shell/policy_assignment.go create mode 100644 cmd/climc/shell/policy_definition.go create mode 100644 pkg/apis/compute/policy_assignment.go create mode 100644 pkg/apis/compute/policy_definition.go create mode 100644 pkg/compute/models/policy_assignment.go create mode 100644 pkg/compute/models/policy_definition.go create mode 100644 pkg/compute/models/policy_definition_resource.go create mode 100644 pkg/compute/tasks/policy_definition_sync_status_task.go create mode 100644 pkg/mcclient/modules/mod_policy_assignments.go create mode 100644 pkg/mcclient/modules/mod_policy_definitions.go create mode 100644 pkg/multicloud/azure/policy.go create mode 100644 pkg/multicloud/azure/shell/policy.go diff --git a/cmd/climc/shell/policy_assignment.go b/cmd/climc/shell/policy_assignment.go new file mode 100644 index 0000000000..5304ede61d --- /dev/null +++ b/cmd/climc/shell/policy_assignment.go @@ -0,0 +1,59 @@ +// Copyright 2019 Yunion +// +// 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 shell + +import ( + "yunion.io/x/jsonutils" + + "yunion.io/x/onecloud/pkg/mcclient" + "yunion.io/x/onecloud/pkg/mcclient/modules" + "yunion.io/x/onecloud/pkg/mcclient/options" +) + +func init() { + type PolicyListOptions struct { + options.BaseListOptions + Policydefinition string `help:"filter by policydefinition"` + } + R(&PolicyListOptions{}, "policy-assignment-list", "List policy assignments", func(s *mcclient.ClientSession, args *PolicyListOptions) error { + params, err := args.Params() + if err != nil { + return err + } + if len(args.Policydefinition) > 0 { + params.Add(jsonutils.NewString(args.Policydefinition), "policydefinition") + } + result, err := modules.PolicyAssignment.List(s, params) + if err != nil { + return err + } + printList(result, modules.PolicyAssignment.GetColumns(s)) + return nil + }) + + type PolicyIdOptions struct { + ID string `help:"policy assignment id or name"` + } + + R(&PolicyIdOptions{}, "policy-assignment-show", "Show policy assignment details", func(s *mcclient.ClientSession, args *PolicyIdOptions) error { + result, err := modules.PolicyAssignment.Get(s, args.ID, nil) + if err != nil { + return err + } + printObject(result) + return nil + + }) +} diff --git a/cmd/climc/shell/policy_definition.go b/cmd/climc/shell/policy_definition.go new file mode 100644 index 0000000000..58970e3cde --- /dev/null +++ b/cmd/climc/shell/policy_definition.go @@ -0,0 +1,62 @@ +// Copyright 2019 Yunion +// +// 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 shell + +import ( + "yunion.io/x/onecloud/pkg/mcclient" + "yunion.io/x/onecloud/pkg/mcclient/modules" + "yunion.io/x/onecloud/pkg/mcclient/options" +) + +func init() { + type PolicyListOptions struct { + options.BaseListOptions + } + R(&PolicyListOptions{}, "policy-definition-list", "List policy definitions", func(s *mcclient.ClientSession, args *PolicyListOptions) error { + params, err := args.Params() + if err != nil { + return err + } + result, err := modules.PolicyDefinition.List(s, params) + if err != nil { + return err + } + printList(result, modules.PolicyDefinition.GetColumns(s)) + return nil + }) + + type PolicyIdOptions struct { + ID string `help:"policy definition name or id"` + } + + R(&PolicyIdOptions{}, "policy-definition-show", "Show policy definition", func(s *mcclient.ClientSession, args *PolicyIdOptions) error { + result, err := modules.PolicyDefinition.Get(s, args.ID, nil) + if err != nil { + return err + } + printObject(result) + return nil + }) + + R(&PolicyIdOptions{}, "policy-definition-syncstatus", "Sync policy definition status", func(s *mcclient.ClientSession, args *PolicyIdOptions) error { + result, err := modules.PolicyDefinition.PerformAction(s, args.ID, "syncstatus", nil) + if err != nil { + return err + } + printObject(result) + return nil + }) + +} diff --git a/pkg/apis/compute/policy_assignment.go b/pkg/apis/compute/policy_assignment.go new file mode 100644 index 0000000000..d822709630 --- /dev/null +++ b/pkg/apis/compute/policy_assignment.go @@ -0,0 +1,42 @@ +// Copyright 2019 Yunion +// +// 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 compute + +import "yunion.io/x/onecloud/pkg/apis" + +type PolicyDefinitionResourceListInput struct { + Policydefinition string +} + +type PolicyAssignmentListInput struct { + apis.DomainLevelResourceListInput + ManagedResourceListInput + + PolicyDefinitionResourceListInput +} + +type PolicyAssignmentCreateInput struct { + SPolicyAssignment +} + +type SPolicyDefinitionDetails struct { +} + +type PolicyAssignmentDetails struct { + apis.DomainLevelResourceDetails + PolicyDefinitionResourceInfo + + SPolicyAssignment +} diff --git a/pkg/apis/compute/policy_definition.go b/pkg/apis/compute/policy_definition.go new file mode 100644 index 0000000000..094ef99d1a --- /dev/null +++ b/pkg/apis/compute/policy_definition.go @@ -0,0 +1,62 @@ +// Copyright 2019 Yunion +// +// 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 compute + +import "yunion.io/x/onecloud/pkg/apis" + +const ( + POLICY_DEFINITION_STATUS_READY = "ready" + POLICY_DEFINITION_STATUS_UNKNOWN = "unknown" + + POLICY_DEFINITION_CATEGORY_CLOUDREGION = "cloudregion" + POLICY_DEFINITION_CATEGORY_TAG = "tag" + + POLICY_DEFINITION_CONDITION_IN = "in" + POLICY_DEFINITION_CONDITION_NOT_IN = "not_in" + POLICY_DEFINITION_CONDITION_CONTAINS = "contains" + POLICY_DEFINITION_CONDITION_EXCEPT = "except" +) + +type PolicyDefinitionListInput struct { + apis.StatusStandaloneResourceListInput + ManagedResourceListInput +} + +type PolicyDefinitionCreateInput struct { + apis.StatusStandaloneResourceCreateInput + SPolicyDefinition +} + +type PolicyDefinitionDetails struct { + apis.StatusStandaloneResourceDetails + + SPolicyDefinition +} + +type PolicyDefinitionResourceInfo struct { + Policydefinition string +} + +type SCloudregionPolicyDefinition struct { + Id string + Name string +} + +type SCloudregionPolicyDefinitions struct { + Cloudregions []SCloudregionPolicyDefinition +} + +type PolicyDefinitionSyncstatusInput struct { +} diff --git a/pkg/apis/compute/zz_generated.model.go b/pkg/apis/compute/zz_generated.model.go index 64cf7552d0..e00bac475c 100644 --- a/pkg/apis/compute/zz_generated.model.go +++ b/pkg/apis/compute/zz_generated.model.go @@ -1580,6 +1580,31 @@ type SNetworkschedtag struct { NetworkId string `json:"network_id"` } +// SPolicyAssignment is an autogenerated struct via yunion.io/x/onecloud/pkg/compute/models.SPolicyAssignment. +type SPolicyAssignment struct { + apis.SDomainLevelResourceBase + SPolicyDefinitionResourceBase +} + +// SPolicyDefinition is an autogenerated struct via yunion.io/x/onecloud/pkg/compute/models.SPolicyDefinition. +type SPolicyDefinition struct { + apis.SStatusStandaloneResourceBase + apis.SExternalizedResourceBase + SManagedResourceBase + // 参数 + Parameters interface{} `json:"parameters"` + // 条件 + Condition string `json:"condition"` + // 类别 + Category string `json:"category"` +} + +// SPolicyDefinitionResourceBase is an autogenerated struct via yunion.io/x/onecloud/pkg/compute/models.SPolicyDefinitionResourceBase. +type SPolicyDefinitionResourceBase struct { + // 策略Id + PolicydefinitionId string `json:"policydefinition_id"` +} + // SQcloudCachedLb is an autogenerated struct via yunion.io/x/onecloud/pkg/compute/models.SQcloudCachedLb. type SQcloudCachedLb struct { apis.SVirtualResourceBase diff --git a/pkg/cloudprovider/cloudprovider.go b/pkg/cloudprovider/cloudprovider.go index a5811bd9e5..ff493c9512 100644 --- a/pkg/cloudprovider/cloudprovider.go +++ b/pkg/cloudprovider/cloudprovider.go @@ -203,6 +203,7 @@ type ICloudProvider interface { GetCapabilities() []string GetICloudQuotas() ([]ICloudQuota, error) + GetICloudPolicyDefinitions() ([]ICloudPolicyDefinition, error) } func IsSupportProject(prod ICloudProvider) bool { @@ -306,6 +307,10 @@ func (self *SBaseProvider) GetICloudQuotas() ([]ICloudQuota, error) { return nil, ErrNotImplemented } +func (self *SBaseProvider) GetICloudPolicyDefinitions() ([]ICloudPolicyDefinition, error) { + return nil, ErrNotImplemented +} + func (self *SBaseProvider) GetCloudRegionExternalIdPrefix() string { return self.factory.GetId() } diff --git a/pkg/cloudprovider/resources.go b/pkg/cloudprovider/resources.go index 0ea8e5ffb9..44e82a8c5d 100644 --- a/pkg/cloudprovider/resources.go +++ b/pkg/cloudprovider/resources.go @@ -950,3 +950,11 @@ type ICloudQuota interface { GetMaxQuotaCount() int GetCurrentQuotaUsedCount() int } + +type ICloudPolicyDefinition interface { + GetGlobalId() string + GetName() string + GetCategory() string + GetCondition() string + GetParameters() *jsonutils.JSONDict +} diff --git a/pkg/compute/models/guests.go b/pkg/compute/models/guests.go index 286d34c8c8..a655699e4d 100644 --- a/pkg/compute/models/guests.go +++ b/pkg/compute/models/guests.go @@ -1340,11 +1340,82 @@ func (manager *SGuestManager) validateCreateData( return nil, httperrors.NewInputParameterError("Invalid userdata: %v", err) } + err = manager.ValidatePolicyDefinitions(ctx, userCred, ownerId, query, input) + if err != nil { + return nil, err + } + input.Project = ownerId.GetProjectId() input.ProjectDomain = ownerId.GetProjectDomainId() return input, nil } +func (manager *SGuestManager) ValidatePolicyDefinitions(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, input *api.ServerCreateInput) error { + definitions, err := PolicyDefinitionManager.GetAvailablePolicyDefinitions(ctx, userCred) + if err != nil { + return httperrors.NewGeneralError(err) + } + for i := range definitions { + switch definitions[i].Category { + case api.POLICY_DEFINITION_CATEGORY_CLOUDREGION: + if len(input.PreferRegion) == 0 { + return httperrors.NewMissingParameterError(fmt.Sprintf("policy definition %s require prefer_region_id parameter", definitions[i].Name)) + } + if definitions[i].Parameters == nil { + return httperrors.NewPolicyDefinitionError("invalid parameters for policy definition %s", definitions[i].Name) + } + regionDefinitions := api.SCloudregionPolicyDefinitions{} + definitions[i].Parameters.Unmarshal(®ionDefinitions) + regions := []string{} + for _, region := range regionDefinitions.Cloudregions { + regions = append(regions, region.Id) + regions = append(regions, region.Name) + } + isIn := utils.IsInStringArray(input.PreferRegion, regions) + switch definitions[i].Condition { + case api.POLICY_DEFINITION_CONDITION_IN: + if !isIn { + return httperrors.NewPolicyDefinitionError("policy definition %s require cloudregion in %s", definitions[i].Name, definitions[i].Parameters) + } + case api.POLICY_DEFINITION_CONDITION_NOT_IN: + if isIn { + return httperrors.NewPolicyDefinitionError("policy definition %s require cloudregion not in %s", definitions[i].Name, definitions[i].Parameters) + } + default: + return httperrors.NewPolicyDefinitionError("invalid policy definition %s(%s) condition %s", definitions[i].Name, definitions[i].Id, definitions[i].Condition) + } + case api.POLICY_DEFINITION_CATEGORY_TAG: + tags := []string{} + if definitions[i].Parameters == nil { + return httperrors.NewPolicyDefinitionError("invalid parameters for policy definition %s", definitions[i].Name) + } + definitions[i].Parameters.Unmarshal(&tags, "tags") + metadataKeys := []string{} + for k, _ := range input.Metadata { + metadataKeys = append(metadataKeys, strings.TrimPrefix(k, db.USER_TAG_PREFIX)) + } + for _, tag := range tags { + isIn := utils.IsInStringArray(tag, metadataKeys) + switch definitions[i].Condition { + case api.POLICY_DEFINITION_CONDITION_CONTAINS: + if !isIn { + return httperrors.NewPolicyDefinitionError("policy definition %s require must contains tag %s", definitions[i].Name, tag) + } + case api.POLICY_DEFINITION_CONDITION_EXCEPT: + if isIn { + return httperrors.NewPolicyDefinitionError("policy definition %s require except tag %s", definitions[i].Name, tag) + } + default: + return httperrors.NewPolicyDefinitionError("invalid policy definition %s(%s) condition %s", definitions[i].Name, definitions[i].Id, definitions[i].Condition) + } + } + default: + return httperrors.NewPolicyDefinitionError("invalid category %s for policy definition %s(%s)", definitions[i].Category, definitions[i].Name, definitions[i].Id) + } + } + return nil +} + func (manager *SGuestManager) BatchCreateValidateCreateData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, data *jsonutils.JSONDict) (*jsonutils.JSONDict, error) { input, err := manager.validateCreateData(ctx, userCred, ownerId, query, data) if err != nil { diff --git a/pkg/compute/models/policy_assignment.go b/pkg/compute/models/policy_assignment.go new file mode 100644 index 0000000000..3ab62d8ec1 --- /dev/null +++ b/pkg/compute/models/policy_assignment.go @@ -0,0 +1,130 @@ +// Copyright 2019 Yunion +// +// 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 models + +import ( + "context" + "fmt" + + "yunion.io/x/jsonutils" + "yunion.io/x/pkg/errors" + "yunion.io/x/sqlchemy" + + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/httperrors" + "yunion.io/x/onecloud/pkg/mcclient" + "yunion.io/x/onecloud/pkg/util/stringutils2" +) + +type SPolicyAssignmentManager struct { + db.SDomainLevelResourceBaseManager + SPolicyDefinitionResourceBaseManager +} + +var PolicyAssignmentManager *SPolicyAssignmentManager + +func init() { + PolicyAssignmentManager = &SPolicyAssignmentManager{ + SDomainLevelResourceBaseManager: db.NewDomainLevelResourceBaseManager( + SPolicyAssignment{}, + "policy_assignments_tbl", + "policy_assignment", + "policy_assignments", + ), + } + PolicyAssignmentManager.SetVirtualObject(PolicyAssignmentManager) +} + +type SPolicyAssignment struct { + db.SDomainLevelResourceBase + + SPolicyDefinitionResourceBase +} + +// 策略分配列表 +func (manager *SPolicyAssignmentManager) ListItemFilter(ctx context.Context, q *sqlchemy.SQuery, userCred mcclient.TokenCredential, query api.PolicyAssignmentListInput) (*sqlchemy.SQuery, error) { + var err error + q, err = manager.SDomainLevelResourceBaseManager.ListItemFilter(ctx, q, userCred, query.DomainLevelResourceListInput) + if err != nil { + return nil, err + } + + q, err = manager.SPolicyDefinitionResourceBaseManager.ListItemFilter(ctx, q, userCred, query.PolicyDefinitionResourceListInput) + if err != nil { + return nil, err + } + + return q, nil +} + +func (manager *SPolicyAssignmentManager) FetchCustomizeColumns(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, objs []interface{}, fields stringutils2.SSortedStrings, isList bool) []api.PolicyAssignmentDetails { + rows := make([]api.PolicyAssignmentDetails, len(objs)) + domainRows := manager.SDomainLevelResourceBaseManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList) + definitionRows := manager.SPolicyDefinitionResourceBaseManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList) + for i := range rows { + rows[i] = api.PolicyAssignmentDetails{ + DomainLevelResourceDetails: domainRows[i], + PolicyDefinitionResourceInfo: definitionRows[i], + } + } + return rows +} + +func (manager *SPolicyAssignmentManager) OrderByExtraFields(ctx context.Context, q *sqlchemy.SQuery, userCred mcclient.TokenCredential, query api.PolicyAssignmentListInput) (*sqlchemy.SQuery, error) { + q, err := manager.SStandaloneResourceBaseManager.OrderByExtraFields(ctx, q, userCred, query.StandaloneResourceListInput) + if err != nil { + return nil, errors.Wrap(err, "SStandaloneResourceBaseManager.OrderByExtraFields") + } + + return q, nil +} + +func (manager *SPolicyAssignmentManager) ValidateCreateData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, input api.PolicyAssignmentCreateInput) (api.PolicyAssignmentCreateInput, error) { + return input, httperrors.NewInputParameterError("not support create") +} + +func (manager *SPolicyAssignmentManager) QueryDistinctExtraField(q *sqlchemy.SQuery, field string) (*sqlchemy.SQuery, error) { + var err error + q, err = manager.SDomainLevelResourceBaseManager.QueryDistinctExtraField(q, field) + if err == nil { + return q, nil + } + + return q, httperrors.ErrNotFound +} + +func (manager *SPolicyAssignmentManager) checkAndSetAssignment(definition *SPolicyDefinition, domainId string) error { + q := manager.Query().Equals("policydefinition_id", definition.Id).Equals("domain_id", domainId) + count, err := q.CountWithError() + if err != nil { + return errors.Wrap(err, "CountWithError") + } + if count == 0 { + return manager.newAssignment(definition, domainId) + } + return nil +} + +func (manager *SPolicyAssignmentManager) newAssignment(definition *SPolicyDefinition, domainId string) error { + assignment := SPolicyAssignment{} + assignment.SetModelManager(manager, &assignment) + + assignment.Name = fmt.Sprintf("assignment for %s domain %s", definition.Name, domainId) + assignment.DomainId = domainId + assignment.PolicydefinitionId = definition.Id + + return manager.TableSpec().Insert(&assignment) +} diff --git a/pkg/compute/models/policy_definition.go b/pkg/compute/models/policy_definition.go new file mode 100644 index 0000000000..4664b9f31c --- /dev/null +++ b/pkg/compute/models/policy_definition.go @@ -0,0 +1,294 @@ +// Copyright 2019 Yunion +// +// 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 models + +import ( + "context" + "fmt" + + "yunion.io/x/jsonutils" + "yunion.io/x/pkg/errors" + "yunion.io/x/pkg/util/compare" + "yunion.io/x/pkg/utils" + "yunion.io/x/sqlchemy" + + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/cloudcommon/db/lockman" + "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/httperrors" + "yunion.io/x/onecloud/pkg/mcclient" + "yunion.io/x/onecloud/pkg/util/stringutils2" +) + +type SPolicyDefinitionManager struct { + db.SStatusStandaloneResourceBaseManager + db.SExternalizedResourceBaseManager + SManagedResourceBaseManager +} + +var PolicyDefinitionManager *SPolicyDefinitionManager + +func init() { + PolicyDefinitionManager = &SPolicyDefinitionManager{ + SStatusStandaloneResourceBaseManager: db.NewStatusStandaloneResourceBaseManager( + SPolicyDefinition{}, + "policy_definitions_tbl", + "policy_definition", + "policy_definitions", + ), + } + PolicyDefinitionManager.SetVirtualObject(PolicyDefinitionManager) +} + +type SPolicyDefinition struct { + db.SStatusStandaloneResourceBase + db.SExternalizedResourceBase + SManagedResourceBase + + // 参数 + Parameters *jsonutils.JSONDict `get:"domain" list:"domain" create:"admin_optional"` + + // 条件 + Condition string `width:"32" charset:"ascii" nullable:"false" get:"domain" list:"domain" create:"required"` + // 类别 + Category string `width:"16" charset:"ascii" nullable:"false" get:"domain" list:"domain" create:"required"` +} + +// 策略列表 +func (manager *SPolicyDefinitionManager) ListItemFilter(ctx context.Context, q *sqlchemy.SQuery, userCred mcclient.TokenCredential, query api.PolicyDefinitionListInput) (*sqlchemy.SQuery, error) { + var err error + q, err = manager.SStatusStandaloneResourceBaseManager.ListItemFilter(ctx, q, userCred, query.StatusStandaloneResourceListInput) + if err != nil { + return nil, err + } + + q, err = manager.SManagedResourceBaseManager.ListItemFilter(ctx, q, userCred, query.ManagedResourceListInput) + if err != nil { + return nil, err + } + + return q, nil +} + +func (manager *SPolicyDefinitionManager) ValidateCreateData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, input api.PolicyDefinitionCreateInput) (api.PolicyDefinitionCreateInput, error) { + return input, httperrors.NewUnsupportOperationError("not support create definition") +} + +func (manager *SPolicyDefinitionManager) OrderByExtraFields(ctx context.Context, q *sqlchemy.SQuery, userCred mcclient.TokenCredential, query api.PolicyDefinitionListInput) (*sqlchemy.SQuery, error) { + q, err := manager.SStatusStandaloneResourceBaseManager.OrderByExtraFields(ctx, q, userCred, query.StatusStandaloneResourceListInput) + if err != nil { + return nil, errors.Wrap(err, "SStatusStandaloneResourceBaseManager.OrderByExtraFields") + } + + return q, nil +} + +func (manager *SPolicyDefinitionManager) QueryDistinctExtraField(q *sqlchemy.SQuery, field string) (*sqlchemy.SQuery, error) { + var err error + + q, err = manager.SStatusStandaloneResourceBaseManager.QueryDistinctExtraField(q, field) + if err == nil { + return q, nil + } + q, err = manager.SManagedResourceBaseManager.QueryDistinctExtraField(q, field) + if err == nil { + return q, nil + } + + return q, httperrors.ErrNotFound +} + +func (self *SPolicyDefinition) GetExtraDetails(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, isList bool) (api.PolicyDefinitionDetails, error) { + return api.PolicyDefinitionDetails{}, nil +} + +func (manager *SPolicyDefinitionManager) FetchCustomizeColumns(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, objs []interface{}, fields stringutils2.SSortedStrings, isList bool) []api.PolicyDefinitionDetails { + rows := make([]api.PolicyDefinitionDetails, len(objs)) + statusRows := manager.SStatusStandaloneResourceBaseManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList) + for i := range rows { + rows[i] = api.PolicyDefinitionDetails{ + StatusStandaloneResourceDetails: statusRows[i], + } + } + return rows +} + +func (manager *SPolicyDefinitionManager) getPolicyDefinitionsByManagerId(providerId string) ([]SPolicyDefinition, error) { + definitions := []SPolicyDefinition{} + err := fetchByManagerId(manager, providerId, &definitions) + if err != nil { + return nil, errors.Wrap(err, "fetchByManagerId") + } + return definitions, nil +} + +func (manager *SPolicyDefinitionManager) GetAvailablePolicyDefinitions(ctx context.Context, userCred mcclient.TokenCredential) ([]SPolicyDefinition, error) { + q := manager.Query() + sq := PolicyAssignmentManager.Query().SubQuery() + q = q.Join(sq, sqlchemy.Equals(q.Field("id"), sq.Field("policydefinition_id"))).Filter( + sqlchemy.Equals(sq.Field("domain_id"), userCred.GetDomainId()), + ).Equals("status", api.POLICY_DEFINITION_STATUS_READY) + definitions := []SPolicyDefinition{} + err := db.FetchModelObjects(manager, q, &definitions) + if err != nil { + return nil, errors.Wrap(err, "db.FetchModelObjects") + } + return definitions, nil +} + +func (manager *SPolicyDefinitionManager) SyncPolicyDefinitions(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, provider *SCloudprovider, iDefinitions []cloudprovider.ICloudPolicyDefinition) compare.SyncResult { + lockman.LockClass(ctx, manager, db.GetLockClassKey(manager, userCred)) + defer lockman.ReleaseClass(ctx, manager, db.GetLockClassKey(manager, userCred)) + + syncResult := compare.SyncResult{} + + dbDefinitions, err := manager.getPolicyDefinitionsByManagerId(provider.Id) + if err != nil { + syncResult.Error(err) + return syncResult + } + + removed := make([]SPolicyDefinition, 0) + commondb := make([]SPolicyDefinition, 0) + commonext := make([]cloudprovider.ICloudPolicyDefinition, 0) + added := make([]cloudprovider.ICloudPolicyDefinition, 0) + + err = compare.CompareSets(dbDefinitions, iDefinitions, &removed, &commondb, &commonext, &added) + if err != nil { + syncResult.Error(err) + return syncResult + } + + for i := 0; i < len(removed); i += 1 { + err = removed[i].purge(ctx, userCred) + if err != nil { + syncResult.DeleteError(err) + continue + } + syncResult.Delete() + } + for i := 0; i < len(commondb); i += 1 { + err = commondb[i].SyncWithCloudPolicyDefinition(ctx, userCred, provider, commonext[i]) + if err != nil { + syncResult.UpdateError(err) + continue + } + syncResult.Update() + } + for i := 0; i < len(added); i += 1 { + err = manager.newFromCloudPolicyDefinition(ctx, userCred, added[i], provider) + if err != nil { + syncResult.AddError(err) + continue + } + syncResult.Add() + } + return syncResult +} + +func (self *SPolicyDefinition) constructParameters(ctx context.Context, userCred mcclient.TokenCredential, extDefinition cloudprovider.ICloudPolicyDefinition) error { + self.Category = extDefinition.GetCategory() + self.Condition = extDefinition.GetCondition() + switch self.Category { + case api.POLICY_DEFINITION_CATEGORY_CLOUDREGION: + if !utils.IsInStringArray(self.Condition, []string{api.POLICY_DEFINITION_CONDITION_NOT_IN, api.POLICY_DEFINITION_CONDITION_IN}) { + return fmt.Errorf("not support category %s condition %s", self.Category, self.Condition) + } + parameters := extDefinition.GetParameters() + if parameters == nil { + return fmt.Errorf("invalid parameters") + } + cloudregions := []string{} + err := parameters.Unmarshal(&cloudregions, "cloudregions") + if err != nil { + return errors.Wrap(err, "parameters.Unmarshal") + } + regions := api.SCloudregionPolicyDefinitions{Cloudregions: []api.SCloudregionPolicyDefinition{}} + for _, cloudregion := range cloudregions { + region, err := db.FetchByExternalId(CloudregionManager, cloudregion) + if err != nil { + return errors.Wrapf(err, "db.FetchByExternalId(%s)", cloudregion) + } + regionPolicyDefinition := api.SCloudregionPolicyDefinition{ + Id: region.GetId(), + Name: region.GetName(), + } + regions.Cloudregions = append(regions.Cloudregions, regionPolicyDefinition) + } + self.Parameters = jsonutils.Marshal(regions).(*jsonutils.JSONDict) + case api.POLICY_DEFINITION_CATEGORY_TAG: + self.Parameters = extDefinition.GetParameters() + default: + return fmt.Errorf("not support category %s", self.Category) + } + self.Status = api.POLICY_DEFINITION_STATUS_READY + return nil +} + +func (manager *SPolicyDefinitionManager) newFromCloudPolicyDefinition(ctx context.Context, userCred mcclient.TokenCredential, extDefinition cloudprovider.ICloudPolicyDefinition, provider *SCloudprovider) error { + definition := SPolicyDefinition{} + definition.SetModelManager(manager, &definition) + + newName, err := db.GenerateName(manager, userCred, extDefinition.GetName()) + if err != nil { + return errors.Wrap(err, "db.GenerateName") + } + + definition.Name = newName + definition.ManagerId = provider.Id + definition.Status = api.POLICY_DEFINITION_STATUS_READY + definition.ExternalId = extDefinition.GetGlobalId() + definition.constructParameters(ctx, userCred, extDefinition) + + err = manager.TableSpec().Insert(&definition) + if err != nil { + return errors.Wrap(err, "Insert") + } + + return PolicyAssignmentManager.newAssignment(&definition, provider.DomainId) +} + +func (self *SPolicyDefinition) GetPolicyAssignments() ([]SPolicyAssignment, error) { + assignments := []SPolicyAssignment{} + q := PolicyAssignmentManager.Query().Equals("policydefinition_id", self.Id) + err := db.FetchModelObjects(PolicyAssignmentManager, q, &assignments) + if err != nil { + return nil, errors.Wrap(err, "db.FetchModelObjects") + } + return assignments, nil +} + +func (self *SPolicyDefinition) SyncWithCloudPolicyDefinition(ctx context.Context, userCred mcclient.TokenCredential, provider *SCloudprovider, extDefinition cloudprovider.ICloudPolicyDefinition) error { + _, err := db.UpdateWithLock(ctx, self, func() error { + return self.constructParameters(ctx, userCred, extDefinition) + }) + if err != nil { + return errors.Wrap(err, "db.UpdateWithLock") + } + return PolicyAssignmentManager.checkAndSetAssignment(self, provider.DomainId) +} + +func (self *SPolicyDefinition) AllowPerformSyncstatus(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { + return userCred.HasSystemAdminPrivilege() +} + +// 同步策略状态 +func (self *SPolicyDefinition) PerformSyncstatus(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.PolicyDefinitionSyncstatusInput) (jsonutils.JSONObject, error) { + if len(self.ManagerId) == 0 { + return nil, nil + } + return nil, StartResourceSyncStatusTask(ctx, userCred, self, "PolicyDefinitionSyncstatusTask", "") +} diff --git a/pkg/compute/models/policy_definition_resource.go b/pkg/compute/models/policy_definition_resource.go new file mode 100644 index 0000000000..7d1b93eadc --- /dev/null +++ b/pkg/compute/models/policy_definition_resource.go @@ -0,0 +1,70 @@ +// Copyright 2019 Yunion +// +// 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 models + +import ( + "context" + "database/sql" + + "yunion.io/x/jsonutils" + "yunion.io/x/pkg/errors" + "yunion.io/x/sqlchemy" + + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/httperrors" + "yunion.io/x/onecloud/pkg/mcclient" + "yunion.io/x/onecloud/pkg/util/stringutils2" +) + +type SPolicyDefinitionResourceBase struct { + // 策略Id + PolicydefinitionId string `width:"36" charset:"ascii" nullable:"false" list:"user" create:"required"` +} + +type SPolicyDefinitionResourceBaseManager struct { +} + +func (manager *SPolicyDefinitionResourceBaseManager) ListItemFilter(ctx context.Context, q *sqlchemy.SQuery, userCred mcclient.TokenCredential, query api.PolicyDefinitionResourceListInput) (*sqlchemy.SQuery, error) { + if len(query.Policydefinition) > 0 { + definition, err := PolicyDefinitionManager.FetchByIdOrName(userCred, query.Policydefinition) + if err != nil { + if errors.Cause(err) != sql.ErrNoRows { + return nil, httperrors.NewGeneralError(err) + } + return nil, httperrors.NewResourceNotFoundError2("policy_definition", query.Policydefinition) + } + q = q.Equals("policydefinition_id", definition.GetId()) + } + return q, nil +} + +func (manager *SPolicyDefinitionResourceBaseManager) FetchCustomizeColumns(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, objs []interface{}, fields stringutils2.SSortedStrings, isList bool, +) []api.PolicyDefinitionResourceInfo { + rows := make([]api.PolicyDefinitionResourceInfo, len(objs)) + definitionIds := make([]string, len(objs)) + for i := range objs { + definitionIds[i] = objs[i].(*SPolicyAssignment).PolicydefinitionId + } + + idMaps, err := db.FetchIdNameMap2(PolicyDefinitionManager, definitionIds) + if err != nil { + return rows + } + for i := range objs { + rows[i].Policydefinition, _ = idMaps[definitionIds[i]] + } + return rows +} diff --git a/pkg/compute/models/purge.go b/pkg/compute/models/purge.go index 4e4b732b06..4faf524ca3 100644 --- a/pkg/compute/models/purge.go +++ b/pkg/compute/models/purge.go @@ -1657,3 +1657,54 @@ func (manager *SCloudproviderQuotaManager) purgeAll(ctx context.Context, userCre } return nil } + +func (assignment *SPolicyAssignment) purge(ctx context.Context, userCred mcclient.TokenCredential) error { + lockman.LockObject(ctx, assignment) + defer lockman.ReleaseObject(ctx, assignment) + + err := assignment.ValidateDeleteCondition(ctx) + if err != nil { + return errors.Wrapf(err, "assignment.ValidateDeleteCondition(%s(%s))", assignment.Name, assignment.Id) + } + + return assignment.Delete(ctx, userCred) +} + +func (definition *SPolicyDefinition) purge(ctx context.Context, userCred mcclient.TokenCredential) error { + lockman.LockObject(ctx, definition) + defer lockman.ReleaseObject(ctx, definition) + + assignments, err := definition.GetPolicyAssignments() + if err != nil { + return errors.Wrap(err, "definition.GetPolicyAssignments") + } + + for i := range assignments { + err = assignments[i].purge(ctx, userCred) + if err != nil { + return err + } + } + + err = definition.ValidateDeleteCondition(ctx) + if err != nil { + return err + } + + return definition.Delete(ctx, userCred) +} + +func (manager *SPolicyDefinitionManager) purgeAll(ctx context.Context, userCred mcclient.TokenCredential, providerId string) error { + definitions := []SPolicyDefinition{} + err := fetchByManagerId(manager, providerId, &definitions) + if err != nil { + return err + } + for i := range definitions { + err := definitions[i].purge(ctx, userCred) + if err != nil { + return err + } + } + return nil +} diff --git a/pkg/compute/policy/resources.go b/pkg/compute/policy/resources.go index 9359632cf4..afa39a4321 100644 --- a/pkg/compute/policy/resources.go +++ b/pkg/compute/policy/resources.go @@ -34,6 +34,7 @@ var ( "loadbalanceragents", "isolated-devices", "reservedips", + "policy_definitions", } computeDomainResources = []string{ "cloudaccounts", @@ -50,6 +51,7 @@ var ( "natgateways", "natsentries", "natdentries", + "policy_assignments", } computeUserResources = []string{ "keypairs", diff --git a/pkg/compute/service/handlers.go b/pkg/compute/service/handlers.go index ad5e39836d..4c954c645d 100644 --- a/pkg/compute/service/handlers.go +++ b/pkg/compute/service/handlers.go @@ -179,6 +179,8 @@ func InitHandlers(app *appsrv.Application) { models.ScalingGroupManager, models.ScalingPolicyManager, models.ScalingActivityManager, + models.PolicyDefinitionManager, + models.PolicyAssignmentManager, } { db.RegisterModelManager(manager) handler := db.NewModelHandler(manager) diff --git a/pkg/compute/tasks/cloud_provider_sync_info_task.go b/pkg/compute/tasks/cloud_provider_sync_info_task.go index 516a162ab4..028686adaf 100644 --- a/pkg/compute/tasks/cloud_provider_sync_info_task.go +++ b/pkg/compute/tasks/cloud_provider_sync_info_task.go @@ -26,7 +26,6 @@ import ( "yunion.io/x/onecloud/pkg/appsrv" "yunion.io/x/onecloud/pkg/cloudcommon/db" "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" - "yunion.io/x/onecloud/pkg/cloudprovider" "yunion.io/x/onecloud/pkg/compute/models" "yunion.io/x/onecloud/pkg/util/logclient" ) @@ -77,7 +76,7 @@ func (self *CloudProviderSyncInfoTask) GetSyncRange() models.SSyncRange { func (self *CloudProviderSyncInfoTask) OnInit(ctx context.Context, obj db.IStandaloneModel, body jsonutils.JSONObject) { provider := obj.(*models.SCloudprovider) - self.SetStage("OnSyncCloudProviderQuotaInfoComplete", nil) + self.SetStage("OnSyncCloudProviderPreInfoComplete", nil) taskman.LocalTaskRun(self, func() (jsonutils.JSONObject, error) { p, err := provider.GetProvider() @@ -85,22 +84,25 @@ func (self *CloudProviderSyncInfoTask) OnInit(ctx context.Context, obj db.IStand return nil, errors.Wrap(err, "GetProvider") } quotas, err := p.GetICloudQuotas() - if err != nil { - if errors.Cause(err) != cloudprovider.ErrNotImplemented { - return nil, errors.Wrap(err, "GetICloudQuotas") - } - return nil, nil + if err == nil { + result := models.CloudproviderQuotaManager.SyncQuotas(ctx, self.GetUserCred(), provider.GetOwnerId(), provider, nil, api.CLOUD_PROVIDER_QUOTA_RANGE_CLOUDPROVIDER, quotas) + msg := result.Result() + notes := fmt.Sprintf("SyncQuotas for provider %s result: %s", provider.Name, msg) + log.Infof(notes) + } + + policyDefinitions, err := p.GetICloudPolicyDefinitions() + if err == nil { + result := models.PolicyDefinitionManager.SyncPolicyDefinitions(ctx, self.GetUserCred(), provider.GetOwnerId(), provider, policyDefinitions) + msg := result.Result() + notes := fmt.Sprintf("SyncPolicyDefinitions for provider %s result: %s", provider.Name, msg) + log.Infof(notes) } - result := models.CloudproviderQuotaManager.SyncQuotas(ctx, self.GetUserCred(), provider.GetOwnerId(), provider, nil, api.CLOUD_PROVIDER_QUOTA_RANGE_CLOUDPROVIDER, quotas) - msg := result.Result() - notes := fmt.Sprintf("SyncQuotas for provider %s result: %s", provider.Name, msg) - log.Infof(notes) return nil, nil }) - } -func (self *CloudProviderSyncInfoTask) OnSyncCloudProviderQuotaInfoComplete(ctx context.Context, obj db.IStandaloneModel, body jsonutils.JSONObject) { +func (self *CloudProviderSyncInfoTask) OnSyncCloudProviderPreInfoComplete(ctx context.Context, obj db.IStandaloneModel, body jsonutils.JSONObject) { provider := obj.(*models.SCloudprovider) syncRange := self.GetSyncRange() @@ -113,9 +115,9 @@ func (self *CloudProviderSyncInfoTask) OnSyncCloudProviderQuotaInfoComplete(ctx }) } -func (self *CloudProviderSyncInfoTask) OnSyncCloudProviderQuotaInfoCompleteFailed(ctx context.Context, obj db.IStandaloneModel, body jsonutils.JSONObject) { +func (self *CloudProviderSyncInfoTask) OnSyncCloudProviderPreInfoCompleteFailed(ctx context.Context, obj db.IStandaloneModel, body jsonutils.JSONObject) { log.Errorf("faild to sync provider quotas %s", body.String()) - self.OnSyncCloudProviderQuotaInfoComplete(ctx, obj, body) + self.OnSyncCloudProviderPreInfoComplete(ctx, obj, body) } func (self *CloudProviderSyncInfoTask) OnSyncCloudProviderInfoComplete(ctx context.Context, obj db.IStandaloneModel, body jsonutils.JSONObject) { diff --git a/pkg/compute/tasks/policy_definition_sync_status_task.go b/pkg/compute/tasks/policy_definition_sync_status_task.go new file mode 100644 index 0000000000..c46f36707e --- /dev/null +++ b/pkg/compute/tasks/policy_definition_sync_status_task.go @@ -0,0 +1,75 @@ +// Copyright 2019 Yunion +// +// 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 tasks + +import ( + "context" + "fmt" + + "yunion.io/x/jsonutils" + "yunion.io/x/pkg/errors" + + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" + "yunion.io/x/onecloud/pkg/compute/models" + "yunion.io/x/onecloud/pkg/util/logclient" +) + +type PolicyDefinitionSyncstatusTask struct { + taskman.STask +} + +func init() { + taskman.RegisterTask(PolicyDefinitionSyncstatusTask{}) +} + +func (self *PolicyDefinitionSyncstatusTask) taskFailed(ctx context.Context, definition *models.SPolicyDefinition, err error) { + definition.SetStatus(self.UserCred, api.POLICY_DEFINITION_STATUS_UNKNOWN, err.Error()) + db.OpsLog.LogEvent(definition, db.ACT_SYNC_STATUS, err.Error(), self.GetUserCred()) + logclient.AddActionLogWithStartable(self, definition, logclient.ACT_SYNC_STATUS, err.Error(), self.UserCred, false) + self.SetStageFailed(ctx, err.Error()) +} + +func (self *PolicyDefinitionSyncstatusTask) OnInit(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) { + definition := obj.(*models.SPolicyDefinition) + cloudprovider := definition.GetCloudprovider() + if cloudprovider == nil { + self.taskFailed(ctx, definition, fmt.Errorf("failed to get cloudprovider for policy definition %s", definition.Name)) + return + } + provider, err := cloudprovider.GetProvider() + if err != nil { + self.taskFailed(ctx, definition, errors.Wrap(err, "GetProvider")) + return + } + policyDefinitions, err := provider.GetICloudPolicyDefinitions() + if err != nil { + self.taskFailed(ctx, definition, errors.Wrap(err, "GetICloudPolicyDefinitions")) + return + } + for i := range policyDefinitions { + if policyDefinitions[i].GetGlobalId() == definition.ExternalId { + err = definition.SyncWithCloudPolicyDefinition(ctx, self.GetUserCred(), cloudprovider, policyDefinitions[i]) + if err != nil { + self.taskFailed(ctx, definition, errors.Wrap(err, "SyncWithCloudPolicyDefinition")) + return + } + self.SetStageComplete(ctx, nil) + return + } + } + self.taskFailed(ctx, definition, fmt.Errorf("failed to found policy definition %s from cloud", definition.Name)) +} diff --git a/pkg/httperrors/consts.go b/pkg/httperrors/consts.go index fa6f70115d..0911b8b6d7 100644 --- a/pkg/httperrors/consts.go +++ b/pkg/httperrors/consts.go @@ -83,6 +83,8 @@ const ( ErrTooManyAttempts = errors.Error("TooManyFailedAttempts") ErrTooManyRequests = errors.Error("TooManyRequests") + + ErrPolicyDefinition = errors.Error("PolicyDefinitionError") ) var ( @@ -155,6 +157,8 @@ var ( ErrTooManyAttempts: 429, ErrTooManyRequests: 429, + + ErrPolicyDefinition: 409, } ) diff --git a/pkg/httperrors/errors.go b/pkg/httperrors/errors.go index 2ef719bea6..b621514913 100644 --- a/pkg/httperrors/errors.go +++ b/pkg/httperrors/errors.go @@ -94,6 +94,10 @@ func NewMissingParameterError(paramName string) *httputils.JSONClientError { return httputils.NewJsonClientError(httpErrorCode[ErrMissingParameter], string(ErrMissingParameter), msg, paramName) } +func NewPolicyDefinitionError(msg string, params ...interface{}) *httputils.JSONClientError { + return httputils.NewJsonClientError(httpErrorCode[ErrPolicyDefinition], string(ErrPolicyDefinition), msg, params...) +} + func NewInsufficientResourceError(msg string, params ...interface{}) *httputils.JSONClientError { return httputils.NewJsonClientError(httpErrorCode[ErrInsufficientResource], string(ErrInsufficientResource), msg, params...) } diff --git a/pkg/mcclient/modules/mod_policy_assignments.go b/pkg/mcclient/modules/mod_policy_assignments.go new file mode 100644 index 0000000000..ca4483419e --- /dev/null +++ b/pkg/mcclient/modules/mod_policy_assignments.go @@ -0,0 +1,33 @@ +// Copyright 2019 Yunion +// +// 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 modules + +import "yunion.io/x/onecloud/pkg/mcclient/modulebase" + +type PolicyAssignmentManager struct { + modulebase.ResourceManager +} + +var ( + PolicyAssignment PolicyAssignmentManager +) + +func init() { + PolicyAssignment = PolicyAssignmentManager{NewComputeManager("policy_assignment", "policy_assignments", + []string{}, + []string{})} + + registerCompute(&PolicyAssignment) +} diff --git a/pkg/mcclient/modules/mod_policy_definitions.go b/pkg/mcclient/modules/mod_policy_definitions.go new file mode 100644 index 0000000000..d725aef0dd --- /dev/null +++ b/pkg/mcclient/modules/mod_policy_definitions.go @@ -0,0 +1,33 @@ +// Copyright 2019 Yunion +// +// 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 modules + +import "yunion.io/x/onecloud/pkg/mcclient/modulebase" + +type PolicyDefinitionManager struct { + modulebase.ResourceManager +} + +var ( + PolicyDefinition PolicyDefinitionManager +) + +func init() { + PolicyDefinition = PolicyDefinitionManager{NewComputeManager("policy_definition", "policy_definitions", + []string{}, + []string{})} + + registerCompute(&PolicyDefinition) +} diff --git a/pkg/multicloud/azure/azure.go b/pkg/multicloud/azure/azure.go index 48b1906358..5bf7a8f8ee 100644 --- a/pkg/multicloud/azure/azure.go +++ b/pkg/multicloud/azure/azure.go @@ -85,6 +85,8 @@ var DEFAULT_API_VERSION = map[string]string{ "Microsoft.ClassicCompute/domainNames": "2015-12-01", //2014-01-01, 2014-06-01, 2015-06-01, 2015-10-01, 2015-12-01, 2016-04-01, 2016-11-01, 2017-11-01, 2017-11-15 "Microsoft.Compute/locations": "2018-06-01", "microsoft.insights/eventtypes/management/values": "2017-03-01-preview", + "Microsoft.Authorization/policyDefinitions": "2019-09-01", + "Microsoft.Authorization/policyAssignments": "2019-09-01", } type AzureClientConfig struct { @@ -128,6 +130,7 @@ func (cfg *AzureClientConfig) Debug(debug bool) *AzureClientConfig { func NewAzureClient(cfg *AzureClientConfig) (*SAzureClient, error) { client := SAzureClient{ AzureClientConfig: cfg, + debug: cfg.debug, } err := client.fetchRegions() if err != nil { diff --git a/pkg/multicloud/azure/policy.go b/pkg/multicloud/azure/policy.go new file mode 100644 index 0000000000..ad1691e1bc --- /dev/null +++ b/pkg/multicloud/azure/policy.go @@ -0,0 +1,250 @@ +// Copyright 2019 Yunion +// +// 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 azure + +import ( + "fmt" + "net/url" + "regexp" + "strings" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + "yunion.io/x/pkg/errors" + + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudprovider" +) + +type SPolicyDefinitonPropertieParameterMetadata struct { + DisplayName string + Description string + StrongType string + AssignPermissions bool +} + +type SPolicyDefinitonPropertieParameter struct { + Type string + Metadata SPolicyDefinitonPropertieParameterMetadata + AllowedValues []string + DefaultValue []string +} + +type SPolicyDefinitonProperties struct { + DisplayName string + PolicyType string + Mode string + Description string + Metadata SPolicyDefinitonPropertieMetadata + Parameters map[string]SPolicyDefinitonPropertieParameter + PolicyRule SPolicyDefinitonPropertieRule +} + +type SPolicyDefinitonPropertieRuleThen struct { + Effect string +} + +type SPolicyDefinitonPropertieRuleInfo jsonutils.JSONDict + +type SPolicyDefinitonPropertieRule struct { + If jsonutils.JSONObject + Then SPolicyDefinitonPropertieRuleThen +} + +type SPolicyDefinitonPropertieMetadata struct { + Version string + Category string +} + +type SPolicyDefinition struct { + Properties SPolicyDefinitonProperties + Id string + Name string + Type string +} + +func (client *SAzureClient) GetPolicyDefinitions() ([]SPolicyDefinition, error) { + definitions := []SPolicyDefinition{} + err := client.ListAll("Microsoft.Authorization/policyDefinitions", &definitions) + if err != nil { + return nil, errors.Wrap(err, "Microsoft.Authorization/policyDefinitions.List") + } + return definitions, nil +} + +func (client *SAzureClient) GetPolicyDefinition(id string) (*SPolicyDefinition, error) { + definition := &SPolicyDefinition{} + err := client.Get(id, []string{}, definition) + if err != nil { + return nil, errors.Wrapf(err, "get %s", id) + } + return definition, nil +} + +type PolicyAssignmentPropertiesParameter struct { + Value []string +} + +type PolicyAssignmentProperties struct { + DisplayName string + Parameters map[string]PolicyAssignmentPropertiesParameter +} + +type SPolicyAssignment struct { + Id string + Properties PolicyAssignmentProperties + values []string + category string + condition string + parameters *jsonutils.JSONDict +} + +func (assignment *SPolicyAssignment) GetName() string { + return assignment.Properties.DisplayName +} + +func (assignment *SPolicyAssignment) GetGlobalId() string { + return strings.ToLower(assignment.Id) +} + +func (assignment *SPolicyAssignment) GetCategory() string { + return assignment.category +} + +func (assignment *SPolicyAssignment) GetCondition() string { + return assignment.condition +} + +func (assignment *SPolicyAssignment) GetParameters() *jsonutils.JSONDict { + return assignment.parameters +} + +func (client *SAzureClient) GetPolicyAssignments(defineId string) ([]SPolicyAssignment, error) { + assignments := []SPolicyAssignment{} + resource := "Microsoft.Authorization/policyAssignments" + if len(defineId) > 0 { + resource += ("?$filter=" + url.PathEscape("policyDefinitionId eq ") + fmt.Sprintf("'%s'", defineId)) + } + err := client.ListAll(resource, &assignments) + if err != nil { + return nil, errors.Wrap(err, "Microsoft.Authorization/policyAssignments.List") + } + return assignments, nil +} + +func (client *SAzureClient) GetICloudDefinitions() ([]cloudprovider.ICloudPolicyDefinition, error) { + ret := []cloudprovider.ICloudPolicyDefinition{} + definitions, err := client.GetPolicyDefinitions() + if err != nil { + return nil, errors.Wrap(err, "GetPolicyDefinitions") + } + for i := range definitions { + if definitions[i].Properties.PolicyRule.Then.Effect != "deny" { + continue + } + rule := definitions[i].Properties.PolicyRule.If + if rule.Contains("field") { + field, _ := rule.GetString("field") + if field == "location" { + defaultValue := []string{} + locationParameter := "" + for k, v := range definitions[i].Properties.Parameters { + if v.Metadata.StrongType == "location" { + defaultValue = v.DefaultValue + locationParameter = k + break + } + } + assignments, err := client.GetPolicyAssignments(definitions[i].Id) + if err != nil { + return nil, errors.Wrapf(err, "GetPolicyAssignments(%s)", definitions[i].Id) + } + for i := range assignments { + location, ok := assignments[i].Properties.Parameters[locationParameter] + if ok { + if len(location.Value) > 0 { + assignments[i].values = location.Value + } else { + assignments[i].values = defaultValue + } + } + regionIds := jsonutils.NewArray() + assignments[i].parameters = jsonutils.NewDict() + for _, value := range assignments[i].values { + region := client.GetRegion(value) + if region != nil { + regionIds.Add(jsonutils.NewString(region.GetGlobalId())) + } else { + log.Errorf("failed to found region %s", value) + } + } + assignments[i].category = api.POLICY_DEFINITION_CATEGORY_CLOUDREGION + if rule.Contains("in") { + assignments[i].condition = api.POLICY_DEFINITION_CONDITION_NOT_IN + } else if rule.Contains("notIn") { + assignments[i].condition = api.POLICY_DEFINITION_CONDITION_IN + } + assignments[i].parameters.Add(regionIds, "cloudregions") + ret = append(ret, &assignments[i]) + } + } else if strings.Contains(field, "tags") { + reg := regexp.MustCompile(`^\[concat\('tags\[', parameters\('\w+'\), '\]'\)\]$`) + if !reg.MatchString(field) { + continue + } + if rule.Contains("exists") { + exists, _ := rule.Bool("exists") + defaultValue := []string{} + tagParameter := "" + for k, v := range definitions[i].Properties.Parameters { + tagParameter = k + defaultValue = v.DefaultValue + } + assignments, err := client.GetPolicyAssignments(definitions[i].Id) + if err != nil { + return nil, errors.Wrapf(err, "GetPolicyAssignments(%s)", definitions[i].Id) + } + for i := range assignments { + tag, ok := assignments[i].Properties.Parameters[tagParameter] + if ok { + if len(tag.Value) > 0 { + assignments[i].values = tag.Value + } else { + assignments[i].values = defaultValue + } + } + if len(assignments[i].values) == 0 { + continue + } + tags := jsonutils.NewArray() + for _, _tag := range assignments[i].values { + tags.Add(jsonutils.NewString(_tag)) + } + assignments[i].parameters = jsonutils.NewDict() + assignments[i].category = api.POLICY_DEFINITION_CATEGORY_TAG + if exists { + assignments[i].condition = api.POLICY_DEFINITION_CONDITION_EXCEPT + } else { + assignments[i].condition = api.POLICY_DEFINITION_CONDITION_CONTAINS + } + assignments[i].parameters.Add(tags, "tags") + ret = append(ret, &assignments[i]) + } + } + } + } + } + return ret, nil +} diff --git a/pkg/multicloud/azure/provider/provider.go b/pkg/multicloud/azure/provider/provider.go index 5d9eda1b65..efe52f39a6 100644 --- a/pkg/multicloud/azure/provider/provider.go +++ b/pkg/multicloud/azure/provider/provider.go @@ -173,6 +173,10 @@ func (self *SAzureProvider) GetIRegions() []cloudprovider.ICloudRegion { return self.client.GetIRegions() } +func (self *SAzureProvider) GetICloudPolicyDefinitions() ([]cloudprovider.ICloudPolicyDefinition, error) { + return self.client.GetICloudDefinitions() +} + func (self *SAzureProvider) GetIRegionById(id string) (cloudprovider.ICloudRegion, error) { return self.client.GetIRegionById(id) } diff --git a/pkg/multicloud/azure/shell/policy.go b/pkg/multicloud/azure/shell/policy.go new file mode 100644 index 0000000000..f9d324de37 --- /dev/null +++ b/pkg/multicloud/azure/shell/policy.go @@ -0,0 +1,73 @@ +// Copyright 2019 Yunion +// +// 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 shell + +import ( + "fmt" + + "yunion.io/x/onecloud/pkg/multicloud/azure" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + type PolicyListOptions struct { + } + shellutils.R(&PolicyListOptions{}, "policy-definition-list", "List policy definitions", func(cli *azure.SRegion, args *PolicyListOptions) error { + definitions, err := cli.GetClient().GetPolicyDefinitions() + if err != nil { + return err + } + printList(definitions, len(definitions), 0, 0, []string{}) + return nil + }) + + type PolicyAssignmentListOptions struct { + DefinitionId string + } + + shellutils.R(&PolicyAssignmentListOptions{}, "policy-assignment-list", "List policy assignment", func(cli *azure.SRegion, args *PolicyAssignmentListOptions) error { + assignments, err := cli.GetClient().GetPolicyAssignments(args.DefinitionId) + if err != nil { + return err + } + printList(assignments, len(assignments), 0, 0, []string{}) + return nil + }) + + type PolicyIdOptions struct { + ID string + } + + shellutils.R(&PolicyIdOptions{}, "policy-definition-show", "Show policy definition", func(cli *azure.SRegion, args *PolicyIdOptions) error { + definition, err := cli.GetClient().GetPolicyDefinition(args.ID) + if err != nil { + return err + } + printObject(definition) + return nil + }) + + shellutils.R(&PolicyListOptions{}, "policy-definition-list-onecloud", "List convert policy assignment", func(cli *azure.SRegion, args *PolicyListOptions) error { + definitions, err := cli.GetClient().GetICloudDefinitions() + if err != nil { + return err + } + for _, definition := range definitions { + fmt.Printf("definition %s category %s condition %s paramters: %s\n", definition.GetName(), definition.GetCategory(), definition.GetCondition(), definition.GetParameters()) + } + return nil + }) + +}