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

mindersec / minder / 13283006810

12 Feb 2025 10:14AM UTC coverage: 57.515% (+0.03%) from 57.481%
13283006810

Pull #5418

github

web-flow
Merge 7e7f02f59 into 8aba270eb
Pull Request #5418: build(deps): bump google.golang.org/protobuf from 1.36.4 to 1.36.5 in /tools

18170 of 31592 relevant lines covered (57.51%)

37.64 hits per line

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

0.0
/internal/providers/github/entities.go
1
// SPDX-FileCopyrightText: Copyright 2024 The Minder Authors
2
// SPDX-License-Identifier: Apache-2.0
3

4
package github
5

6
import (
7
        "context"
8
        "errors"
9
        "fmt"
10
        "net/url"
11

12
        "github.com/google/go-github/v63/github"
13
        "github.com/google/uuid"
14
        "github.com/rs/zerolog"
15
        "google.golang.org/protobuf/reflect/protoreflect"
16

17
        "github.com/mindersec/minder/internal/db"
18
        ghprop "github.com/mindersec/minder/internal/providers/github/properties"
19
        "github.com/mindersec/minder/internal/util/ptr"
20
        minderv1 "github.com/mindersec/minder/pkg/api/protobuf/go/minder/v1"
21
        "github.com/mindersec/minder/pkg/entities/properties"
22
)
23

24
var (
25
        targetedEvents = []string{"*"}
26
)
27

28
// GetEntityName implements the Provider interface
29
func (c *GitHub) GetEntityName(entType minderv1.Entity, props *properties.Properties) (string, error) {
×
30
        if props == nil {
×
31
                return "", errors.New("properties are nil")
×
32
        }
×
33
        if c.propertyFetchers == nil {
×
34
                return "", errors.New("property fetchers not initialized")
×
35
        }
×
36
        fetcher := c.propertyFetchers.EntityPropertyFetcher(entType)
×
37
        if fetcher == nil {
×
38
                return "", fmt.Errorf("no fetcher found for entity type %s", entType)
×
39
        }
×
40
        return fetcher.GetName(props)
×
41
}
42

43
// SupportsEntity implements the Provider interface
44
func (c *GitHub) SupportsEntity(entType minderv1.Entity) bool {
×
45
        return c.propertyFetchers.EntityPropertyFetcher(entType) != nil
×
46
}
×
47

48
// RegisterEntity implements the Provider interface
49
func (c *GitHub) RegisterEntity(
50
        ctx context.Context, entityType minderv1.Entity, props *properties.Properties,
51
) (*properties.Properties, error) {
×
52
        // We only need explicit registration steps for repositories
×
53
        // The rest of the entities are originated from them.
×
54
        if entityType != minderv1.Entity_ENTITY_REPOSITORIES {
×
55
                return props, nil
×
56
        }
×
57

58
        webhookURLProvider, err := url.JoinPath(
×
59
                c.webhookConfig.ExternalWebhookURL,
×
60
                url.PathEscape(string(db.ProviderTypeGithub)),
×
61
        )
×
62
        if err != nil {
×
63
                return nil, fmt.Errorf("error joining webhook URL: %w", err)
×
64
        }
×
65
        parsedBaseURL, err := url.Parse(webhookURLProvider)
×
66
        if err != nil {
×
67
                return nil, errors.New("error parsing webhook base URL. Please check the configuration")
×
68
        }
×
69

70
        hookUUID := uuid.New().String()
×
71
        webhookURL := parsedBaseURL.JoinPath(hookUUID)
×
72

×
73
        repoNameP := props.GetProperty(ghprop.RepoPropertyName)
×
74
        if repoNameP == nil {
×
75
                return nil, errors.New("repo name property not found")
×
76
        }
×
77

78
        repoOwnerP := props.GetProperty(ghprop.RepoPropertyOwner)
×
79
        if repoOwnerP == nil {
×
80
                return nil, errors.New("repo owner property not found")
×
81
        }
×
82

83
        repoName := repoNameP.GetString()
×
84
        repoOwner := repoOwnerP.GetString()
×
85

×
86
        // If we have an existing hook for same repo, delete it
×
87
        if err := c.cleanupStaleHooks(ctx, repoOwner, repoName, webhookURL.Host); err != nil {
×
88
                return nil, fmt.Errorf("error cleaning up stale hooks: %w", err)
×
89
        }
×
90

91
        // Attempt to register new webhook
92
        secret, err := c.webhookConfig.GetWebhookSecret()
×
93
        if err != nil {
×
94
                return nil, fmt.Errorf("error getting webhook secret: %w", err)
×
95
        }
×
96
        ping := c.webhookConfig.ExternalPingURL
×
97

×
98
        newHook := getGitHubWebhook(webhookURL.String(), ping, secret)
×
99
        webhook, err := c.CreateHook(ctx, repoOwner, repoName, newHook)
×
100
        if err != nil {
×
101
                return nil, fmt.Errorf("error creating hook: %w", err)
×
102
        }
×
103

104
        whprops, err := properties.NewProperties(map[string]any{
×
105
                ghprop.RepoPropertyHookUiid: hookUUID,
×
106
                ghprop.RepoPropertyHookId:   webhook.GetID(),
×
107
                ghprop.RepoPropertyHookUrl:  webhook.GetURL(),
×
108
                ghprop.RepoPropertyHookName: webhook.GetName(),
×
109
                ghprop.RepoPropertyHookType: webhook.GetType(),
×
110
        })
×
111
        if err != nil {
×
112
                return nil, fmt.Errorf("error creating webhook properties: %w", err)
×
113
        }
×
114

115
        return props.Merge(whprops), nil
×
116
}
117

