• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

goto / guardian / 17695496595

13 Sep 2025 10:46AM UTC coverage: 70.095%. First build
17695496595

push

github

rahmatrhd
feat: introduce create resource API

0 of 65 new or added lines in 5 files covered. (0.0%)

11319 of 16148 relevant lines covered (70.1%)

4.63 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

82.35
/core/resource/service.go
1
package resource
2

3
import (
4
        "context"
5
        "fmt"
6

7
        "github.com/goto/guardian/domain"
8
        "github.com/goto/guardian/pkg/log"
9
        "github.com/imdario/mergo"
10
)
11

12
const (
13
        AuditKeyResoruceBulkUpsert  = "resource.bulkUpsert"
14
        AuditKeyResourceUpdate      = "resource.update"
15
        AuditKeyResourceDelete      = "resource.delete"
16
        AuditKeyResourceBatchDelete = "resource.batchDelete"
17

18
        ReservedDetailsKeyMetadata = "__metadata"
19
)
20

21
//go:generate mockery --name=repository --exported --with-expecter
22
type repository interface {
23
        Find(context.Context, domain.ListResourcesFilter) ([]*domain.Resource, error)
24
        GetOne(ctx context.Context, id string) (*domain.Resource, error)
25
        Create(context.Context, *domain.Resource) error
26
        BulkUpsert(context.Context, []*domain.Resource) error
27
        Update(context.Context, *domain.Resource) error
28
        Delete(ctx context.Context, id string) error
29
        BatchDelete(context.Context, []string) error
30
        GetResourcesTotalCount(context.Context, domain.ListResourcesFilter) (int64, error)
31
}
32

33
//go:generate mockery --name=auditLogger --exported --with-expecter
34
type auditLogger interface {
35
        Log(ctx context.Context, action string, data interface{}) error
36
}
37

38
// Service handles the business logic for resource
39
type Service struct {
40
        repo repository
41

42
        logger      log.Logger
43
        auditLogger auditLogger
44
}
45

46
type ServiceDeps struct {
47
        Repository repository
48

49
        Logger      log.Logger
50
        AuditLogger auditLogger
51
}
52

53
// NewService returns *Service
54
func NewService(deps ServiceDeps) *Service {
7✔
55
        return &Service{
7✔
56
                deps.Repository,
7✔
57

7✔
58
                deps.Logger,
7✔
59
                deps.AuditLogger,
7✔
60
        }
7✔
61
}
7✔
62

63
// Find records based on filters
64
func (s *Service) Find(ctx context.Context, filter domain.ListResourcesFilter) ([]*domain.Resource, error) {
4✔
65
        return s.repo.Find(ctx, filter)
4✔
66
}
4✔
67

68
func (s *Service) GetOne(ctx context.Context, id string) (*domain.Resource, error) {
8✔
69
        r, err := s.repo.GetOne(ctx, id)
8✔
70
        if err != nil {
10✔
71
                return nil, err
2✔
72
        }
2✔
73

74
        return r, nil
6✔
75
}
76

NEW
77
func (s *Service) Create(ctx context.Context, r *domain.Resource) error {
×
NEW
78
        if err := r.Validate(); err != nil {
×
NEW
79
                return fmt.Errorf("%w: %v", ErrInvalidResource, err)
×
NEW
80
        }
×
NEW
81
        return s.repo.Create(ctx, r)
×
82
}
83

84
// BulkUpsert inserts or updates records
85
func (s *Service) BulkUpsert(ctx context.Context, resources []*domain.Resource) error {
1✔
86
        if err := s.repo.BulkUpsert(ctx, resources); err != nil {
2✔
87
                return err
1✔
88
        }
1✔
89

90
        go func() {
×
91
                ctx := context.WithoutCancel(ctx)
×
92
                if err := s.auditLogger.Log(ctx, AuditKeyResoruceBulkUpsert, resources); err != nil {
×
93
                        s.logger.Error(ctx, "failed to record audit log", "error", err)
×
94
                }
×
95
        }()
96

97
        return nil
×
98
}
99

