feat: [CODE-2568]: Add rules API for space level (#2865)

* Add openapi spec for space rules
* Merge branch 'main' into dd/space-branch-rules
* Fix doc comments and space permissions
* Merge branch 'main' into dd/space-branch-rules
* Add equal check to update rule
* Merge branch 'main' into dd/space-branch-rules
* Use consistently RuleParentInfo
* Add RuleParentInfo type
* Merge branch 'main' into dd/space-branch-rules
* Rename r to rule in funcs and rule to protection
* Merge remote-tracking branch 'origin/main' into dd/space-branch-rules
* Unifiy instrumentation and audit handling
* Add delete, find, list and patch svc funcs and API endpoints
* Add rules API for space level
pull/3590/head
Darko Draskovic 2024-11-19 12:52:27 +00:00 committed by Harness
parent 37c7d3c626
commit fd9a1ad400
45 changed files with 1779 additions and 557 deletions

View File

@ -35,6 +35,7 @@ import (
"github.com/harness/gitness/app/services/locker"
"github.com/harness/gitness/app/services/protection"
"github.com/harness/gitness/app/services/publicaccess"
"github.com/harness/gitness/app/services/rules"
"github.com/harness/gitness/app/services/settings"
"github.com/harness/gitness/app/services/usergroup"
"github.com/harness/gitness/app/store"
@ -102,6 +103,7 @@ type Controller struct {
publicAccess publicaccess.Service
labelSvc *label.Service
instrumentation instrument.Service
rulesSvc *rules.Service
}
func NewController(
@ -136,6 +138,7 @@ func NewController(
instrumentation instrument.Service,
userGroupStore store.UserGroupStore,
userGroupService usergroup.SearchService,
rulesSvc *rules.Service,
) *Controller {
return &Controller{
defaultBranch: config.Git.DefaultBranch,
@ -169,6 +172,7 @@ func NewController(
instrumentation: instrumentation,
userGroupStore: userGroupStore,
userGroupService: userGroupService,
rulesSvc: rulesSvc,
}
}
@ -249,76 +253,3 @@ func (c *Controller) fetchRules(
return protectionRules, isRepoOwner, nil
}
func (c *Controller) getRuleUserAndUserGroups(
ctx context.Context,
r *types.Rule,
) (map[int64]*types.PrincipalInfo, map[int64]*types.UserGroupInfo, error) {
rule, err := c.parseRule(r)
if err != nil {
return nil, nil, fmt.Errorf("failed to parse rule: %w", err)
}
userMap, err := c.getRuleUsers(ctx, rule)
if err != nil {
return nil, nil, fmt.Errorf("failed to get rule users: %w", err)
}
userGroupMap, err := c.getRuleUserGroups(ctx, rule)
if err != nil {
return nil, nil, fmt.Errorf("failed to get rule user groups: %w", err)
}
return userMap, userGroupMap, nil
}
func (c *Controller) getRuleUsers(
ctx context.Context,
rule protection.Protection,
) (map[int64]*types.PrincipalInfo, error) {
userIDs, err := rule.UserIDs()
if err != nil {
return nil, fmt.Errorf("failed to get user ID from rule: %w", err)
}
userMap, err := c.principalInfoCache.Map(ctx, userIDs)
if err != nil {
return nil, fmt.Errorf("failed to get principal infos: %w", err)
}
return userMap, nil
}
func (c *Controller) getRuleUserGroups(
ctx context.Context,
rule protection.Protection,
) (map[int64]*types.UserGroupInfo, error) {
groupIDs, err := rule.UserGroupIDs()
if err != nil {
return nil, fmt.Errorf("failed to get group IDs from rule: %w", err)
}
userGroupInfoMap := make(map[int64]*types.UserGroupInfo)
if len(groupIDs) == 0 {
return userGroupInfoMap, nil
}
groupMap, err := c.userGroupStore.Map(ctx, groupIDs)
if err != nil {
return nil, fmt.Errorf("failed to get userGroup infos: %w", err)
}
for k, v := range groupMap {
userGroupInfoMap[k] = v.ToUserGroupInfo()
}
return userGroupInfoMap, nil
}
func (c *Controller) parseRule(r *types.Rule) (protection.Protection, error) {
rule, err := c.protectionManager.FromJSON(r.Type, r.Definition, false)
if err != nil {
return nil, fmt.Errorf("failed to parse json rule definition: %w", err)
}
return rule, nil
}

View File

@ -16,139 +16,34 @@ package repo
import (
"context"
"encoding/json"
"fmt"
"time"
"github.com/harness/gitness/app/api/usererror"
"github.com/harness/gitness/app/auth"
"github.com/harness/gitness/app/paths"
"github.com/harness/gitness/app/services/instrument"
"github.com/harness/gitness/app/services/protection"
"github.com/harness/gitness/audit"
"github.com/harness/gitness/app/services/rules"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/check"
"github.com/harness/gitness/types/enum"
"github.com/rs/zerolog/log"
)
type RuleCreateInput struct {
Type types.RuleType `json:"type"`
State enum.RuleState `json:"state"`
// TODO [CODE-1363]: remove after identifier migration.
UID string `json:"uid" deprecated:"true"`
Identifier string `json:"identifier"`
Description string `json:"description"`
Pattern protection.Pattern `json:"pattern"`
Definition json.RawMessage `json:"definition"`
}
// sanitize validates and sanitizes the create rule input data.
func (in *RuleCreateInput) sanitize() error {
// TODO [CODE-1363]: remove after identifier migration.
if in.Identifier == "" {
in.Identifier = in.UID
}
if err := check.Identifier(in.Identifier); err != nil {
return err
}
if err := in.Pattern.Validate(); err != nil {
return usererror.BadRequestf("invalid pattern: %s", err)
}
var ok bool
in.State, ok = in.State.Sanitize()
if !ok {
return usererror.BadRequest("rule state is invalid")
}
if in.Type == "" {
in.Type = protection.TypeBranch
}
if len(in.Definition) == 0 {
return usererror.BadRequest("rule definition missing")
}
return nil
}
// RuleCreate creates a new protection rule for a repo.
func (c *Controller) RuleCreate(ctx context.Context,
session *auth.Session,
repoRef string,
in *RuleCreateInput,
in *rules.CreateInput,
) (*types.Rule, error) {
if err := in.sanitize(); err != nil {
return nil, err
}
repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoEdit)
if err != nil {
return nil, err
}
in.Definition, err = c.protectionManager.SanitizeJSON(in.Type, in.Definition)
if err != nil {
return nil, usererror.BadRequestf("invalid rule definition: %s", err.Error())
}
now := time.Now().UnixMilli()
r := &types.Rule{
CreatedBy: session.Principal.ID,
Created: now,
Updated: now,
RepoID: &repo.ID,
SpaceID: nil,
Type: in.Type,
State: in.State,
Identifier: in.Identifier,
Description: in.Description,
Pattern: in.Pattern.JSON(),
Definition: in.Definition,
CreatedByInfo: types.PrincipalInfo{},
}
err = c.ruleStore.Create(ctx, r)
if err != nil {
return nil, fmt.Errorf("failed to create repository-level protection rule: %w", err)
}
err = c.auditService.Log(ctx,
session.Principal,
audit.NewResource(audit.ResourceTypeBranchRule, r.Identifier, audit.RepoName, repo.Identifier),
audit.ActionCreated,
paths.Parent(repo.Path),
audit.WithNewObject(r),
rule, err := c.rulesSvc.Create(
ctx, &session.Principal,
enum.RuleParentRepo, repo.ID,
repo.Identifier, repo.Path,
in,
)
if err != nil {
log.Ctx(ctx).Warn().Msgf("failed to insert audit log for create branch rule operation: %s", err)
return nil, fmt.Errorf("failed to create repo-level protection rule: %w", err)
}
err = c.instrumentation.Track(ctx, instrument.Event{
Type: instrument.EventTypeCreateBranchRule,
Principal: session.Principal.ToPrincipalInfo(),
Path: repo.Path,
Properties: map[instrument.Property]any{
instrument.PropertyRepositoryID: repo.ID,
instrument.PropertyRepositoryName: repo.Identifier,
instrument.PropertyRuleID: r.ID,
},
})
if err != nil {
log.Ctx(ctx).Warn().Msgf("failed to insert instrumentation record for create branch rule operation: %s", err)
}
userMap, userGroupMap, err := c.getRuleUserAndUserGroups(ctx, r)
if err != nil {
return nil, fmt.Errorf("failed to get rule users and user groups: %w", err)
}
r.Users = userMap
r.UserGroups = userGroupMap
return r, nil
return rule, nil
}

View File

@ -19,11 +19,7 @@ import (
"fmt"
"github.com/harness/gitness/app/auth"
"github.com/harness/gitness/app/paths"
"github.com/harness/gitness/audit"
"github.com/harness/gitness/types/enum"
"github.com/rs/zerolog/log"
)
// RuleDelete deletes a protection rule by identifier.
@ -37,25 +33,15 @@ func (c *Controller) RuleDelete(ctx context.Context,
return err
}
r, err := c.ruleStore.FindByIdentifier(ctx, nil, &repo.ID, identifier)
if err != nil {
return fmt.Errorf("failed to find repository-level protection rule by identifier: %w", err)
}
err = c.ruleStore.Delete(ctx, r.ID)
if err != nil {
return fmt.Errorf("failed to delete repository-level protection rule: %w", err)
}
err = c.auditService.Log(ctx,
session.Principal,
audit.NewResource(audit.ResourceTypeBranchRule, r.Identifier, audit.RepoName, repo.Identifier),
audit.ActionDeleted,
paths.Parent(repo.Path),
audit.WithOldObject(r),
err = c.rulesSvc.Delete(
ctx,
&session.Principal,
enum.RuleParentRepo, repo.ID,
repo.Identifier, repo.Path,
identifier,
)
if err != nil {
log.Ctx(ctx).Warn().Msgf("failed to insert audit log for delete branch rule operation: %s", err)
return fmt.Errorf("failed to delete repo-level protection rule: %w", err)
}
return nil

View File

@ -34,18 +34,10 @@ func (c *Controller) RuleFind(ctx context.Context,
return nil, err
}
r, err := c.ruleStore.FindByIdentifier(ctx, nil, &repo.ID, identifier)
rule, err := c.rulesSvc.Find(ctx, enum.RuleParentRepo, repo.ID, identifier)
if err != nil {
return nil, fmt.Errorf("failed to find repository-level protection rule by identifier: %w", err)
return nil, fmt.Errorf("failed to find repo-level protection rule by identifier: %w", err)
}
userMap, userGroupMap, err := c.getRuleUserAndUserGroups(ctx, r)
if err != nil {
return nil, fmt.Errorf("failed to get rule users and user groups: %w", err)
}
r.Users = userMap
r.UserGroups = userGroupMap
return r, nil
return rule, nil
}

View File

@ -19,7 +19,6 @@ import (
"fmt"
"github.com/harness/gitness/app/auth"
"github.com/harness/gitness/store/database/dbtx"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/enum"
)
@ -28,6 +27,7 @@ import (
func (c *Controller) RuleList(ctx context.Context,
session *auth.Session,
repoRef string,
inherited bool,
filter *types.RuleFilter,
) ([]types.Rule, int64, error) {
repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoView)
@ -35,36 +35,9 @@ func (c *Controller) RuleList(ctx context.Context,
return nil, 0, err
}
var list []types.Rule
var count int64
err = c.tx.WithTx(ctx, func(ctx context.Context) error {
list, err = c.ruleStore.List(ctx, nil, &repo.ID, filter)
if err != nil {
return fmt.Errorf("failed to list repository-level protection rules: %w", err)
}
if filter.Page == 1 && len(list) < filter.Size {
count = int64(len(list))
return nil
}
count, err = c.ruleStore.Count(ctx, nil, &repo.ID, filter)
if err != nil {
return fmt.Errorf("failed to count repository-level protection rules: %w", err)
}
return nil
}, dbtx.TxDefaultReadOnly)
list, count, err := c.rulesSvc.List(ctx, repo.ID, enum.RuleParentRepo, inherited, filter)
if err != nil {
return nil, 0, err
}
for i := range list {
list[i].Users, list[i].UserGroups, err = c.getRuleUserAndUserGroups(ctx, &list[i])
if err != nil {
return nil, 0, err
}
return nil, 0, fmt.Errorf("failed to list repo-level protection rules: %w", err)
}
return list, count, nil

View File

@ -16,146 +16,38 @@ package repo
import (
"context"
"encoding/json"
"fmt"
"github.com/harness/gitness/app/api/usererror"
"github.com/harness/gitness/app/auth"
"github.com/harness/gitness/app/paths"
"github.com/harness/gitness/app/services/protection"
"github.com/harness/gitness/audit"
"github.com/harness/gitness/app/services/rules"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/check"
"github.com/harness/gitness/types/enum"
"github.com/rs/zerolog/log"
)
type RuleUpdateInput struct {
// TODO [CODE-1363]: remove after identifier migration.
UID *string `json:"uid" deprecated:"true"`
Identifier *string `json:"identifier"`
State *enum.RuleState `json:"state"`
Description *string `json:"description"`
Pattern *protection.Pattern `json:"pattern"`
Definition *json.RawMessage `json:"definition"`
}
// sanitize validates and sanitizes the update rule input data.
func (in *RuleUpdateInput) sanitize() error {
// TODO [CODE-1363]: remove after identifier migration.
if in.Identifier == nil {
in.Identifier = in.UID
}
if in.Identifier != nil {
if err := check.Identifier(*in.Identifier); err != nil {
return err
}
}
if in.State != nil {
state, ok := in.State.Sanitize()
if !ok {
return usererror.BadRequest("rule state is invalid")
}
in.State = &state
}
if in.Pattern != nil {
if err := in.Pattern.Validate(); err != nil {
return usererror.BadRequestf("invalid pattern: %s", err)
}
}
if in.Definition != nil && len(*in.Definition) == 0 {
return usererror.BadRequest("rule definition missing")
}
return nil
}
func (in *RuleUpdateInput) isEmpty() bool {
return in.Identifier == nil && in.State == nil && in.Description == nil && in.Pattern == nil && in.Definition == nil
}
// RuleUpdate updates an existing protection rule for a repository.
func (c *Controller) RuleUpdate(ctx context.Context,
session *auth.Session,
repoRef string,
identifier string,
in *RuleUpdateInput,
in *rules.UpdateInput,
) (*types.Rule, error) {
if err := in.sanitize(); err != nil {
return nil, err
}
repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoEdit)
if err != nil {
return nil, err
}
r, err := c.ruleStore.FindByIdentifier(ctx, nil, &repo.ID, identifier)
if err != nil {
return nil, fmt.Errorf("failed to get a repository rule by its identifier: %w", err)
}
oldRule := r.Clone()
if in.isEmpty() {
userMap, userGroupMap, err := c.getRuleUserAndUserGroups(ctx, r)
if err != nil {
return nil, fmt.Errorf("failed to get rule users and user groups: %w", err)
}
r.Users = userMap
r.UserGroups = userGroupMap
return r, nil
}
if in.Identifier != nil {
r.Identifier = *in.Identifier
}
if in.State != nil {
r.State = *in.State
}
if in.Description != nil {
r.Description = *in.Description
}
if in.Pattern != nil {
r.Pattern = in.Pattern.JSON()
}
if in.Definition != nil {
r.Definition, err = c.protectionManager.SanitizeJSON(r.Type, *in.Definition)
if err != nil {
return nil, usererror.BadRequestf("invalid rule definition: %s", err.Error())
}
}
userMap, userGroupMap, err := c.getRuleUserAndUserGroups(ctx, r)
if err != nil {
return nil, fmt.Errorf("failed to get rule users and user groups: %w", err)
}
r.Users = userMap
r.UserGroups = userGroupMap
err = c.ruleStore.Update(ctx, r)
if err != nil {
return nil, fmt.Errorf("failed to update repository-level protection rule: %w", err)
}
err = c.auditService.Log(ctx,
session.Principal,
audit.NewResource(audit.ResourceTypeBranchRule, r.Identifier, audit.RepoName, repo.Identifier),
audit.ActionUpdated,
paths.Parent(repo.Path),
audit.WithOldObject(oldRule),
audit.WithNewObject(r),
rule, err := c.rulesSvc.Update(
ctx, &session.Principal,
enum.RuleParentRepo,
repo.ID,
repo.Identifier,
repo.Path,
identifier,
in,
)
if err != nil {
log.Ctx(ctx).Warn().Msgf("failed to insert audit log for update branch rule operation: %s", err)
return nil, fmt.Errorf("failed to update repo-level protection rule by identifier: %w", err)
}
return r, nil
return rule, nil
}

View File

@ -26,6 +26,7 @@ import (
"github.com/harness/gitness/app/services/locker"
"github.com/harness/gitness/app/services/protection"
"github.com/harness/gitness/app/services/publicaccess"
"github.com/harness/gitness/app/services/rules"
"github.com/harness/gitness/app/services/settings"
"github.com/harness/gitness/app/services/usergroup"
"github.com/harness/gitness/app/store"
@ -77,6 +78,7 @@ func ProvideController(
instrumentation instrument.Service,
userGroupStore store.UserGroupStore,
userGroupService usergroup.SearchService,
rulesSvc *rules.Service,
) *Controller {
return NewController(config, tx, urlProvider,
authorizer,
@ -84,7 +86,9 @@ func ProvideController(
principalStore, ruleStore, checkStore, pullReqStore, settings,
principalInfoCache, protectionManager, rpcClient, importer,
codeOwners, reporeporter, indexer, limiter, locker, auditService, mtxManager, identifierCheck,
repoChecks, publicAccess, labelSvc, instrumentation, userGroupStore, userGroupService)
repoChecks, publicAccess, labelSvc, instrumentation, userGroupStore, userGroupService,
rulesSvc,
)
}
func ProvideRepoCheck() Check {

View File

@ -28,6 +28,7 @@ import (
"github.com/harness/gitness/app/services/label"
"github.com/harness/gitness/app/services/publicaccess"
"github.com/harness/gitness/app/services/pullreq"
"github.com/harness/gitness/app/services/rules"
"github.com/harness/gitness/app/sse"
"github.com/harness/gitness/app/store"
"github.com/harness/gitness/app/url"
@ -90,6 +91,7 @@ type Controller struct {
labelSvc *label.Service
instrumentation instrument.Service
executionStore store.ExecutionStore
rulesSvc *rules.Service
}
func NewController(config *types.Config, tx dbtx.Transactor, urlProvider url.Provider,
@ -102,6 +104,7 @@ func NewController(config *types.Config, tx dbtx.Transactor, urlProvider url.Pro
limiter limiter.ResourceLimiter, publicAccess publicaccess.Service, auditService audit.Service,
gitspaceSvc *gitspace.Service, labelSvc *label.Service,
instrumentation instrument.Service, executionStore store.ExecutionStore,
rulesSvc *rules.Service,
) *Controller {
return &Controller{
nestedSpacesEnabled: config.NestedSpacesEnabled,
@ -130,5 +133,6 @@ func NewController(config *types.Config, tx dbtx.Transactor, urlProvider url.Pro
labelSvc: labelSvc,
instrumentation: instrumentation,
executionStore: executionStore,
rulesSvc: rulesSvc,
}
}

View File

@ -0,0 +1,52 @@
// Copyright 2023 Harness, Inc.
//
// 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 space
import (
"context"
"fmt"
"github.com/harness/gitness/app/auth"
"github.com/harness/gitness/app/services/rules"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/enum"
)
// RuleCreate creates a new protection rule for a space.
func (c *Controller) RuleCreate(ctx context.Context,
session *auth.Session,
spaceRef string,
in *rules.CreateInput,
) (*types.Rule, error) {
space, err := c.getSpaceCheckAuth(ctx, session, spaceRef, enum.PermissionSpaceEdit)
if err != nil {
return nil, fmt.Errorf("failed to acquire access to space: %w", err)
}
rule, err := c.rulesSvc.Create(
ctx,
&session.Principal,
enum.RuleParentSpace,
space.ID,
space.Identifier,
space.Path,
in,
)
if err != nil {
return nil, fmt.Errorf("failed to create space-level protection rule: %w", err)
}
return rule, nil
}

View File

@ -0,0 +1,50 @@
// Copyright 2023 Harness, Inc.
//
// 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 space
import (
"context"
"fmt"
"github.com/harness/gitness/app/auth"
"github.com/harness/gitness/types/enum"
)
// RuleDelete deletes a protection rule by identifier.
func (c *Controller) RuleDelete(ctx context.Context,
session *auth.Session,
spaceRef string,
identifier string,
) error {
space, err := c.getSpaceCheckAuth(ctx, session, spaceRef, enum.PermissionSpaceEdit)
if err != nil {
return fmt.Errorf("failed to acquire access to space: %w", err)
}
err = c.rulesSvc.Delete(
ctx,
&session.Principal,
enum.RuleParentSpace,
space.ID,
space.Identifier,
space.Path,
identifier,
)
if err != nil {
return fmt.Errorf("failed to delete space-level protection rule: %w", err)
}
return nil
}

View File

@ -0,0 +1,43 @@
// Copyright 2023 Harness, Inc.
//
// 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 space
import (
"context"
"fmt"
"github.com/harness/gitness/app/auth"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/enum"
)
// RuleFind returns the protection rule by identifier.
func (c *Controller) RuleFind(ctx context.Context,
session *auth.Session,
spaceRef string,
identifier string,
) (*types.Rule, error) {
space, err := c.getSpaceCheckAuth(ctx, session, spaceRef, enum.PermissionSpaceView)
if err != nil {
return nil, fmt.Errorf("failed to acquire access to space: %w", err)
}
rule, err := c.rulesSvc.Find(ctx, enum.RuleParentSpace, space.ID, identifier)
if err != nil {
return nil, fmt.Errorf("failed to find space-level protection rule by identifier: %w", err)
}
return rule, nil
}

View File

@ -0,0 +1,44 @@
// Copyright 2023 Harness, Inc.
//
// 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 space
import (
"context"
"fmt"
"github.com/harness/gitness/app/auth"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/enum"
)
// RuleList returns protection rules for a repository.
func (c *Controller) RuleList(ctx context.Context,
session *auth.Session,
spaceRef string,
inherited bool,
filter *types.RuleFilter,
) ([]types.Rule, int64, error) {
space, err := c.getSpaceCheckAuth(ctx, session, spaceRef, enum.PermissionSpaceView)
if err != nil {
return nil, 0, fmt.Errorf("failed to acquire access to space: %w", err)
}
list, count, err := c.rulesSvc.List(ctx, space.ID, enum.RuleParentSpace, inherited, filter)
if err != nil {
return nil, 0, fmt.Errorf("failed to list space-level protection rules: %w", err)
}
return list, count, nil
}

View File

@ -0,0 +1,54 @@
// Copyright 2023 Harness, Inc.
//
// 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 space
import (
"context"
"fmt"
"github.com/harness/gitness/app/auth"
"github.com/harness/gitness/app/services/rules"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/enum"
)
// RuleUpdate updates an existing protection rule for a space.
func (c *Controller) RuleUpdate(ctx context.Context,
session *auth.Session,
spaceRef string,
identifier string,
in *rules.UpdateInput,
) (*types.Rule, error) {
space, err := c.getSpaceCheckAuth(ctx, session, spaceRef, enum.PermissionSpaceEdit)
if err != nil {
return nil, fmt.Errorf("failed to acquire access to space: %w", err)
}
rule, err := c.rulesSvc.Update(
ctx,
&session.Principal,
enum.RuleParentSpace,
space.ID,
space.Identifier,
space.Path,
identifier,
in,
)
if err != nil {
return nil, fmt.Errorf("failed to update space-level protection rule by identifier: %w", err)
}
return rule, nil
}

View File

@ -25,6 +25,7 @@ import (
"github.com/harness/gitness/app/services/label"
"github.com/harness/gitness/app/services/publicaccess"
"github.com/harness/gitness/app/services/pullreq"
"github.com/harness/gitness/app/services/rules"
"github.com/harness/gitness/app/sse"
"github.com/harness/gitness/app/store"
"github.com/harness/gitness/app/url"
@ -47,18 +48,21 @@ func ProvideController(config *types.Config, tx dbtx.Transactor, urlProvider url
connectorStore store.ConnectorStore, templateStore store.TemplateStore,
spaceStore store.SpaceStore, repoStore store.RepoStore, principalStore store.PrincipalStore,
repoCtrl *repo.Controller, membershipStore store.MembershipStore, prListService *pullreq.ListService,
importer *importer.Repository, exporter *exporter.Repository, limiter limiter.ResourceLimiter,
publicAccess publicaccess.Service, auditService audit.Service, gitspaceService *gitspace.Service,
importer *importer.Repository,
exporter *exporter.Repository, limiter limiter.ResourceLimiter, publicAccess publicaccess.Service,
auditService audit.Service, gitspaceService *gitspace.Service,
labelSvc *label.Service, instrumentation instrument.Service, executionStore store.ExecutionStore,
rulesSvc *rules.Service,
) *Controller {
return NewController(config, tx, urlProvider,
sseStreamer, identifierCheck, authorizer,
spacePathStore, pipelineStore, secretStore,
connectorStore, templateStore,
spaceStore, repoStore, principalStore,
repoCtrl, membershipStore, prListService,
importer, exporter, limiter,
publicAccess, auditService, gitspaceService,
repoCtrl, membershipStore, prListService, importer,
exporter, limiter, publicAccess,
auditService, gitspaceService,
labelSvc, instrumentation, executionStore,
rulesSvc,
)
}

View File

@ -21,9 +21,10 @@ import (
"github.com/harness/gitness/app/api/controller/repo"
"github.com/harness/gitness/app/api/render"
"github.com/harness/gitness/app/api/request"
"github.com/harness/gitness/app/services/rules"
)
// HandleRuleCreate handles API that adds a new protection rule to a repository.
// HandleRuleCreate adds a new protection rule to a repository.
func HandleRuleCreate(repoCtrl *repo.Controller) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
@ -35,7 +36,7 @@ func HandleRuleCreate(repoCtrl *repo.Controller) http.HandlerFunc {
return
}
in := new(repo.RuleCreateInput)
in := new(rules.CreateInput)
err = json.NewDecoder(r.Body).Decode(in)
if err != nil {
render.BadRequestf(ctx, w, "Invalid Request Body: %s.", err)

View File

@ -22,7 +22,7 @@ import (
"github.com/harness/gitness/app/api/request"
)
// HandleRuleDelete handles API that deletes a protection rule.
// HandleRuleDelete deletes a protection rule of a repository.
func HandleRuleDelete(repoCtrl *repo.Controller) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()

View File

@ -22,7 +22,7 @@ import (
"github.com/harness/gitness/app/api/request"
)
// HandleRuleFind handles API that returns a protection rule of a repository.
// HandleRuleFind finds a protection rule of a repository.
func HandleRuleFind(repoCtrl *repo.Controller) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()

View File

@ -22,7 +22,7 @@ import (
"github.com/harness/gitness/app/api/request"
)
// HandleRuleList handles API that lists a protection rules of a repository.
// HandleRuleList lists rotection rules of a repository.
func HandleRuleList(repoCtrl *repo.Controller) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
@ -36,7 +36,12 @@ func HandleRuleList(repoCtrl *repo.Controller) http.HandlerFunc {
filter := request.ParseRuleFilter(r)
rules, rulesCount, err := repoCtrl.RuleList(ctx, session, repoRef, filter)
inherited, err := request.ParseInheritedFromQuery(r)
if err != nil {
render.TranslatedUserError(ctx, w, err)
}
rules, rulesCount, err := repoCtrl.RuleList(ctx, session, repoRef, inherited, filter)
if err != nil {
render.TranslatedUserError(ctx, w, err)
return

View File

@ -21,9 +21,10 @@ import (
"github.com/harness/gitness/app/api/controller/repo"
"github.com/harness/gitness/app/api/render"
"github.com/harness/gitness/app/api/request"
"github.com/harness/gitness/app/services/rules"
)
// HandleRuleUpdate handles API that updates a protection rule of a repository.
// HandleRuleUpdate updates a protection rule of a repository.
func HandleRuleUpdate(repoCtrl *repo.Controller) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
@ -41,7 +42,7 @@ func HandleRuleUpdate(repoCtrl *repo.Controller) http.HandlerFunc {
return
}
in := new(repo.RuleUpdateInput)
in := new(rules.UpdateInput)
err = json.NewDecoder(r.Body).Decode(in)
if err != nil {
render.BadRequestf(ctx, w, "Invalid Request Body: %s.", err)

View File

@ -0,0 +1,54 @@
// Copyright 2023 Harness, Inc.
//
// 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 space
import (
"encoding/json"
"net/http"
"github.com/harness/gitness/app/api/controller/space"
"github.com/harness/gitness/app/api/render"
"github.com/harness/gitness/app/api/request"
"github.com/harness/gitness/app/services/rules"
)
// HandleRuleCreate adds a new protection rule to a space.
func HandleRuleCreate(spaceCtrl *space.Controller) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
session, _ := request.AuthSessionFrom(ctx)
spaceRef, err := request.GetSpaceRefFromPath(r)
if err != nil {
render.TranslatedUserError(ctx, w, err)
return
}
in := new(rules.CreateInput)
err = json.NewDecoder(r.Body).Decode(in)
if err != nil {
render.BadRequestf(ctx, w, "Invalid Request Body: %s.", err)
return
}
rule, err := spaceCtrl.RuleCreate(ctx, session, spaceRef, in)
if err != nil {
render.TranslatedUserError(ctx, w, err)
return
}
render.JSON(w, http.StatusCreated, rule)
}
}

View File

@ -0,0 +1,51 @@
// Copyright 2023 Harness, Inc.
//
// 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 space
import (
"net/http"
"github.com/harness/gitness/app/api/controller/space"
"github.com/harness/gitness/app/api/render"
"github.com/harness/gitness/app/api/request"
)
// HandleRuleDelete deletes a protection rule of a space.
func HandleRuleDelete(spaceCtrl *space.Controller) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
session, _ := request.AuthSessionFrom(ctx)
spaceRef, err := request.GetSpaceRefFromPath(r)
if err != nil {
render.TranslatedUserError(ctx, w, err)
return
}
ruleIdentifier, err := request.GetRuleIdentifierFromPath(r)
if err != nil {
render.TranslatedUserError(ctx, w, err)
return
}
err = spaceCtrl.RuleDelete(ctx, session, spaceRef, ruleIdentifier)
if err != nil {
render.TranslatedUserError(ctx, w, err)
return
}
render.DeleteSuccessful(w)
}
}

View File

@ -0,0 +1,51 @@
// Copyright 2023 Harness, Inc.
//
// 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 space
import (
"net/http"
"github.com/harness/gitness/app/api/controller/space"
"github.com/harness/gitness/app/api/render"
"github.com/harness/gitness/app/api/request"
)
// HandleRuleFind finds a protection rule of a space.
func HandleRuleFind(spaceCtrl *space.Controller) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
session, _ := request.AuthSessionFrom(ctx)
spaceRef, err := request.GetSpaceRefFromPath(r)
if err != nil {
render.TranslatedUserError(ctx, w, err)
return
}
ruleIdentifier, err := request.GetRuleIdentifierFromPath(r)
if err != nil {
render.TranslatedUserError(ctx, w, err)
return
}
rule, err := spaceCtrl.RuleFind(ctx, session, spaceRef, ruleIdentifier)
if err != nil {
render.TranslatedUserError(ctx, w, err)
return
}
render.JSON(w, http.StatusOK, rule)
}
}

View File

@ -0,0 +1,52 @@
// Copyright 2023 Harness, Inc.
//
// 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 space
import (
"net/http"
"github.com/harness/gitness/app/api/controller/space"
"github.com/harness/gitness/app/api/render"
"github.com/harness/gitness/app/api/request"
)
// HandleRuleList lists a protection rules of a space.
func HandleRuleList(spaceCtrl *space.Controller) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
session, _ := request.AuthSessionFrom(ctx)
spaceRef, err := request.GetSpaceRefFromPath(r)
if err != nil {
render.TranslatedUserError(ctx, w, err)
return
}
filter := request.ParseRuleFilter(r)
inherited, err := request.ParseInheritedFromQuery(r)
if err != nil {
render.TranslatedUserError(ctx, w, err)
}
rules, rulesCount, err := spaceCtrl.RuleList(ctx, session, spaceRef, inherited, filter)
if err != nil {
render.TranslatedUserError(ctx, w, err)
return
}
render.Pagination(r, w, filter.Page, filter.Size, int(rulesCount))
render.JSON(w, http.StatusOK, rules)
}
}

View File

@ -0,0 +1,60 @@
// Copyright 2023 Harness, Inc.
//
// 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 space
import (
"encoding/json"
"net/http"
"github.com/harness/gitness/app/api/controller/space"
"github.com/harness/gitness/app/api/render"
"github.com/harness/gitness/app/api/request"
"github.com/harness/gitness/app/services/rules"
)
// HandleRuleUpdate updates a protection rule of a space.
func HandleRuleUpdate(spaceCtrl *space.Controller) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
session, _ := request.AuthSessionFrom(ctx)
spaceRef, err := request.GetSpaceRefFromPath(r)
if err != nil {
render.TranslatedUserError(ctx, w, err)
return
}
ruleIdentifier, err := request.GetRuleIdentifierFromPath(r)
if err != nil {
render.TranslatedUserError(ctx, w, err)
return
}
in := new(rules.UpdateInput)
err = json.NewDecoder(r.Body).Decode(in)
if err != nil {
render.BadRequestf(ctx, w, "Invalid Request Body: %s.", err)
return
}
rule, err := spaceCtrl.RuleUpdate(ctx, session, spaceRef, ruleIdentifier, in)
if err != nil {
render.TranslatedUserError(ctx, w, err)
return
}
render.JSON(w, http.StatusOK, rule)
}
}

View File

@ -61,6 +61,7 @@ func (*OpenAPI) Generate() *openapi3.Spec {
spaceOperations(&reflector)
pluginOperations(&reflector)
repoOperations(&reflector)
rulesOperations(&reflector)
pipelineOperations(&reflector)
connectorOperations(&reflector)
templateOperations(&reflector)

View File

@ -21,7 +21,6 @@ import (
"github.com/harness/gitness/app/api/controller/reposettings"
"github.com/harness/gitness/app/api/request"
"github.com/harness/gitness/app/api/usererror"
"github.com/harness/gitness/app/services/protection"
"github.com/harness/gitness/git"
gittypes "github.com/harness/gitness/git/api"
"github.com/harness/gitness/types"
@ -175,31 +174,6 @@ type codeOwnersValidate struct {
repoRequest
}
// ruleType is a plugin for types.RuleType to allow using oneof.
type ruleType string
func (ruleType) Enum() []interface{} {
return []interface{}{protection.TypeBranch}
}
// ruleDefinition is a plugin for types.Rule Definition to allow using oneof.
type ruleDefinition struct{}
func (ruleDefinition) JSONSchemaOneOf() []interface{} {
return []interface{}{protection.Branch{}}
}
type rule struct {
types.Rule
// overshadow Type and Definition to enable oneof.
Type ruleType `json:"type"`
Definition ruleDefinition `json:"definition"`
// overshadow Pattern to correct the type
Pattern protection.Pattern `json:"pattern"`
}
type restoreRequest struct {
repoRequest
repo.RestoreInput
@ -1142,88 +1116,6 @@ func repoOperations(reflector *openapi3.Reflector) {
_ = reflector.SetJSONResponse(&opMergeCheck, new(usererror.Error), http.StatusForbidden)
_ = reflector.Spec.AddOperation(http.MethodPost, "/repos/{repo_ref}/merge-check/{range}", opMergeCheck)
opRuleAdd := openapi3.Operation{}
opRuleAdd.WithTags("repository")
opRuleAdd.WithMapOfAnything(map[string]interface{}{"operationId": "ruleAdd"})
_ = reflector.SetRequest(&opRuleAdd, struct {
repoRequest
repo.RuleCreateInput
// overshadow "definition"
Type ruleType `json:"type"`
Definition ruleDefinition `json:"definition"`
}{}, http.MethodPost)
_ = reflector.SetJSONResponse(&opRuleAdd, rule{}, http.StatusCreated)
_ = reflector.SetJSONResponse(&opRuleAdd, new(usererror.Error), http.StatusInternalServerError)
_ = reflector.SetJSONResponse(&opRuleAdd, new(usererror.Error), http.StatusUnauthorized)
_ = reflector.SetJSONResponse(&opRuleAdd, new(usererror.Error), http.StatusForbidden)
_ = reflector.SetJSONResponse(&opRuleAdd, new(usererror.Error), http.StatusNotFound)
_ = reflector.Spec.AddOperation(http.MethodPost, "/repos/{repo_ref}/rules", opRuleAdd)
opRuleDelete := openapi3.Operation{}
opRuleDelete.WithTags("repository")
opRuleDelete.WithMapOfAnything(map[string]interface{}{"operationId": "ruleDelete"})
_ = reflector.SetRequest(&opRuleDelete, struct {
repoRequest
RuleIdentifier string `path:"rule_identifier"`
}{}, http.MethodDelete)
_ = reflector.SetJSONResponse(&opRuleDelete, nil, http.StatusNoContent)
_ = reflector.SetJSONResponse(&opRuleDelete, new(usererror.Error), http.StatusInternalServerError)
_ = reflector.SetJSONResponse(&opRuleDelete, new(usererror.Error), http.StatusUnauthorized)
_ = reflector.SetJSONResponse(&opRuleDelete, new(usererror.Error), http.StatusForbidden)
_ = reflector.SetJSONResponse(&opRuleDelete, new(usererror.Error), http.StatusNotFound)
_ = reflector.Spec.AddOperation(http.MethodDelete, "/repos/{repo_ref}/rules/{rule_identifier}", opRuleDelete)
opRuleUpdate := openapi3.Operation{}
opRuleUpdate.WithTags("repository")
opRuleUpdate.WithMapOfAnything(map[string]interface{}{"operationId": "ruleUpdate"})
_ = reflector.SetRequest(&opRuleUpdate, &struct {
repoRequest
Identifier string `path:"rule_identifier"`
repo.RuleUpdateInput
// overshadow Type and Definition to enable oneof.
Type ruleType `json:"type"`
Definition ruleDefinition `json:"definition"`
}{}, http.MethodPatch)
_ = reflector.SetJSONResponse(&opRuleUpdate, rule{}, http.StatusOK)
_ = reflector.SetJSONResponse(&opRuleUpdate, new(usererror.Error), http.StatusInternalServerError)
_ = reflector.SetJSONResponse(&opRuleUpdate, new(usererror.Error), http.StatusUnauthorized)
_ = reflector.SetJSONResponse(&opRuleUpdate, new(usererror.Error), http.StatusForbidden)
_ = reflector.SetJSONResponse(&opRuleUpdate, new(usererror.Error), http.StatusNotFound)
_ = reflector.Spec.AddOperation(http.MethodPatch, "/repos/{repo_ref}/rules/{rule_identifier}", opRuleUpdate)
opRuleList := openapi3.Operation{}
opRuleList.WithTags("repository")
opRuleList.WithMapOfAnything(map[string]interface{}{"operationId": "ruleList"})
opRuleList.WithParameters(
queryParameterQueryRuleList,
queryParameterOrder, queryParameterSortRuleList,
QueryParameterPage, QueryParameterLimit)
_ = reflector.SetRequest(&opRuleList, &struct {
repoRequest
}{}, http.MethodGet)
_ = reflector.SetJSONResponse(&opRuleList, []rule{}, http.StatusOK)
_ = reflector.SetJSONResponse(&opRuleList, new(usererror.Error), http.StatusInternalServerError)
_ = reflector.SetJSONResponse(&opRuleList, new(usererror.Error), http.StatusUnauthorized)
_ = reflector.SetJSONResponse(&opRuleList, new(usererror.Error), http.StatusForbidden)
_ = reflector.SetJSONResponse(&opRuleList, new(usererror.Error), http.StatusNotFound)
_ = reflector.Spec.AddOperation(http.MethodGet, "/repos/{repo_ref}/rules", opRuleList)
opRuleGet := openapi3.Operation{}
opRuleGet.WithTags("repository")
opRuleGet.WithMapOfAnything(map[string]interface{}{"operationId": "ruleGet"})
_ = reflector.SetRequest(&opRuleGet, &struct {
repoRequest
Identifier string `path:"rule_identifier"`
}{}, http.MethodGet)
_ = reflector.SetJSONResponse(&opRuleGet, rule{}, http.StatusOK)
_ = reflector.SetJSONResponse(&opRuleGet, new(usererror.Error), http.StatusInternalServerError)
_ = reflector.SetJSONResponse(&opRuleGet, new(usererror.Error), http.StatusUnauthorized)
_ = reflector.SetJSONResponse(&opRuleGet, new(usererror.Error), http.StatusForbidden)
_ = reflector.SetJSONResponse(&opRuleGet, new(usererror.Error), http.StatusNotFound)
_ = reflector.Spec.AddOperation(http.MethodGet, "/repos/{repo_ref}/rules/{rule_identifier}", opRuleGet)
opCodeOwnerValidate := openapi3.Operation{}
opCodeOwnerValidate.WithTags("repository")
opCodeOwnerValidate.WithMapOfAnything(map[string]interface{}{"operationId": "codeOwnersValidate"})

217
app/api/openapi/rules.go Normal file
View File

@ -0,0 +1,217 @@
// Copyright 2023 Harness, Inc.
//
// 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 openapi
import (
"net/http"
"github.com/harness/gitness/app/api/usererror"
"github.com/harness/gitness/app/services/protection"
"github.com/harness/gitness/app/services/rules"
"github.com/harness/gitness/types"
"github.com/swaggest/openapi-go/openapi3"
)
// ruleType is a plugin for types.RuleType to allow using oneof.
type ruleType string
func (ruleType) Enum() []interface{} {
return []interface{}{protection.TypeBranch}
}
// ruleDefinition is a plugin for types.Rule Definition to allow using oneof.
type ruleDefinition struct{}
func (ruleDefinition) JSONSchemaOneOf() []interface{} {
return []interface{}{protection.Branch{}}
}
type rule struct {
types.Rule
// overshadow Type and Definition to enable oneof.
Type ruleType `json:"type"`
Definition ruleDefinition `json:"definition"`
// overshadow Pattern to correct the type
Pattern protection.Pattern `json:"pattern"`
}
func rulesOperations(reflector *openapi3.Reflector) {
opSpaceRuleAdd := openapi3.Operation{}
opSpaceRuleAdd.WithTags("space")
opSpaceRuleAdd.WithMapOfAnything(map[string]interface{}{"operationId": "spaceRuleAdd"})
_ = reflector.SetRequest(&opSpaceRuleAdd, struct {
spaceRequest
rules.CreateInput
// overshadow "definition"
Type ruleType `json:"type"`
Definition ruleDefinition `json:"definition"`
}{}, http.MethodPost)
_ = reflector.SetJSONResponse(&opSpaceRuleAdd, rule{}, http.StatusCreated)
_ = reflector.SetJSONResponse(&opSpaceRuleAdd, new(usererror.Error), http.StatusInternalServerError)
_ = reflector.SetJSONResponse(&opSpaceRuleAdd, new(usererror.Error), http.StatusUnauthorized)
_ = reflector.SetJSONResponse(&opSpaceRuleAdd, new(usererror.Error), http.StatusForbidden)
_ = reflector.SetJSONResponse(&opSpaceRuleAdd, new(usererror.Error), http.StatusNotFound)
_ = reflector.Spec.AddOperation(http.MethodPost, "/spaces/{space_ref}/rules", opSpaceRuleAdd)
opSpaceRuleDelete := openapi3.Operation{}
opSpaceRuleDelete.WithTags("space")
opSpaceRuleDelete.WithMapOfAnything(map[string]interface{}{"operationId": "spaceRuleDelete"})
_ = reflector.SetRequest(&opSpaceRuleDelete, struct {
spaceRequest
RuleIdentifier string `path:"rule_identifier"`
}{}, http.MethodDelete)
_ = reflector.SetJSONResponse(&opSpaceRuleDelete, nil, http.StatusNoContent)
_ = reflector.SetJSONResponse(&opSpaceRuleDelete, new(usererror.Error), http.StatusInternalServerError)
_ = reflector.SetJSONResponse(&opSpaceRuleDelete, new(usererror.Error), http.StatusUnauthorized)
_ = reflector.SetJSONResponse(&opSpaceRuleDelete, new(usererror.Error), http.StatusForbidden)
_ = reflector.SetJSONResponse(&opSpaceRuleDelete, new(usererror.Error), http.StatusNotFound)
_ = reflector.Spec.AddOperation(http.MethodDelete, "/spaces/{space_ref}/rules/{rule_identifier}", opSpaceRuleDelete)
opSpaceRuleUpdate := openapi3.Operation{}
opSpaceRuleUpdate.WithTags("space")
opSpaceRuleUpdate.WithMapOfAnything(map[string]interface{}{"operationId": "spaceRuleUpdate"})
_ = reflector.SetRequest(&opSpaceRuleUpdate, &struct {
spaceRequest
Identifier string `path:"rule_identifier"`
rules.UpdateInput
// overshadow Type and Definition to enable oneof.
Type ruleType `json:"type"`
Definition ruleDefinition `json:"definition"`
}{}, http.MethodPatch)
_ = reflector.SetJSONResponse(&opSpaceRuleUpdate, rule{}, http.StatusOK)
_ = reflector.SetJSONResponse(&opSpaceRuleUpdate, new(usererror.Error), http.StatusInternalServerError)
_ = reflector.SetJSONResponse(&opSpaceRuleUpdate, new(usererror.Error), http.StatusUnauthorized)
_ = reflector.SetJSONResponse(&opSpaceRuleUpdate, new(usererror.Error), http.StatusForbidden)
_ = reflector.SetJSONResponse(&opSpaceRuleUpdate, new(usererror.Error), http.StatusNotFound)
_ = reflector.Spec.AddOperation(http.MethodPatch, "/spaces/{space_ref}/rules/{rule_identifier}", opSpaceRuleUpdate)
opSpaceRuleList := openapi3.Operation{}
opSpaceRuleList.WithTags("space")
opSpaceRuleList.WithMapOfAnything(map[string]interface{}{"operationId": "spaceRuleList"})
opSpaceRuleList.WithParameters(
queryParameterQueryRuleList,
queryParameterOrder, queryParameterSortRuleList,
QueryParameterPage, QueryParameterLimit)
_ = reflector.SetRequest(&opSpaceRuleList, &struct {
spaceRequest
}{}, http.MethodGet)
_ = reflector.SetJSONResponse(&opSpaceRuleList, []rule{}, http.StatusOK)
_ = reflector.SetJSONResponse(&opSpaceRuleList, new(usererror.Error), http.StatusInternalServerError)
_ = reflector.SetJSONResponse(&opSpaceRuleList, new(usererror.Error), http.StatusUnauthorized)
_ = reflector.SetJSONResponse(&opSpaceRuleList, new(usererror.Error), http.StatusForbidden)
_ = reflector.SetJSONResponse(&opSpaceRuleList, new(usererror.Error), http.StatusNotFound)
_ = reflector.Spec.AddOperation(http.MethodGet, "/spaces/{space_ref}/rules", opSpaceRuleList)
opSpaceRuleGet := openapi3.Operation{}
opSpaceRuleGet.WithTags("space")
opSpaceRuleGet.WithMapOfAnything(map[string]interface{}{"operationId": "spaceRuleGet"})
_ = reflector.SetRequest(&opSpaceRuleGet, &struct {
spaceRequest
Identifier string `path:"rule_identifier"`
}{}, http.MethodGet)
_ = reflector.SetJSONResponse(&opSpaceRuleGet, rule{}, http.StatusOK)
_ = reflector.SetJSONResponse(&opSpaceRuleGet, new(usererror.Error), http.StatusInternalServerError)
_ = reflector.SetJSONResponse(&opSpaceRuleGet, new(usererror.Error), http.StatusUnauthorized)
_ = reflector.SetJSONResponse(&opSpaceRuleGet, new(usererror.Error), http.StatusForbidden)
_ = reflector.SetJSONResponse(&opSpaceRuleGet, new(usererror.Error), http.StatusNotFound)
_ = reflector.Spec.AddOperation(http.MethodGet, "/spaces/{space_ref}/rules/{rule_identifier}", opSpaceRuleGet)
opRepoRuleAdd := openapi3.Operation{}
opRepoRuleAdd.WithTags("repository")
opRepoRuleAdd.WithMapOfAnything(map[string]interface{}{"operationId": "repoRuleAdd"})
_ = reflector.SetRequest(&opRepoRuleAdd, struct {
repoRequest
rules.CreateInput
// overshadow "definition"
Type ruleType `json:"type"`
Definition ruleDefinition `json:"definition"`
}{}, http.MethodPost)
_ = reflector.SetJSONResponse(&opRepoRuleAdd, rule{}, http.StatusCreated)
_ = reflector.SetJSONResponse(&opRepoRuleAdd, new(usererror.Error), http.StatusInternalServerError)
_ = reflector.SetJSONResponse(&opRepoRuleAdd, new(usererror.Error), http.StatusUnauthorized)
_ = reflector.SetJSONResponse(&opRepoRuleAdd, new(usererror.Error), http.StatusForbidden)
_ = reflector.SetJSONResponse(&opRepoRuleAdd, new(usererror.Error), http.StatusNotFound)
_ = reflector.Spec.AddOperation(http.MethodPost, "/repos/{repo_ref}/rules", opRepoRuleAdd)
opRepoRuleDelete := openapi3.Operation{}
opRepoRuleDelete.WithTags("repository")
opRepoRuleDelete.WithMapOfAnything(map[string]interface{}{"operationId": "repoRuleDelete"})
_ = reflector.SetRequest(&opRepoRuleDelete, struct {
repoRequest
RuleIdentifier string `path:"rule_identifier"`
}{}, http.MethodDelete)
_ = reflector.SetJSONResponse(&opRepoRuleDelete, nil, http.StatusNoContent)
_ = reflector.SetJSONResponse(&opRepoRuleDelete, new(usererror.Error), http.StatusInternalServerError)
_ = reflector.SetJSONResponse(&opRepoRuleDelete, new(usererror.Error), http.StatusUnauthorized)
_ = reflector.SetJSONResponse(&opRepoRuleDelete, new(usererror.Error), http.StatusForbidden)
_ = reflector.SetJSONResponse(&opRepoRuleDelete, new(usererror.Error), http.StatusNotFound)
_ = reflector.Spec.AddOperation(http.MethodDelete, "/repos/{repo_ref}/rules/{rule_identifier}", opRepoRuleDelete)
opRepoRuleUpdate := openapi3.Operation{}
opRepoRuleUpdate.WithTags("repository")
opRepoRuleUpdate.WithMapOfAnything(map[string]interface{}{"operationId": "repoRuleUpdate"})
_ = reflector.SetRequest(&opRepoRuleUpdate, &struct {
repoRequest
Identifier string `path:"rule_identifier"`
rules.UpdateInput
// overshadow Type and Definition to enable oneof.
Type ruleType `json:"type"`
Definition ruleDefinition `json:"definition"`
}{}, http.MethodPatch)
_ = reflector.SetJSONResponse(&opRepoRuleUpdate, rule{}, http.StatusOK)
_ = reflector.SetJSONResponse(&opRepoRuleUpdate, new(usererror.Error), http.StatusInternalServerError)
_ = reflector.SetJSONResponse(&opRepoRuleUpdate, new(usererror.Error), http.StatusUnauthorized)
_ = reflector.SetJSONResponse(&opRepoRuleUpdate, new(usererror.Error), http.StatusForbidden)
_ = reflector.SetJSONResponse(&opRepoRuleUpdate, new(usererror.Error), http.StatusNotFound)
_ = reflector.Spec.AddOperation(http.MethodPatch, "/repos/{repo_ref}/rules/{rule_identifier}", opRepoRuleUpdate)
opRepoRuleList := openapi3.Operation{}
opRepoRuleList.WithTags("repository")
opRepoRuleList.WithMapOfAnything(map[string]interface{}{"operationId": "repoRuleList"})
opRepoRuleList.WithParameters(
queryParameterQueryRuleList,
queryParameterOrder, queryParameterSortRuleList,
QueryParameterPage, QueryParameterLimit)
_ = reflector.SetRequest(&opRepoRuleList, &struct {
repoRequest
}{}, http.MethodGet)
_ = reflector.SetJSONResponse(&opRepoRuleList, []rule{}, http.StatusOK)
_ = reflector.SetJSONResponse(&opRepoRuleList, new(usererror.Error), http.StatusInternalServerError)
_ = reflector.SetJSONResponse(&opRepoRuleList, new(usererror.Error), http.StatusUnauthorized)
_ = reflector.SetJSONResponse(&opRepoRuleList, new(usererror.Error), http.StatusForbidden)
_ = reflector.SetJSONResponse(&opRepoRuleList, new(usererror.Error), http.StatusNotFound)
_ = reflector.Spec.AddOperation(http.MethodGet, "/repos/{repo_ref}/rules", opRepoRuleList)
opRepoRuleGet := openapi3.Operation{}
opRepoRuleGet.WithTags("repository")
opRepoRuleGet.WithMapOfAnything(map[string]interface{}{"operationId": "repoRuleGet"})
_ = reflector.SetRequest(&opRepoRuleGet, &struct {
repoRequest
Identifier string `path:"rule_identifier"`
}{}, http.MethodGet)
_ = reflector.SetJSONResponse(&opRepoRuleGet, rule{}, http.StatusOK)
_ = reflector.SetJSONResponse(&opRepoRuleGet, new(usererror.Error), http.StatusInternalServerError)
_ = reflector.SetJSONResponse(&opRepoRuleGet, new(usererror.Error), http.StatusUnauthorized)
_ = reflector.SetJSONResponse(&opRepoRuleGet, new(usererror.Error), http.StatusForbidden)
_ = reflector.SetJSONResponse(&opRepoRuleGet, new(usererror.Error), http.StatusNotFound)
_ = reflector.Spec.AddOperation(http.MethodGet, "/repos/{repo_ref}/rules/{rule_identifier}", opRepoRuleGet)
}

View File

@ -293,6 +293,7 @@ func setupSpaces(
SetupSpaceLabels(r, spaceCtrl)
SetupWebhookSpace(r, webhookCtrl)
SetupRulesSpace(r, spaceCtrl)
})
})
}
@ -341,6 +342,19 @@ func SetupWebhookSpace(r chi.Router, webhookCtrl *webhook.Controller) {
})
}
func SetupRulesSpace(r chi.Router, spaceCtrl *space.Controller) {
r.Route("/rules", func(r chi.Router) {
r.Post("/", handlerspace.HandleRuleCreate(spaceCtrl))
r.Get("/", handlerspace.HandleRuleList(spaceCtrl))
r.Route(fmt.Sprintf("/{%s}", request.PathParamRuleIdentifier), func(r chi.Router) {
r.Patch("/", handlerspace.HandleRuleUpdate(spaceCtrl))
r.Delete("/", handlerspace.HandleRuleDelete(spaceCtrl))
r.Get("/", handlerspace.HandleRuleFind(spaceCtrl))
})
})
}
func setupRepos(r chi.Router,
repoCtrl *repo.Controller,
repoSettingsCtrl *reposettings.Controller,
@ -460,7 +474,7 @@ func setupRepos(r chi.Router,
SetupUploads(r, uploadCtrl)
SetupRules(r, repoCtrl)
SetupRulesRepo(r, repoCtrl)
SetupRepoLabels(r, repoCtrl)
})
@ -742,7 +756,7 @@ func SetupChecks(r chi.Router, checkCtrl *check.Controller) {
})
}
func SetupRules(r chi.Router, repoCtrl *repo.Controller) {
func SetupRulesRepo(r chi.Router, repoCtrl *repo.Controller) {
r.Route("/rules", func(r chi.Router) {
r.Post("/", handlerrepo.HandleRuleCreate(repoCtrl))
r.Get("/", handlerrepo.HandleRuleList(repoCtrl))

View File

@ -42,6 +42,9 @@ const (
PropertyIsDefaultBranch Property = "is_default_branch"
PropertyDecision Property = "decision"
PropertyRepositories Property = "repositories"
PropertySpaceID Property = "space_id"
PropertySpaceName Property = "space_name"
)
type EventType string

View File

@ -0,0 +1,96 @@
// Copyright 2023 Harness, Inc.
//
// 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 rules
import (
"context"
"fmt"
"github.com/harness/gitness/app/services/protection"
"github.com/harness/gitness/types"
)
func (s *Service) getRuleUserAndUserGroups(
ctx context.Context,
rule *types.Rule,
) (map[int64]*types.PrincipalInfo, map[int64]*types.UserGroupInfo, error) {
protection, err := s.parseRule(rule)
if err != nil {
return nil, nil, fmt.Errorf("failed to parse rule: %w", err)
}
userMap, err := s.getRuleUsers(ctx, protection)
if err != nil {
return nil, nil, fmt.Errorf("failed to get rule users: %w", err)
}
userGroupMap, err := s.getRuleUserGroups(ctx, protection)
if err != nil {
return nil, nil, fmt.Errorf("failed to get rule user groups: %w", err)
}
return userMap, userGroupMap, nil
}
func (s *Service) getRuleUsers(
ctx context.Context,
protection protection.Protection,
) (map[int64]*types.PrincipalInfo, error) {
userIDs, err := protection.UserIDs()
if err != nil {
return nil, fmt.Errorf("failed to get user ID from rule: %w", err)
}
userMap, err := s.principalInfoCache.Map(ctx, userIDs)
if err != nil {
return nil, fmt.Errorf("failed to get principal infos: %w", err)
}
return userMap, nil
}
func (s *Service) getRuleUserGroups(
ctx context.Context,
protection protection.Protection,
) (map[int64]*types.UserGroupInfo, error) {
groupIDs, err := protection.UserGroupIDs()
if err != nil {
return nil, fmt.Errorf("failed to get group IDs from rule: %w", err)
}
userGroupInfoMap := make(map[int64]*types.UserGroupInfo)
if len(groupIDs) == 0 {
return userGroupInfoMap, nil
}
groupMap, err := s.userGroupStore.Map(ctx, groupIDs)
if err != nil {
return nil, fmt.Errorf("failed to get userGroup infos: %w", err)
}
for k, v := range groupMap {
userGroupInfoMap[k] = v.ToUserGroupInfo()
}
return userGroupInfoMap, nil
}
func (s *Service) parseRule(rule *types.Rule) (protection.Protection, error) {
protection, err := s.protectionManager.FromJSON(rule.Type, rule.Definition, false)
if err != nil {
return nil, fmt.Errorf("failed to parse json rule definition: %w", err)
}
return protection, nil
}

View File

@ -0,0 +1,197 @@
// Copyright 2023 Harness, Inc.
//
// 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 rules
import (
"context"
"encoding/json"
"fmt"
"time"
"github.com/harness/gitness/app/api/usererror"
"github.com/harness/gitness/app/paths"
"github.com/harness/gitness/app/services/instrument"
"github.com/harness/gitness/app/services/protection"
"github.com/harness/gitness/audit"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/check"
"github.com/harness/gitness/types/enum"
"github.com/rs/zerolog/log"
)
type CreateInput struct {
Type types.RuleType `json:"type"`
State enum.RuleState `json:"state"`
// TODO [CODE-1363]: remove after identifier migration.
UID string `json:"uid" deprecated:"true"`
Identifier string `json:"identifier"`
Description string `json:"description"`
Pattern protection.Pattern `json:"pattern"`
Definition json.RawMessage `json:"definition"`
}
// sanitize validates and sanitizes the create rule input data.
func (in *CreateInput) sanitize() error {
// TODO [CODE-1363]: remove after identifier migration.
if in.Identifier == "" {
in.Identifier = in.UID
}
if err := check.Identifier(in.Identifier); err != nil {
return err
}
if err := in.Pattern.Validate(); err != nil {
return usererror.BadRequestf("invalid pattern: %s", err)
}
var ok bool
in.State, ok = in.State.Sanitize()
if !ok {
return usererror.BadRequest("rule state is invalid")
}
if in.Type == "" {
in.Type = protection.TypeBranch
}
if len(in.Definition) == 0 {
return usererror.BadRequest("rule definition missing")
}
return nil
}
// Create creates a new protection rule for a scope.
func (s *Service) Create(ctx context.Context,
principal *types.Principal,
parentType enum.RuleParent,
parentID int64,
scopeIdentifier string,
path string,
in *CreateInput,
) (*types.Rule, error) {
if err := in.sanitize(); err != nil {
return nil, err
}
var err error
in.Definition, err = s.protectionManager.SanitizeJSON(in.Type, in.Definition)
if err != nil {
return nil, usererror.BadRequestf("invalid rule definition: %s", err.Error())
}
now := time.Now().UnixMilli()
rule := &types.Rule{
CreatedBy: principal.ID,
Created: now,
Updated: now,
Type: in.Type,
State: in.State,
Identifier: in.Identifier,
Description: in.Description,
Pattern: in.Pattern.JSON(),
Definition: in.Definition,
CreatedByInfo: types.PrincipalInfo{},
}
nameKey := audit.RepoName
if parentType == enum.RuleParentRepo {
rule.RepoID = &parentID
} else if parentType == enum.RuleParentSpace {
nameKey = audit.SpaceName
rule.SpaceID = &parentID
}
err = s.ruleStore.Create(ctx, rule)
if err != nil {
return nil, fmt.Errorf("failed to create protection rule: %w", err)
}
err = s.auditService.Log(ctx,
*principal,
audit.NewResource(audit.ResourceTypeBranchRule, rule.Identifier, nameKey, scopeIdentifier),
audit.ActionCreated,
paths.Parent(path),
audit.WithNewObject(rule),
)
if err != nil {
log.Ctx(ctx).Warn().Msgf("failed to insert audit log for create branch rule operation: %s", err)
}
userMap, userGroupMap, err := s.getRuleUserAndUserGroups(ctx, rule)
if err != nil {
return nil, fmt.Errorf("failed to get rule users and user groups: %w", err)
}
rule.Users = userMap
rule.UserGroups = userGroupMap
var event instrument.Event
if parentType == enum.RuleParentRepo {
event = instrumentEventRepo(
rule.ID, principal.ToPrincipalInfo(), parentID, scopeIdentifier, path,
)
} else if parentType == enum.RuleParentSpace {
event = instrumentEventSpace(
rule.ID, principal.ToPrincipalInfo(), parentID, scopeIdentifier, path,
)
}
err = s.instrumentation.Track(ctx, event)
if err != nil {
log.Ctx(ctx).Warn().Msgf("failed to insert instrumentation record for create branch rule operation: %s", err)
}
return rule, nil
}
func instrumentEventRepo(
ruleID int64,
principalInfo *types.PrincipalInfo,
scopeID int64,
scopeIdentifier string,
path string,
) instrument.Event {
return instrument.Event{
Type: instrument.EventTypeCreateBranchRule,
Principal: principalInfo,
Path: path,
Properties: map[instrument.Property]any{
instrument.PropertyRepositoryID: scopeID,
instrument.PropertyRepositoryName: scopeIdentifier,
instrument.PropertyRuleID: ruleID,
},
}
}
func instrumentEventSpace(
ruleID int64,
principalInfo *types.PrincipalInfo,
scopeID int64,
scopeIdentifier string,
path string,
) instrument.Event {
return instrument.Event{
Type: instrument.EventTypeCreateBranchRule,
Principal: principalInfo,
Path: path,
Properties: map[instrument.Property]any{
instrument.PropertySpaceID: scopeID,
instrument.PropertySpaceName: scopeIdentifier,
instrument.PropertyRuleID: ruleID,
},
}
}

View File

@ -0,0 +1,69 @@
// Copyright 2023 Harness, Inc.
//
// 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 rules
import (
"context"
"fmt"
"github.com/harness/gitness/app/paths"
"github.com/harness/gitness/audit"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/enum"
"github.com/rs/zerolog/log"
)
// Delete deletes a protection rule by identifier.
func (s *Service) Delete(ctx context.Context,
principal *types.Principal,
parentType enum.RuleParent,
parentID int64,
scopeIdentifier string,
path string,
identifier string,
) error {
rule, err := s.ruleStore.FindByIdentifier(ctx, parentType, parentID, identifier)
if err != nil {
return fmt.Errorf("failed to find protection rule by identifier: %w", err)
}
err = s.ruleStore.Delete(ctx, rule.ID)
if err != nil {
return fmt.Errorf("failed to delete protection rule: %w", err)
}
nameKey := audit.RepoName
if parentType == enum.RuleParentSpace {
nameKey = audit.SpaceName
}
err = s.auditService.Log(ctx,
*principal,
audit.NewResource(
audit.ResourceTypeBranchRule,
rule.Identifier,
nameKey,
scopeIdentifier,
),
audit.ActionDeleted,
paths.Parent(path),
audit.WithOldObject(rule),
)
if err != nil {
log.Ctx(ctx).Warn().Msgf("failed to insert audit log for delete branch rule operation: %s", err)
}
return nil
}

View File

@ -0,0 +1,45 @@
// Copyright 2023 Harness, Inc.
//
// 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 rules
import (
"context"
"fmt"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/enum"
)
// Find returns the protection rule by identifier.
func (s *Service) Find(ctx context.Context,
parentType enum.RuleParent,
parentID int64,
identifier string,
) (*types.Rule, error) {
rule, err := s.ruleStore.FindByIdentifier(ctx, parentType, parentID, identifier)
if err != nil {
return nil, fmt.Errorf("failed to find protection rule by identifier: %w", err)
}
userMap, userGroupMap, err := s.getRuleUserAndUserGroups(ctx, rule)
if err != nil {
return nil, fmt.Errorf("failed to get rule users and user groups: %w", err)
}
rule.Users = userMap
rule.UserGroups = userGroupMap
return rule, nil
}

147
app/services/rules/list.go Normal file
View File

@ -0,0 +1,147 @@
// Copyright 2023 Harness, Inc.
//
// 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 rules
import (
"context"
"fmt"
"github.com/harness/gitness/store/database/dbtx"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/enum"
)
// List returns protection rules for a scope.
func (s *Service) List(ctx context.Context,
parentID int64,
parentType enum.RuleParent,
inherited bool,
filter *types.RuleFilter,
) ([]types.Rule, int64, error) {
var parents []types.RuleParentInfo
var err error
switch parentType {
case enum.RuleParentRepo:
parents, err = s.getParentInfoRepo(ctx, parentID, inherited)
if err != nil {
return nil, 0, err
}
case enum.RuleParentSpace:
parents, err = s.getParentInfoSpace(ctx, parentID, inherited)
if err != nil {
return nil, 0, err
}
default:
return nil, 0, fmt.Errorf("webhook type %s is not supported", parentType)
}
var list []types.Rule
var count int64
err = s.tx.WithTx(ctx, func(ctx context.Context) error {
list, err = s.ruleStore.List(ctx, parents, filter)
if err != nil {
return fmt.Errorf("failed to list protection rules: %w", err)
}
if filter.Page == 1 && len(list) < filter.Size {
count = int64(len(list))
return nil
}
count, err = s.ruleStore.Count(ctx, parents, filter)
if err != nil {
return fmt.Errorf("failed to count protection rules: %w", err)
}
return nil
}, dbtx.TxDefaultReadOnly)
if err != nil {
return nil, 0, err
}
for i := range list {
list[i].Users, list[i].UserGroups, err = s.getRuleUserAndUserGroups(ctx, &list[i])
if err != nil {
return nil, 0, err
}
}
return list, count, nil
}
func (s *Service) getParentInfoRepo(
ctx context.Context,
repoID int64,
inherited bool,
) ([]types.RuleParentInfo, error) {
var parents []types.RuleParentInfo
parents = append(parents, types.RuleParentInfo{
ID: repoID,
Type: enum.RuleParentRepo,
})
if inherited {
repo, err := s.repoStore.Find(ctx, repoID)
if err != nil {
return nil, fmt.Errorf("failed to get repo: %w", err)
}
ids, err := s.spaceStore.GetAncestorIDs(ctx, repo.ParentID)
if err != nil {
return nil, fmt.Errorf("failed to get parent space ids: %w", err)
}
for _, id := range ids {
parents = append(parents, types.RuleParentInfo{
Type: enum.RuleParentSpace,
ID: id,
})
}
}
return parents, nil
}
func (s *Service) getParentInfoSpace(
ctx context.Context,
spaceID int64,
inherited bool,
) ([]types.RuleParentInfo, error) {
var parents []types.RuleParentInfo
if inherited {
ids, err := s.spaceStore.GetAncestorIDs(ctx, spaceID)
if err != nil {
return nil, fmt.Errorf("failed to get parent space ids: %w", err)
}
for _, id := range ids {
parents = append(parents, types.RuleParentInfo{
Type: enum.RuleParentSpace,
ID: id,
})
}
} else {
parents = append(parents, types.RuleParentInfo{
Type: enum.RuleParentSpace,
ID: spaceID,
})
}
return parents, nil
}

View File

@ -0,0 +1,66 @@
// Copyright 2023 Harness, Inc.
//
// 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 rules
import (
"github.com/harness/gitness/app/services/instrument"
"github.com/harness/gitness/app/services/protection"
"github.com/harness/gitness/app/services/usergroup"
"github.com/harness/gitness/app/store"
"github.com/harness/gitness/audit"
"github.com/harness/gitness/store/database/dbtx"
)
// Service is responsible for processing rules.
type Service struct {
tx dbtx.Transactor
ruleStore store.RuleStore
repoStore store.RepoStore
spaceStore store.SpaceStore
protectionManager *protection.Manager
auditService audit.Service
instrumentation instrument.Service
principalInfoCache store.PrincipalInfoCache
userGroupStore store.UserGroupStore
userGroupService usergroup.SearchService
}
func NewService(
tx dbtx.Transactor,
ruleStore store.RuleStore,
repoStore store.RepoStore,
spaceStore store.SpaceStore,
protectionManager *protection.Manager,
auditService audit.Service,
instrumentation instrument.Service,
principalInfoCache store.PrincipalInfoCache,
userGroupStore store.UserGroupStore,
userGroupService usergroup.SearchService,
) *Service {
return &Service{
tx: tx,
ruleStore: ruleStore,
repoStore: repoStore,
spaceStore: spaceStore,
protectionManager: protectionManager,
auditService: auditService,
instrumentation: instrumentation,
principalInfoCache: principalInfoCache,
userGroupStore: userGroupStore,
userGroupService: userGroupService,
}
}

View File

@ -0,0 +1,167 @@
// Copyright 2023 Harness, Inc.
//
// 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 rules
import (
"context"
"encoding/json"
"fmt"
"github.com/harness/gitness/app/api/usererror"
"github.com/harness/gitness/app/paths"
"github.com/harness/gitness/app/services/protection"
"github.com/harness/gitness/audit"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/check"
"github.com/harness/gitness/types/enum"
"github.com/rs/zerolog/log"
)
type UpdateInput struct {
// TODO [CODE-1363]: remove after identifier migration.
UID *string `json:"uid" deprecated:"true"`
Identifier *string `json:"identifier"`
State *enum.RuleState `json:"state"`
Description *string `json:"description"`
Pattern *protection.Pattern `json:"pattern"`
Definition *json.RawMessage `json:"definition"`
}
// sanitize validates and sanitizes the update rule input data.
func (in *UpdateInput) sanitize() error {
// TODO [CODE-1363]: remove after identifier migration.
if in.Identifier == nil {
in.Identifier = in.UID
}
if in.Identifier != nil {
if err := check.Identifier(*in.Identifier); err != nil {
return err
}
}
if in.State != nil {
state, ok := in.State.Sanitize()
if !ok {
return usererror.BadRequest("rule state is invalid")
}
in.State = &state
}
if in.Pattern != nil {
if err := in.Pattern.Validate(); err != nil {
return usererror.BadRequestf("invalid pattern: %s", err)
}
}
if in.Definition != nil && len(*in.Definition) == 0 {
return usererror.BadRequest("rule definition missing")
}
return nil
}
func (in *UpdateInput) isEmpty() bool {
return in.Identifier == nil && in.State == nil && in.Description == nil && in.Pattern == nil && in.Definition == nil
}
// Update updates an existing protection rule for a repository.
func (s *Service) Update(ctx context.Context,
principal *types.Principal,
parentType enum.RuleParent,
parentID int64,
scopeIdentifier string,
path string,
identifier string,
in *UpdateInput,
) (*types.Rule, error) {
if err := in.sanitize(); err != nil {
return nil, err
}
rule, err := s.ruleStore.FindByIdentifier(ctx, parentType, parentID, identifier)
if err != nil {
return nil, fmt.Errorf("failed to get a repository rule by its identifier: %w", err)
}
oldRule := rule.Clone()
if in.isEmpty() {
userMap, userGroupMap, err := s.getRuleUserAndUserGroups(ctx, rule)
if err != nil {
return nil, fmt.Errorf("failed to get rule users and user groups: %w", err)
}
rule.Users = userMap
rule.UserGroups = userGroupMap
return rule, nil
}
if in.Identifier != nil {
rule.Identifier = *in.Identifier
}
if in.State != nil {
rule.State = *in.State
}
if in.Description != nil {
rule.Description = *in.Description
}
if in.Pattern != nil {
rule.Pattern = in.Pattern.JSON()
}
if in.Definition != nil {
rule.Definition, err = s.protectionManager.SanitizeJSON(rule.Type, *in.Definition)
if err != nil {
return nil, usererror.BadRequestf("invalid rule definition: %s", err.Error())
}
}
userMap, userGroupMap, err := s.getRuleUserAndUserGroups(ctx, rule)
if err != nil {
return nil, fmt.Errorf("failed to get rule users and user groups: %w", err)
}
rule.Users = userMap
rule.UserGroups = userGroupMap
if rule.IsEqual(&oldRule) {
return rule, nil
}
err = s.ruleStore.Update(ctx, rule)
if err != nil {
return nil, fmt.Errorf("failed to update repository-level protection rule: %w", err)
}
nameKey := audit.RepoName
if parentType == enum.RuleParentSpace {
nameKey = audit.SpaceName
}
err = s.auditService.Log(ctx,
*principal,
audit.NewResource(audit.ResourceTypeBranchRule, rule.Identifier, nameKey, scopeIdentifier),
audit.ActionUpdated,
paths.Parent(path),
audit.WithOldObject(oldRule),
audit.WithNewObject(rule),
)
if err != nil {
log.Ctx(ctx).Warn().Msgf("failed to insert audit log for update branch rule operation: %s", err)
}
return rule, nil
}

View File

@ -0,0 +1,57 @@
// Copyright 2023 Harness, Inc.
//
// 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 rules
import (
"github.com/harness/gitness/app/services/instrument"
"github.com/harness/gitness/app/services/protection"
"github.com/harness/gitness/app/services/usergroup"
"github.com/harness/gitness/app/store"
"github.com/harness/gitness/audit"
"github.com/harness/gitness/store/database/dbtx"
"github.com/google/wire"
)
// WireSet provides a wire set for this package.
var WireSet = wire.NewSet(
ProvideService,
)
func ProvideService(
tx dbtx.Transactor,
ruleStore store.RuleStore,
repoStore store.RepoStore,
spaceStore store.SpaceStore,
protectionManager *protection.Manager,
auditService audit.Service,
instrumentation instrument.Service,
principalInfoCache store.PrincipalInfoCache,
userGroupStore store.UserGroupStore,
userGroupService usergroup.SearchService,
) *Service {
return NewService(
tx,
ruleStore,
repoStore,
spaceStore,
protectionManager,
auditService,
instrumentation,
principalInfoCache,
userGroupStore,
userGroupService,
)
}

View File

@ -526,7 +526,12 @@ type (
Find(ctx context.Context, id int64) (*types.Rule, error)
// FindByIdentifier finds a protection rule by parent ID and identifier.
FindByIdentifier(ctx context.Context, spaceID, repoID *int64, identifier string) (*types.Rule, error)
FindByIdentifier(
ctx context.Context,
parentType enum.RuleParent,
parentID int64,
identifier string,
) (*types.Rule, error)
// Create inserts a new protection rule.
Create(ctx context.Context, rule *types.Rule) error
@ -537,14 +542,19 @@ type (
// Delete removes a protection rule by its ID.
Delete(ctx context.Context, id int64) error
// DeleteByIdentifier removes a protection rule by its identifier.
DeleteByIdentifier(ctx context.Context, spaceID, repoID *int64, identifier string) error
// Count returns count of protection rules of a repository or a space.
Count(
ctx context.Context,
parents []types.RuleParentInfo,
filter *types.RuleFilter,
) (int64, error)
// Count returns count of protection rules matching the provided criteria.
Count(ctx context.Context, spaceID, repoID *int64, filter *types.RuleFilter) (int64, error)
// List returns a list of protection rules of a repository or a space that matches the provided criteria.
List(ctx context.Context, spaceID, repoID *int64, filter *types.RuleFilter) ([]types.Rule, error)
// List returns a list of protection rules of a repository or a space.
List(
ctx context.Context,
parents []types.RuleParentInfo,
filter *types.RuleFilter,
) ([]types.Rule, error)
// ListAllRepoRules returns a list of all protection rules that can be applied on a repository.
ListAllRepoRules(ctx context.Context, repoID int64) ([]types.RuleInfoInternal, error)

View File

@ -114,15 +114,23 @@ func (s *RuleStore) Find(ctx context.Context, id int64) (*types.Rule, error) {
func (s *RuleStore) FindByIdentifier(
ctx context.Context,
spaceID *int64,
repoID *int64,
parentType enum.RuleParent,
parentID int64,
identifier string,
) (*types.Rule, error) {
stmt := database.Builder.
Select(ruleColumns).
From("rules").
Where("LOWER(rule_uid) = ?", strings.ToLower(identifier))
stmt = s.applyParentID(stmt, spaceID, repoID)
switch parentType {
case enum.RuleParentRepo:
stmt = stmt.Where("rule_repo_id = ?", parentID)
case enum.RuleParentSpace:
stmt = stmt.Where("rule_space_id = ?", parentID)
default:
return nil, fmt.Errorf("rule parent type '%s' is not supported", parentType)
}
sql, args, err := stmt.ToSql()
if err != nil {
@ -252,40 +260,21 @@ func (s *RuleStore) Delete(ctx context.Context, id int64) error {
return nil
}
func (s *RuleStore) DeleteByIdentifier(ctx context.Context, spaceID, repoID *int64, identifier string) error {
stmt := database.Builder.
Delete("rules").
Where("LOWER(rule_uid) = ?", strings.ToLower(identifier))
if spaceID != nil {
stmt = stmt.Where("rule_space_id = ?", *spaceID)
}
if repoID != nil {
stmt = stmt.Where("rule_repo_id = ?", *repoID)
}
sql, args, err := stmt.ToSql()
if err != nil {
return fmt.Errorf("failed to convert delete rule by identifier to sql: %w", err)
}
db := dbtx.GetAccessor(ctx, s.db)
if _, err = db.ExecContext(ctx, sql, args...); err != nil {
return database.ProcessSQLErrorf(ctx, err, "Failed executing delete rule by identifier query")
}
return nil
}
// Count returns count of protection rules matching the provided criteria.
func (s *RuleStore) Count(ctx context.Context, spaceID, repoID *int64, filter *types.RuleFilter) (int64, error) {
func (s *RuleStore) Count(
ctx context.Context,
parents []types.RuleParentInfo,
filter *types.RuleFilter,
) (int64, error) {
stmt := database.Builder.
Select("count(*)").
From("rules")
stmt = s.applyParentID(stmt, spaceID, repoID)
err := selectRuleParents(parents, &stmt)
if err != nil {
return 0, fmt.Errorf("failed to select rule parents: %w", err)
}
stmt = s.applyFilter(stmt, filter)
sql, args, err := stmt.ToSql()
@ -306,12 +295,20 @@ func (s *RuleStore) Count(ctx context.Context, spaceID, repoID *int64, filter *t
}
// List returns a list of protection rules of a repository or a space.
func (s *RuleStore) List(ctx context.Context, spaceID, repoID *int64, filter *types.RuleFilter) ([]types.Rule, error) {
func (s *RuleStore) List(
ctx context.Context,
parents []types.RuleParentInfo,
filter *types.RuleFilter,
) ([]types.Rule, error) {
stmt := database.Builder.
Select(ruleColumns).
From("rules")
stmt = s.applyParentID(stmt, spaceID, repoID)
err := selectRuleParents(parents, &stmt)
if err != nil {
return nil, fmt.Errorf("failed to select rule parents: %w", err)
}
stmt = s.applyFilter(stmt, filter)
stmt = stmt.Limit(database.Limit(filter.Size))
@ -427,21 +424,6 @@ func (s *RuleStore) ListAllRepoRules(ctx context.Context, repoID int64) ([]types
return s.mapToRuleInfos(result), nil
}
func (*RuleStore) applyParentID(
stmt squirrel.SelectBuilder,
spaceID, repoID *int64,
) squirrel.SelectBuilder {
if spaceID != nil {
stmt = stmt.Where("rule_space_id = ?", *spaceID)
}
if repoID != nil {
stmt = stmt.Where("rule_repo_id = ?", *repoID)
}
return stmt
}
func (*RuleStore) applyFilter(
stmt squirrel.SelectBuilder,
filter *types.RuleFilter,
@ -544,3 +526,28 @@ func (s *RuleStore) mapToRuleInfos(
}
return res
}
func selectRuleParents(
parents []types.RuleParentInfo,
stmt *squirrel.SelectBuilder,
) error {
var parentSelector squirrel.Or
for _, parent := range parents {
switch parent.Type {
case enum.RuleParentRepo:
parentSelector = append(parentSelector, squirrel.Eq{
"rule_repo_id": parent.ID,
})
case enum.RuleParentSpace:
parentSelector = append(parentSelector, squirrel.Eq{
"rule_space_id": parent.ID,
})
default:
return fmt.Errorf("rule parent type '%s' is not supported", parent.Type)
}
}
*stmt = stmt.Where(parentSelector)
return nil
}

View File

@ -352,9 +352,9 @@ func (s *WebhookStore) Count(
Select("count(*)").
From("webhooks")
err := selectParents(parents, &stmt)
err := selectWebhookParents(parents, &stmt)
if err != nil {
return 0, fmt.Errorf("failed to select parents: %w", err)
return 0, fmt.Errorf("failed to select webhook parents: %w", err)
}
stmt = applyWebhookFilter(opts, stmt)
@ -384,9 +384,9 @@ func (s *WebhookStore) List(
Select(webhookColumns).
From("webhooks")
err := selectParents(parents, &stmt)
err := selectWebhookParents(parents, &stmt)
if err != nil {
return nil, fmt.Errorf("failed to select parents: %w", err)
return nil, fmt.Errorf("failed to select webhook parents: %w", err)
}
stmt = applyWebhookFilter(opts, stmt)
@ -559,7 +559,7 @@ func applyWebhookFilter(
return stmt
}
func selectParents(
func selectWebhookParents(
parents []types.WebhookParentInfo,
stmt *squirrel.SelectBuilder,
) error {

View File

@ -33,6 +33,7 @@ var (
const (
ResourceName = "resourceName"
RepoName = "repoName"
SpaceName = "spaceName"
BypassedResourceType = "bypassedResourceType"
BypassedResourceName = "bypassedResourceName"
RepoPath = "repoPath"

View File

@ -104,6 +104,7 @@ import (
"github.com/harness/gitness/app/services/publickey"
pullreqservice "github.com/harness/gitness/app/services/pullreq"
reposervice "github.com/harness/gitness/app/services/repo"
"github.com/harness/gitness/app/services/rules"
secretservice "github.com/harness/gitness/app/services/secret"
"github.com/harness/gitness/app/services/settings"
systemsvc "github.com/harness/gitness/app/services/system"
@ -243,6 +244,7 @@ func initSystem(ctx context.Context, config *types.Config) (*cliserver.System, e
gitspaceevent.WireSet,
cliserver.ProvideKeywordSearchConfig,
keywordsearch.WireSet,
rules.WireSet,
controllerkeywordsearch.WireSet,
settings.WireSet,
systemsvc.WireSet,

View File

@ -95,6 +95,7 @@ import (
"github.com/harness/gitness/app/services/publickey"
"github.com/harness/gitness/app/services/pullreq"
repo2 "github.com/harness/gitness/app/services/repo"
"github.com/harness/gitness/app/services/rules"
secret3 "github.com/harness/gitness/app/services/secret"
"github.com/harness/gitness/app/services/settings"
system2 "github.com/harness/gitness/app/services/system"
@ -251,7 +252,8 @@ func initSystem(ctx context.Context, config *types.Config) (*server.System, erro
instrumentService := instrument.ProvideService()
userGroupStore := database.ProvideUserGroupStore(db)
searchService := usergroup.ProvideSearchService()
repoController := repo.ProvideController(config, transactor, provider, authorizer, repoStore, spaceStore, pipelineStore, principalStore, executionStore, ruleStore, checkStore, pullReqStore, settingsService, principalInfoCache, protectionManager, gitInterface, repository, codeownersService, reporter, indexer, resourceLimiter, lockerLocker, auditService, mutexManager, repoIdentifier, repoCheck, publicaccessService, labelService, instrumentService, userGroupStore, searchService)
rulesService := rules.ProvideService(transactor, ruleStore, repoStore, spaceStore, protectionManager, auditService, instrumentService, principalInfoCache, userGroupStore, searchService)
repoController := repo.ProvideController(config, transactor, provider, authorizer, repoStore, spaceStore, pipelineStore, principalStore, executionStore, ruleStore, checkStore, pullReqStore, settingsService, principalInfoCache, protectionManager, gitInterface, repository, codeownersService, reporter, indexer, resourceLimiter, lockerLocker, auditService, mutexManager, repoIdentifier, repoCheck, publicaccessService, labelService, instrumentService, userGroupStore, searchService, rulesService)
reposettingsController := reposettings.ProvideController(authorizer, repoStore, settingsService, auditService)
stageStore := database.ProvideStageStore(db)
schedulerScheduler, err := scheduler.ProvideScheduler(stageStore, mutexManager)
@ -324,7 +326,7 @@ func initSystem(ctx context.Context, config *types.Config) (*server.System, erro
resolverFactory := secret.ProvideResolverFactory(passwordResolver)
orchestratorOrchestrator := orchestrator.ProvideOrchestrator(scmSCM, infraProviderResourceStore, infraProvisioner, containerOrchestrator, eventsReporter, orchestratorConfig, vsCode, vsCodeWeb, resolverFactory)
gitspaceService := gitspace.ProvideGitspace(transactor, gitspaceConfigStore, gitspaceInstanceStore, eventsReporter, gitspaceEventStore, spaceStore, infraproviderService, orchestratorOrchestrator, scmSCM, config)
spaceController := space.ProvideController(config, transactor, provider, streamer, spaceIdentifier, authorizer, spacePathStore, pipelineStore, secretStore, connectorStore, templateStore, spaceStore, repoStore, principalStore, repoController, membershipStore, listService, repository, exporterRepository, resourceLimiter, publicaccessService, auditService, gitspaceService, labelService, instrumentService, executionStore)
spaceController := space.ProvideController(config, transactor, provider, streamer, spaceIdentifier, authorizer, spacePathStore, pipelineStore, secretStore, connectorStore, templateStore, spaceStore, repoStore, principalStore, repoController, membershipStore, listService, repository, exporterRepository, resourceLimiter, publicaccessService, auditService, gitspaceService, labelService, instrumentService, executionStore, rulesService)
reporter3, err := events5.ProvideReporter(eventsSystem)
if err != nil {
return nil, err

View File

@ -83,3 +83,21 @@ func ParseRuleSortAttr(s string) RuleSort {
return RuleSortIdentifier
}
// RuleParent defines different types of parents of a rule.
type RuleParent string
func (RuleParent) Enum() []interface{} { return toInterfaceSlice(RuleParents) }
const (
// RuleParentRepo describes a repo as Rule owner.
RuleParentRepo RuleParent = "repo"
// RuleParentSpace describes a space as Rule owner.
RuleParentSpace RuleParent = "space"
)
var RuleParents = sortEnum([]RuleParent{
RuleParentRepo,
RuleParentSpace,
})

View File

@ -15,6 +15,7 @@
package types
import (
"bytes"
"encoding/json"
"fmt"
@ -124,6 +125,12 @@ func (r Rule) Clone() Rule {
return r
}
func (r *Rule) IsEqual(rule *Rule) bool {
return r.Identifier == rule.Identifier && r.State == rule.State &&
r.Description == rule.Description && bytes.Equal(r.Pattern, rule.Pattern) &&
bytes.Equal(r.Definition, rule.Definition)
}
type RuleType string
type RuleFilter struct {
@ -211,3 +218,8 @@ type DryRunRulesOutput struct {
DryRunRules bool `json:"dry_run_rules,omitempty"`
RuleViolations []RuleViolations `json:"rule_violations,omitempty"`
}
type RuleParentInfo struct {
Type enum.RuleParent
ID int64
}