118
// DeregisterEntity implements the Provider interface
119
func (c *GitHub) DeregisterEntity(ctx context.Context, entityType minderv1.Entity, props *properties.Properties) error {
×
120
        // We only need explicit registration steps for repositories
×
121
        // The rest of the entities are originated from them.
×
122
        if entityType != minderv1.Entity_ENTITY_REPOSITORIES {
×
123
                return nil
×
124
        }
×
125

126
        repoNameP := props.GetProperty(ghprop.RepoPropertyName)
×
127
        if repoNameP == nil {
×
128
                return errors.New("repo name property not found")
×
129
        }
×
130

131
        repoOwnerP := props.GetProperty(ghprop.RepoPropertyOwner)
×
132
        if repoOwnerP == nil {
×
133
                return errors.New("repo owner property not found")
×
134
        }
×
135

136
        hookIDP := props.GetProperty(ghprop.RepoPropertyHookId)
×
137
        if hookIDP == nil {
×
138
                return errors.New("hook ID property not found")
×
139
        }
×
140

141
        repoName := repoNameP.GetString()
×
142
        repoOwner := repoOwnerP.GetString()
×
143
        hookID := hookIDP.GetInt64()
×
144

×
145
        err := c.DeleteHook(ctx, repoOwner, repoName, hookID)
×
146
        if err != nil {
×
147
                return fmt.Errorf("error deleting hook: %w", err)
×
148
        }
×
149

150
        return nil
×
151
}
152