100
// Update updates only details and labels of a resource by ID
101
func (s *Service) Update(ctx context.Context, r *domain.Resource) error {
7✔
102
        filterBy := r.ID
7✔
103
        if r.ID == "" {
8✔
104
                filterBy = r.GlobalURN
1✔
105
        }
1✔
106

107
        existingResource, err := s.GetOne(ctx, filterBy)
7✔
108
        if err != nil {
9✔
109
                return err
2✔
110
        }
2✔
111

112
        // Details[ReservedDetailsKeyMetadata] is not allowed to be updated by users
113
        // value for this field should only set by the provider on FetchResources
114
        delete(r.Details, ReservedDetailsKeyMetadata)
5✔
115

5✔
116
        if err := mergo.Merge(r, existingResource); err != nil {
5✔
117
                return err
×
118
        }
×
119
        s.logger.Debug(ctx, "merged existing resource with updated resource", "resource", r.ID)
5✔
120

5✔
121
        res := &domain.Resource{
5✔
122
                ID:      r.ID,
5✔
123
                Details: r.Details,
5✔
124
                Labels:  r.Labels,
5✔
125
        }
5✔
126
        if err := s.repo.Update(ctx, res); err != nil {
6✔
127
                s.logger.Error(ctx, "failed to update resource", "resource", r.ID, "error", err)
1✔
128
                return err
1✔
129
        }
1✔
130
        s.logger.Info(ctx, "resource updated", "resource", r.ID)
4✔
131

4✔
132
        r.UpdatedAt = res.UpdatedAt
4✔
133

4✔
134
        go func() {
8✔
135
                ctx := context.WithoutCancel(ctx)
4✔
136
                if err := s.auditLogger.Log(ctx, AuditKeyResourceUpdate, r); err != nil {
4✔
137
                        s.logger.Error(ctx, "failed to record audit log", "error", err)
×
138
                }
×
139
        }()
140

141
        return nil
4✔
142
}
143

144
func (s *Service) Get(ctx context.Context, ri *domain.ResourceIdentifier) (*domain.Resource, error) {
3✔
145
        var resource *domain.Resource
3✔
146
        if ri.ID != "" {
4✔
147
                if r, err := s.GetOne(ctx, ri.ID); err != nil {
1✔
148
                        return nil, err
×
149
                } else {
1✔
150
                        resource = r
1✔
151
                }
1✔
152
        } else {
2✔
153
                if resources, err := s.Find(ctx, domain.ListResourcesFilter{
2✔
154
                        ProviderType: ri.ProviderType,
2✔
155
                        ProviderURN:  ri.ProviderURN,
2✔
156
                        ResourceType: ri.Type,
2✔
157
                        ResourceURN:  ri.URN,
2✔
158
                }); err != nil {
2✔
159
                        return nil, err
×
160
                } else {
2✔
161
                        if len(resources) == 0 {
3✔
162
                                return nil, ErrRecordNotFound
1✔
163
                        } else {
2✔
164
                                resource = resources[0]
1✔
165
                        }
1✔
166
                }
167
        }
168
        return resource, nil
2✔
169
}
170

171
func (s *Service) Delete(ctx context.Context, id string) error {
2✔
172
        if err := s.repo.Delete(ctx, id); err != nil {
3✔
173
                s.logger.Error(ctx, "failed to delete resource", "resource", id, "error", err)
1✔
174
                return err
1✔
175
        }
1✔
176
        s.logger.Info(ctx, "resource deleted", "resource", id)
1✔
177

1✔
178
        go func() {
2✔
179
                ctx := context.WithoutCancel(ctx)
1✔
180
                if err := s.auditLogger.Log(ctx, AuditKeyResourceDelete, map[string]interface{}{"id": id}); err != nil {
1✔
181
                        s.logger.Error(ctx, "failed to record audit log", "error", err)
×
182
                }
×
183
        }()
184

185
        return nil
1✔
186
}
187

188
func (s *Service) BatchDelete(ctx context.Context, ids []string) error {
2✔
189
        if err := s.repo.BatchDelete(ctx, ids); err != nil {
3✔
190
                s.logger.Error(ctx, "failed to delete resources", "resources", len(ids), "error", err)
1✔
191
                return err
1✔
192
        }
1✔
193
        s.logger.Info(ctx, "resources deleted", "resources", len(ids))
1✔
194

1✔
195
        go func() {
2✔
196
                ctx := context.WithoutCancel(ctx)
1✔
197
                if err := s.auditLogger.Log(ctx, AuditKeyResourceBatchDelete, map[string]interface{}{"ids": ids}); err != nil {
1✔
198
                        s.logger.Error(ctx, "failed to record audit log", "error", err)
×
199
                }
×
200
        }()
201

202
        return nil
1✔
203
}
204

205
func (s *Service) GetResourcesTotalCount(ctx context.Context, filters domain.ListResourcesFilter) (int64, error) {
2✔
206
        return s.repo.GetResourcesTotalCount(ctx, filters)
2✔
207
}
2✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc