[code-1524] audit trails package (#1176)

gitness-test1
Enver Bisevac 2024-04-08 21:13:57 +00:00 committed by Harness
parent fea9e46dc8
commit 227aac90b5
21 changed files with 585 additions and 30 deletions

View File

@ -34,6 +34,7 @@ import (
"github.com/harness/gitness/app/services/settings"
"github.com/harness/gitness/app/store"
"github.com/harness/gitness/app/url"
"github.com/harness/gitness/audit"
"github.com/harness/gitness/git"
"github.com/harness/gitness/lock"
"github.com/harness/gitness/store/database/dbtx"
@ -68,6 +69,7 @@ type Controller struct {
indexer keywordsearch.Indexer
resourceLimiter limiter.ResourceLimiter
locker *locker.Locker
auditService audit.Service
mtxManager lock.MutexManager
identifierCheck check.RepoIdentifier
repoCheck Check
@ -93,6 +95,7 @@ func NewController(
indexer keywordsearch.Indexer,
limiter limiter.ResourceLimiter,
locker *locker.Locker,
auditService audit.Service,
mtxManager lock.MutexManager,
identifierCheck check.RepoIdentifier,
repoCheck Check,
@ -118,6 +121,7 @@ func NewController(
indexer: indexer,
resourceLimiter: limiter,
locker: locker,
auditService: auditService,
mtxManager: mtxManager,
identifierCheck: identifierCheck,
repoCheck: repoCheck,

View File

@ -28,6 +28,8 @@ import (
"github.com/harness/gitness/app/auth"
"github.com/harness/gitness/app/bootstrap"
"github.com/harness/gitness/app/githook"
"github.com/harness/gitness/app/paths"
"github.com/harness/gitness/audit"
"github.com/harness/gitness/git"
"github.com/harness/gitness/resources"
"github.com/harness/gitness/types"
@ -121,6 +123,17 @@ func (c *Controller) Create(ctx context.Context, session *auth.Session, in *Crea
return nil, err
}
err = c.auditService.Log(ctx,
session.Principal,
audit.NewResource(audit.ResourceTypeRepository, repo.Identifier),
audit.ActionCreated,
paths.Space(repo.Path),
audit.WithNewObject(repo),
)
if err != nil {
log.Ctx(ctx).Warn().Msgf("failed to insert audit log for create repository operation: %s", err)
}
// backfil GitURL
repo.GitURL = c.urlProvider.GenerateGITCloneURL(repo.Path)

View File

@ -23,10 +23,14 @@ import (
"github.com/harness/gitness/app/auth"
"github.com/harness/gitness/app/bootstrap"
repoevents "github.com/harness/gitness/app/events/repo"
"github.com/harness/gitness/app/paths"
"github.com/harness/gitness/audit"
"github.com/harness/gitness/contextutil"
"github.com/harness/gitness/git"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/enum"
"github.com/rs/zerolog/log"
)
type UpdateDefaultBranchInput struct {
@ -44,7 +48,7 @@ func (c *Controller) UpdateDefaultBranch(
if err != nil {
return nil, err
}
repoClone := repo.Clone()
// the max time we give an update default branch to succeed
const timeout = 2 * time.Minute
@ -91,6 +95,18 @@ func (c *Controller) UpdateDefaultBranch(
return nil, fmt.Errorf("failed to update the repo default branch on db:%w", err)
}
err = c.auditService.Log(ctx,
session.Principal,
audit.NewResource(audit.ResourceTypeRepository, repo.Identifier),
audit.ActionUpdated,
paths.Space(repo.Path),
audit.WithOldObject(repoClone),
audit.WithNewObject(repo),
)
if err != nil {
log.Ctx(ctx).Warn().Msgf("failed to insert audit log for update default branch operation: %s", err)
}
c.eventReporter.DefaultBranchUpdated(ctx, &repoevents.DefaultBranchUpdatedPayload{
RepoID: repo.ID,
PrincipalID: bootstrap.NewSystemServiceSession().Principal.ID,

View File

@ -22,10 +22,14 @@ import (
"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/types"
"github.com/harness/gitness/types/check"
"github.com/harness/gitness/types/enum"
"github.com/rs/zerolog/log"
)
type RuleCreateInput struct {
@ -112,6 +116,17 @@ func (c *Controller) RuleCreate(ctx context.Context,
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.ActionCreated,
paths.Space(repo.Path),
audit.WithNewObject(r),
)
if err != nil {
log.Ctx(ctx).Warn().Msgf("failed to insert audit log for create branch rule operation: %s", err)
}
r.Users, err = c.getRuleUsers(ctx, r)
if err != nil {
return nil, err

View File

@ -19,7 +19,11 @@ 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.
@ -43,5 +47,16 @@ func (c *Controller) RuleDelete(ctx context.Context,
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.ActionDeleted,
paths.Space(repo.Path),
audit.WithOldObject(r),
)
if err != nil {
log.Ctx(ctx).Warn().Msgf("failed to insert audit log for delete branch rule operation: %s", err)
}
return nil
}

View File

@ -21,10 +21,14 @@ import (
"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/types"
"github.com/harness/gitness/types/check"
"github.com/harness/gitness/types/enum"
"github.com/rs/zerolog/log"
)
type RuleUpdateInput struct {
@ -96,7 +100,7 @@ func (c *Controller) RuleUpdate(ctx context.Context,
if err != nil {
return nil, fmt.Errorf("failed to get a repository rule by its identifier: %w", err)
}
oldRule := r.Clone()
if in.isEmpty() {
r.Users, err = c.getRuleUsers(ctx, r)
if err != nil {
@ -134,5 +138,17 @@ func (c *Controller) RuleUpdate(ctx context.Context,
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.ActionUpdated,
paths.Space(repo.Path),
audit.WithOldObject(oldRule),
audit.WithNewObject(r),
)
if err != nil {
log.Ctx(ctx).Warn().Msgf("failed to insert audit log for update branch rule operation: %s", err)
}
return r, nil
}

View File

@ -22,6 +22,8 @@ import (
apiauth "github.com/harness/gitness/app/api/auth"
"github.com/harness/gitness/app/api/usererror"
"github.com/harness/gitness/app/auth"
"github.com/harness/gitness/app/paths"
"github.com/harness/gitness/audit"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/enum"
@ -62,6 +64,17 @@ func (c *Controller) SoftDelete(
return nil, fmt.Errorf("failed to soft delete repo: %w", err)
}
err = c.auditService.Log(ctx,
session.Principal,
audit.NewResource(audit.ResourceTypeRepository, repo.Identifier),
audit.ActionDeleted,
paths.Space(repo.Path),
audit.WithOldObject(repo),
)
if err != nil {
log.Ctx(ctx).Warn().Msgf("failed to insert audit log for delete repository operation: %s", err)
}
return &SoftDeleteResponse{DeletedAt: now}, nil
}

View File

@ -20,9 +20,13 @@ import (
"strings"
"github.com/harness/gitness/app/auth"
"github.com/harness/gitness/app/paths"
"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"
)
// UpdateInput is used for updating a repo.
@ -47,6 +51,8 @@ func (c *Controller) Update(ctx context.Context,
return nil, err
}
repoClone := repo.Clone()
if !in.hasChanges(repo) {
return repo, nil
}
@ -70,6 +76,18 @@ func (c *Controller) Update(ctx context.Context,
return nil, err
}
err = c.auditService.Log(ctx,
session.Principal,
audit.NewResource(audit.ResourceTypeRepository, repo.Identifier),
audit.ActionUpdated,
paths.Space(repo.Path),
audit.WithOldObject(repoClone),
audit.WithNewObject(repo),
)
if err != nil {
log.Ctx(ctx).Warn().Msgf("failed to insert audit log for update repository operation: %s", err)
}
// backfill repo url
repo.GitURL = c.urlProvider.GenerateGITCloneURL(repo.Path)

View File

@ -26,6 +26,7 @@ import (
"github.com/harness/gitness/app/services/settings"
"github.com/harness/gitness/app/store"
"github.com/harness/gitness/app/url"
"github.com/harness/gitness/audit"
"github.com/harness/gitness/git"
"github.com/harness/gitness/lock"
"github.com/harness/gitness/store/database/dbtx"
@ -60,6 +61,7 @@ func ProvideController(
indexer keywordsearch.Indexer,
limiter limiter.ResourceLimiter,
locker *locker.Locker,
auditService audit.Service,
mtxManager lock.MutexManager,
identifierCheck check.RepoIdentifier,
repoChecks Check,
@ -68,7 +70,7 @@ func ProvideController(
authorizer, repoStore,
spaceStore, pipelineStore,
principalStore, ruleStore, settings, principalInfoCache, protectionManager, rpcClient, importer,
codeOwners, reporeporter, indexer, limiter, locker, mtxManager, identifierCheck, repoChecks)
codeOwners, reporeporter, indexer, limiter, locker, auditService, mtxManager, identifierCheck, repoChecks)
}
func ProvideRepoCheck() Check {

View File

@ -97,3 +97,8 @@ func IsAncesterOf(path string, other string) bool {
path+types.PathSeparator,
)
}
func Space(repoPath string) string {
spacePath, _, _ := DisectLeaf(repoPath)
return spacePath
}

View File

@ -72,6 +72,7 @@ import (
"github.com/harness/gitness/app/api/request"
"github.com/harness/gitness/app/auth/authn"
"github.com/harness/gitness/app/githook"
"github.com/harness/gitness/audit"
"github.com/harness/gitness/git"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/enum"
@ -141,6 +142,8 @@ func NewAPIHandler(
// for now always attempt auth - enforced per operation.
r.Use(middlewareauthn.Attempt(authenticator))
r.Use(audit.Middleware())
r.Route("/v1", func(r chi.Router) {
setupRoutesV1(r, appCtx, config, repoCtrl, repoSettingsCtrl, executionCtrl, triggerCtrl, logCtrl, pipelineCtrl,
connectorCtrl, templateCtrl, pluginCtrl, secretCtrl, spaceCtrl, pullreqCtrl,

175
audit/audit.go Normal file
View File

@ -0,0 +1,175 @@
// 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 audit
import (
"context"
"errors"
"github.com/harness/gitness/types"
)
var (
ErrActionUndefined = errors.New("undefined action")
ErrResourceTypeUndefined = errors.New("undefined resource type")
ErrResourceIdentifierIsRequired = errors.New("resource identifier is required")
ErrUserIsRequired = errors.New("user is required")
ErrSpacePathIsRequired = errors.New("space path is required")
)
type Action string
const (
ActionCreated Action = "created"
ActionUpdated Action = "updated" // update default branch, switching default branch, updating description
ActionDeleted Action = "deleted"
)
func (a Action) Validate() error {
switch a {
case ActionCreated, ActionUpdated, ActionDeleted:
return nil
default:
return ErrActionUndefined
}
}
type ResourceType string
const (
ResourceTypeRepository ResourceType = "repository"
ResourceTypeBranchRule ResourceType = "branch_rule"
)
func (a ResourceType) Validate() error {
switch a {
case ResourceTypeRepository, ResourceTypeBranchRule:
return nil
default:
return ErrResourceTypeUndefined
}
}
type Resource struct {
Type ResourceType
Identifier string
}
func NewResource(rtype ResourceType, identifier string) Resource {
return Resource{
Type: rtype,
Identifier: identifier,
}
}
func (r Resource) Validate() error {
if err := r.Type.Validate(); err != nil {
return err
}
if r.Identifier == "" {
return ErrResourceIdentifierIsRequired
}
return nil
}
type DiffObject struct {
OldObject any
NewObject any
}
type Event struct {
ID string
Timestamp int64
Action Action // example: ActionCreated
User types.Principal // example: Admin
SpacePath string // example: /root/projects
Resource Resource
DiffObject DiffObject
Data map[string]string // internal data like correlationID/requestID
}
func (e *Event) Validate() error {
if err := e.Action.Validate(); err != nil {
return err
}
if e.User.UID == "" {
return ErrUserIsRequired
}
if e.SpacePath == "" {
return ErrSpacePathIsRequired
}
if err := e.Resource.Validate(); err != nil {
return err
}
return nil
}
type Noop struct{}
func New() *Noop {
return &Noop{}
}
func (s *Noop) Log(
context.Context,
types.Principal,
Resource,
Action,
string,
...Option,
) error {
// No implementation
return nil
}
type FuncOption func(e *Event)
func (f FuncOption) Apply(event *Event) {
f(event)
}
type Option interface {
Apply(e *Event)
}
func WithID(value string) FuncOption {
return func(e *Event) {
e.ID = value
}
}
func WithNewObject(value any) FuncOption {
return func(e *Event) {
e.DiffObject.NewObject = value
}
}
func WithOldObject(value any) FuncOption {
return func(e *Event) {
e.DiffObject.OldObject = value
}
}
func WithData(keyValues ...string) FuncOption {
return func(e *Event) {
if e.Data == nil {
e.Data = make(map[string]string)
}
for i := 0; i < len(keyValues); i += 2 {
k, v := keyValues[i], keyValues[i+1]
e.Data[k] = v
}
}
}

55
audit/context.go Normal file
View File

@ -0,0 +1,55 @@
// 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 audit
import "context"
type key int
const (
realIPKey key = iota
requestID
requestMethod
)
// GetRealIP returns IP address from context.
func GetRealIP(ctx context.Context) string {
ip, ok := ctx.Value(realIPKey).(string)
if !ok {
return ""
}
return ip
}
// GetRequestID returns requestID from context.
func GetRequestID(ctx context.Context) string {
id, ok := ctx.Value(requestID).(string)
if !ok {
return ""
}
return id
}
// GetRequestMethod returns http method from context.
func GetRequestMethod(ctx context.Context) string {
method, ok := ctx.Value(requestMethod).(string)
if !ok {
return ""
}
return method
}

32
audit/interface.go Normal file
View File

@ -0,0 +1,32 @@
// 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 audit
import (
"context"
"github.com/harness/gitness/types"
)
type Service interface {
Log(
ctx context.Context,
user types.Principal,
resource Resource,
action Action,
spacePath string,
options ...Option,
) error
}

68
audit/middleware.go Normal file
View File

@ -0,0 +1,68 @@
// 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 audit
import (
"context"
"net"
"net/http"
"strings"
)
var (
trueClientIP = http.CanonicalHeaderKey("True-Client-IP")
xForwardedFor = http.CanonicalHeaderKey("X-Forwarded-For")
xRealIP = http.CanonicalHeaderKey("X-Real-IP")
)
// Middleware process request headers to fill internal info data.
func Middleware() func(next http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
if rip := realIP(r); rip != "" {
ctx = context.WithValue(ctx, realIPKey, rip)
}
ctx = context.WithValue(ctx, requestMethod, r.Method)
ctx = context.WithValue(ctx, requestID, w.Header().Get("X-Request-Id"))
r = r.WithContext(ctx)
next.ServeHTTP(w, r)
})
}
}
func realIP(r *http.Request) string {
var ip string
if tcip := r.Header.Get(trueClientIP); tcip != "" {
ip = tcip
} else if xrip := r.Header.Get(xRealIP); xrip != "" {
ip = xrip
} else if xff := r.Header.Get(xForwardedFor); xff != "" {
i := strings.Index(xff, ",")
if i == -1 {
i = len(xff)
}
ip = xff[:i]
} else {
ip = strings.Split(r.RemoteAddr, ":")[0]
}
if ip == "" || net.ParseIP(ip) == nil {
return ""
}
return ip
}

25
audit/wire.go Normal file
View File

@ -0,0 +1,25 @@
// 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 audit
import "github.com/google/wire"
var WireSet = wire.NewSet(
ProvideAuditService,
)
func ProvideAuditService() Service {
return New()
}

View File

@ -75,6 +75,7 @@ import (
"github.com/harness/gitness/app/store/database"
"github.com/harness/gitness/app/store/logs"
"github.com/harness/gitness/app/url"
"github.com/harness/gitness/audit"
"github.com/harness/gitness/blob"
cliserver "github.com/harness/gitness/cli/operations/server"
"github.com/harness/gitness/encrypt"
@ -189,6 +190,7 @@ func initSystem(ctx context.Context, config *types.Config) (*cliserver.System, e
usergroup.WireSet,
openapi.WireSet,
repo.ProvideRepoCheck,
audit.WireSet,
)
return &cliserver.System{}, nil
}

View File

@ -74,6 +74,7 @@ import (
"github.com/harness/gitness/app/store/database"
"github.com/harness/gitness/app/store/logs"
"github.com/harness/gitness/app/url"
"github.com/harness/gitness/audit"
"github.com/harness/gitness/blob"
"github.com/harness/gitness/cli/operations/server"
"github.com/harness/gitness/encrypt"
@ -193,9 +194,10 @@ func initSystem(ctx context.Context, config *types.Config) (*server.System, erro
return nil, err
}
lockerLocker := locker.ProvideLocker(mutexManager)
auditService := audit.ProvideAuditService()
repoIdentifier := check.ProvideRepoIdentifierCheck()
repoCheck := repo.ProvideRepoCheck()
repoController := repo.ProvideController(config, transactor, provider, authorizer, repoStore, spaceStore, pipelineStore, principalStore, ruleStore, settingsService, principalInfoCache, protectionManager, gitInterface, repository, codeownersService, reporter, indexer, resourceLimiter, lockerLocker, mutexManager, repoIdentifier, repoCheck)
repoController := repo.ProvideController(config, transactor, provider, authorizer, repoStore, spaceStore, pipelineStore, principalStore, ruleStore, settingsService, principalInfoCache, protectionManager, gitInterface, repository, codeownersService, reporter, indexer, resourceLimiter, lockerLocker, auditService, mutexManager, repoIdentifier, repoCheck)
reposettingsController := reposettings.ProvideController(authorizer, repoStore, settingsService)
executionStore := database.ProvideExecutionStore(db)
checkStore := database.ProvideCheckStore(db, principalInfoCache)

2
go.mod
View File

@ -176,5 +176,5 @@ require (
golang.org/x/tools v0.13.0 // indirect
google.golang.org/genproto v0.0.0-20230706204954-ccb25ca9f130 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
gopkg.in/yaml.v3 v3.0.1
)

View File

@ -23,37 +23,48 @@ import (
// Repository represents a code repository.
type Repository struct {
// TODO: int64 ID doesn't match DB
ID int64 `json:"id"`
Version int64 `json:"-"`
ParentID int64 `json:"parent_id"`
Identifier string `json:"identifier"`
Path string `json:"path"`
Description string `json:"description"`
IsPublic bool `json:"is_public"`
CreatedBy int64 `json:"created_by"`
Created int64 `json:"created"`
Updated int64 `json:"updated"`
Deleted *int64 `json:"deleted,omitempty"`
ID int64 `json:"id" yaml:"id"`
Version int64 `json:"-" yaml:"version"`
ParentID int64 `json:"parent_id" yaml:"parent_id"`
Identifier string `json:"identifier" yaml:"identifier"`
Path string `json:"path" yaml:"path"`
Description string `json:"description" yaml:"description"`
IsPublic bool `json:"is_public" yaml:"is_public"`
CreatedBy int64 `json:"created_by" yaml:"created_by"`
Created int64 `json:"created" yaml:"created"`
Updated int64 `json:"updated" yaml:"updated"`
Deleted *int64 `json:"deleted,omitempty" yaml:"deleted"`
Size int64 `json:"size"`
SizeUpdated int64 `json:"size_updated"`
Size int64 `json:"size" yaml:"size"`
SizeUpdated int64 `json:"size_updated" yaml:"size_updated"`
GitUID string `json:"-"`
DefaultBranch string `json:"default_branch"`
ForkID int64 `json:"fork_id"`
PullReqSeq int64 `json:"-"`
GitUID string `json:"-" yaml:"-"`
DefaultBranch string `json:"default_branch" yaml:"default_branch"`
ForkID int64 `json:"fork_id" yaml:"fork_id"`
PullReqSeq int64 `json:"-" yaml:"-"`
NumForks int `json:"num_forks"`
NumPulls int `json:"num_pulls"`
NumClosedPulls int `json:"num_closed_pulls"`
NumOpenPulls int `json:"num_open_pulls"`
NumMergedPulls int `json:"num_merged_pulls"`
NumForks int `json:"num_forks" yaml:"num_forks"`
NumPulls int `json:"num_pulls" yaml:"num_pulls"`
NumClosedPulls int `json:"num_closed_pulls" yaml:"num_closed_pulls"`
NumOpenPulls int `json:"num_open_pulls" yaml:"num_open_pulls"`
NumMergedPulls int `json:"num_merged_pulls" yaml:"num_merged_pulls"`
Importing bool `json:"importing"`
IsEmpty bool `json:"is_empty,omitempty"`
Importing bool `json:"importing" yaml:"-"`
IsEmpty bool `json:"is_empty,omitempty" yaml:"is_empty"`
// git urls
GitURL string `json:"git_url"`
GitURL string `json:"git_url" yaml:"git_url"`
}
// Clone makes deep copy of repository object.
func (r Repository) Clone() Repository {
var deleted *int64
if r.Deleted != nil {
id := *r.Deleted
deleted = &id
}
r.Deleted = deleted
return r
}
// TODO [CODE-1363]: remove after identifier migration.

View File

@ -19,6 +19,8 @@ import (
"fmt"
"github.com/harness/gitness/types/enum"
"gopkg.in/yaml.v3"
)
type Rule struct {
@ -59,6 +61,69 @@ func (r Rule) MarshalJSON() ([]byte, error) {
})
}
func (r Rule) MarshalYAML() (interface{}, error) {
// yaml cannot marshal json.RawMessage
pattern := make(map[string]any)
err := yaml.Unmarshal(r.Pattern, pattern)
if err != nil {
return nil, err
}
definition := make(map[string]any)
err = yaml.Unmarshal(r.Definition, definition)
if err != nil {
return nil, err
}
return map[string]any{
"id": r.ID,
"version": r.Version,
"created": r.Created,
"updated": r.Updated,
"created_by": r.CreatedBy,
"identifier": r.Identifier,
"description": r.Description,
"type": r.Type,
"state": r.State,
"pattern": pattern,
"definition": definition,
}, nil
}
// Clone makes deep copy of the rule object.
func (r Rule) Clone() Rule {
var repoID *int64
var spaceID *int64
if r.RepoID != nil {
id := *r.RepoID
repoID = &id
}
if r.SpaceID != nil {
id := *r.SpaceID
spaceID = &id
}
r.RepoID = repoID
r.SpaceID = spaceID
pattern := make(json.RawMessage, len(r.Pattern))
copy(pattern, r.Pattern)
r.Pattern = pattern
definition := make(json.RawMessage, len(r.Definition))
copy(definition, r.Definition)
r.Definition = definition
users := make(map[int64]*PrincipalInfo, len(r.Users))
for key, value := range r.Users {
cloned := *value
users[key] = &cloned
}
r.Users = users
return r
}
type RuleType string
type RuleFilter struct {