153
// ReregisterEntity implements the Provider interface
154
func (c *GitHub) ReregisterEntity(
155
        ctx context.Context, entityType minderv1.Entity, props *properties.Properties,
156
) error {
×
157
        // We only need explicit registration steps for repositories
×
158
        // The rest of the entities are originated from them.
×
159
        if entityType != minderv1.Entity_ENTITY_REPOSITORIES {
×
160
                return nil
×
161
        }
×
162

163
        repoNameP := props.GetProperty(ghprop.RepoPropertyName)
×
164
        if repoNameP == nil {
×
165
                return errors.New("repo name property not found")
×
166
        }
×
167

168
        repoOwnerP := props.GetProperty(ghprop.RepoPropertyOwner)
×
169
        if repoOwnerP == nil {
×
170
                return errors.New("repo owner property not found")
×
171
        }
×
172

173
        hookIDP := props.GetProperty(ghprop.RepoPropertyHookId)
×
174
        if hookIDP == nil {
×
175
                return errors.New("hook ID property not found")
×
176
        }
×
177

178
        hookURL := props.GetProperty(ghprop.RepoPropertyHookUrl)
×
179
        if hookURL == nil {
×
180
                return errors.New("hook URL property not found")
×
181
        }
×
182

183
        ping := c.webhookConfig.ExternalPingURL
×
184

×
185
        secret, err := c.webhookConfig.GetWebhookSecret()
×
186
        if err != nil {
×
187
                return fmt.Errorf("error getting webhook secret for github provider: %w", err)
×
188
        }
×
189

190
        repoName := repoNameP.GetString()
×
191
        repoOwner := repoOwnerP.GetString()
×
192
        hookID := hookIDP.GetInt64()
×
193

×
194
        hook := getGitHubWebhook(hookURL.GetString(), ping, secret)
×
195
        _, err = c.EditHook(ctx, repoOwner, repoName, hookID, hook)
×
196
        if err != nil {
×
197
                zerolog.Ctx(ctx).Error().Err(err).Msg("unable to update hook")
×
198
                return fmt.Errorf("unable to update hook: %w", err)
×
199
        }
×
200

201
        return nil
×
202
}
203

204
func (c *GitHub) cleanupStaleHooks(
205
        ctx context.Context,
206
        repoOwner string,
207
        repoName string,
208
        webhookHost string,
209
) error {
×
210
        logger := zerolog.Ctx(ctx)
×
211
        hooks, err := c.ListHooks(ctx, repoOwner, repoName)
×
212
        if errors.Is(err, ErrNotFound) {
×
213
                logger.Debug().Msg("no hooks found")
×
214
                return nil
×
215
        } else if err != nil {
×
216
                return fmt.Errorf("error listing hooks: %w", err)
×
217
        }
×
218

219
        for _, hook := range hooks {
×
220
                // it is our hook, we can remove it
×
221
                shouldDelete, err := IsMinderHook(hook, webhookHost)
×
222
                // If err != nil, shouldDelete == false - use one error check for both calls
×
223
                if shouldDelete {
×
224
                        err = c.DeleteHook(ctx, repoOwner, repoName, hook.GetID())
×
225
                }
×
226
                if err != nil {
×
227
                        return fmt.Errorf("error deleting hook: %w", err)
×
228
                }
×
229
        }
230

231
        return nil
×
232
}
233

234
// PropertiesToProtoMessage implements the ProtoMessageConverter interface
235
func (c *GitHub) PropertiesToProtoMessage(
236
        entType minderv1.Entity, props *properties.Properties,
237
) (protoreflect.ProtoMessage, error) {
×
238
        if !c.SupportsEntity(entType) {
×
239
                return nil, fmt.Errorf("entity type %s is not supported by the github provider", entType)
×
240
        }
×
241

242
        switch entType { // nolint:exhaustive // these are really the only entities we support
×
243
        case minderv1.Entity_ENTITY_REPOSITORIES:
×
244
                return ghprop.RepoV1FromProperties(props)
×
245
        case minderv1.Entity_ENTITY_ARTIFACTS:
×
246
                return ghprop.ArtifactV1FromProperties(props)
×
247
        case minderv1.Entity_ENTITY_PULL_REQUESTS:
×
248
                return ghprop.PullRequestV1FromProperties(props)
×
249
        case minderv1.Entity_ENTITY_RELEASE:
×
250
                return ghprop.EntityInstanceV1FromReleaseProperties(props)
×
251
        }
252

253
        return nil, fmt.Errorf("conversion of entity type %s is not handled by the github provider", entType)
×
254
}
255

256
func getGitHubWebhook(webhookURL, pingURL, secret string) *github.Hook {
×
257
        return &github.Hook{
×
258
                Config: &github.HookConfig{
×
259
                        URL:         ptr.Ptr(webhookURL),
×
260
                        ContentType: ptr.Ptr("json"),
×
261
                        Secret:      &secret,
×
262
                },
×
263
                PingURL: ptr.Ptr(pingURL),
×
264
                Events:  targetedEvents,
×
265
        }
×
266
}
×
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