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

mindersec / minder / 14376078638

10 Apr 2025 08:39AM UTC coverage: 56.857%. First build
14376078638

Pull #5515

github

web-flow
Merge ea220ae51 into 0f5ecd80e
Pull Request #5515: Don't return errors when getting properties

27 of 49 new or added lines in 22 files covered. (55.1%)

18305 of 32195 relevant lines covered (56.86%)

37.04 hits per line

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

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

4
package gitlab
5

6
import (
7
        "context"
8
        "encoding/json"
9
        "fmt"
10
        "net/http"
11
        "net/url"
12
        "strings"
13
        "time"
14

15
        "github.com/cenkalti/backoff/v4"
16
        "github.com/rs/zerolog"
17
        gitlablib "gitlab.com/gitlab-org/api/client-go"
18
        "golang.org/x/mod/semver"
19

20
        minderv1 "github.com/mindersec/minder/pkg/api/protobuf/go/minder/v1"
21
        "github.com/mindersec/minder/pkg/entities/properties"
22
)
23

24
// FormatReleaseUpstreamID returns the upstream ID for a gitlab release
25
// This is done so we don't have to deal with conversions in the provider
26
// when dealing with entities
27
func FormatReleaseUpstreamID(id int) string {
×
28
        return fmt.Sprintf("%d", id)
×
29
}
×
30

31
func (c *gitlabClient) getPropertiesForRelease(
32
        ctx context.Context, getByProps *properties.Properties,
33
) (*properties.Properties, error) {
×
34
        uid, err := getByProps.GetProperty(properties.PropertyUpstreamID).AsString()
×
35
        if err != nil {
×
36
                return nil, fmt.Errorf("upstream ID not found or invalid: %w", err)
×
37
        }
×
38

39
        pid, err := getByProps.GetProperty(ReleasePropertyProjectID).AsString()
×
40
        if err != nil {
×
41
                return nil, fmt.Errorf("project ID not found or invalid: %w", err)
×
42
        }
×
43

44
        releaseTagName, err := getByProps.GetProperty(ReleasePropertyTag).AsString()
×
45
        if err != nil {
×
46
                return nil, fmt.Errorf("tag name not found or invalid: %w", err)
×
47
        }
×
48

49
        releasePath, err := url.JoinPath("projects", pid, "releases", releaseTagName)
×
50
        if err != nil {
×
51
                return nil, fmt.Errorf("failed to join URL path for release using upstream ID: %w", err)
×
52
        }
×
53

54
        // NOTE: We're not using github.com/xanzy/go-gitlab to do the actual
55
        // request here because of the way they form authentication for requests.
56
        // It would be ideal to use it, so we should consider contributing and making
57
        // that part more pluggable.
58
        req, err := c.NewRequest(http.MethodGet, releasePath, nil)
×
59
        if err != nil {
×
60
                return nil, fmt.Errorf("failed to create request for release using upstream ID: %w", err)
×
61
        }
×
62

63
        resp, err := c.Do(ctx, req)
×
64
        if err != nil {
×
65
                return nil, fmt.Errorf("failed to get release using upstream ID: %w", err)
×
66
        }
×
67

68
        defer resp.Body.Close()
×
69

×
70
        if resp.StatusCode != http.StatusOK {
×
71
                return nil, fmt.Errorf("failed to get release using upstream ID: %w", err)
×
72
        }
×
73

74
        release := &gitlablib.Release{}
×
75
        if err := json.NewDecoder(resp.Body).Decode(release); err != nil {
×
76
                return nil, fmt.Errorf("failed to decode response for release using upstream ID: %w", err)
×
77
        }
×
78

79
        proj, err := c.getGitLabProject(ctx, pid)
×
80
        if err != nil {
×
81
                return nil, fmt.Errorf("failed to get project: %w", err)
×
82
        }
×
83

84
        // Get the commit refs for the release
85
        refs, err := c.getCommitBranchRefs(ctx, pid, release.Commit.ID)
×
86
        if err != nil {
×
87
                return nil, fmt.Errorf("failed to get commit refs: %w", err)
×
88
        }
×
89

90
        if len(refs) == 0 {
×
91
                return nil, fmt.Errorf("no commit refs found for release")
×
92
        }
×
93

94
        // try to guess the branch from the commit refs
95
        branch := guessBranchFromCommitRefs(refs, release.TagName)
×
96

×
NEW
97
        outProps := gitlabReleaseToProperties(uid, releaseTagName, proj, branch)
×
98

×
99
        return outProps, nil
×
100
}
101

102
func (c *gitlabClient) getCommitBranchRefs(
103
        ctx context.Context, projID string, commitID string,
104
) ([]*gitlablib.CommitRef, error) {
×
105
        commitRefsPath, err := url.JoinPath("projects", projID, "repository", "commits", commitID, "refs")
×
106
        if err != nil {
×
107
                return nil, fmt.Errorf("failed to join URL path for commit refs: %w", err)
×
108
        }
×
109

110
        // append the type=branch query param
111
        // It's safe to append this way since it's a constant string
112
        // and we've already parsed the URL
113
        commitRefsPath = fmt.Sprintf("%s?type=branch", commitRefsPath)
×
114

×
115
        var resp *http.Response
×
116
        err = backoff.RetryNotify(
×
117
                func() error {
×
118
                        req, err := c.NewRequest(http.MethodGet, commitRefsPath, nil)
×
119
                        if err != nil {
×
120
                                return fmt.Errorf("failed to create request for commit refs: %w", err)
×
121
                        }
×
122

123
                        resp, err = c.Do(ctx, req)
×
124
                        if err != nil {
×
125
                                return fmt.Errorf("failed to get commit refs: %w", err)
×
126
                        }
×
127

128
                        if resp.StatusCode != http.StatusOK {
×
129
                                return fmt.Errorf("failed to get commit refs: HTTP Status %d", resp.StatusCode)
×
130
                        }
×
131

132
                        return nil
×
133
                },
134
                backoff.WithMaxRetries(backoff.NewExponentialBackOff(), 5),
135
                func(err error, _ time.Duration) {
×
136
                        zerolog.Ctx(ctx).Debug().Err(err).Msg("failed to get commit refs, retrying")
×
137
                })
×
138
        if err != nil {
×
139
                return nil, fmt.Errorf("failed to get commit refs: %w", err)
×
140
        }
×
141

142
        defer resp.Body.Close()
×
143

×
144
        commitRefs := []*gitlablib.CommitRef{}
×
145
        if err := json.NewDecoder(resp.Body).Decode(&commitRefs); err != nil {
×
146
                return nil, fmt.Errorf("failed to decode response for commit refs: %w", err)
×
147
        }
×
148

149
        return commitRefs, nil
×
150
}
151

152
func releaseEntityV1FromProperties(props *properties.Properties) (*minderv1.EntityInstance, error) {
×
153
        // validation
×
154
        _, err := props.GetProperty(properties.PropertyUpstreamID).AsString()
×
155
        if err != nil {
×
156
                return nil, fmt.Errorf("upstream ID not found or invalid: %w", err)
×
157
        }
×
158

159
        _, err = props.GetProperty(ReleasePropertyProjectID).AsString()
×
160
        if err != nil {
×
161
                return nil, fmt.Errorf("project ID not found or invalid: %w", err)
×
162
        }
×
163

164
        _, err = props.GetProperty(ReleasePropertyBranch).AsString()
×
165
        if err != nil {
×
166
                return nil, fmt.Errorf("branch not found or invalid: %w", err)
×
167
        }
×
168

169
        if _, err = props.GetProperty(RepoPropertyNamespace).AsString(); err != nil {
×
170
                return nil, fmt.Errorf("namespace not found or invalid: %w", err)
×
171
        }
×
172

173
        if _, err = props.GetProperty(RepoPropertyProjectName).AsString(); err != nil {
×
174
                return nil, fmt.Errorf("project name not found or invalid: %w", err)
×
175
        }
×
176

177
        name, err := getReleaseNameFromProperties(props)
×
178
        if err != nil {
×
179
                return nil, fmt.Errorf("failed to get release name: %w", err)
×
180
        }
×
181

182
        return &minderv1.EntityInstance{
×
183
                Type:       minderv1.Entity_ENTITY_RELEASE,
×
184
                Name:       name,
×
185
                Properties: props.ToProtoStruct(),
×
186
        }, nil
×
187
}
188

189
func getReleaseNameFromProperties(props *properties.Properties) (string, error) {
×
190
        branch, err := props.GetProperty(ReleasePropertyTag).AsString()
×
191
        if err != nil {
×
192
                return "", fmt.Errorf("branch not found or invalid: %w", err)
×
193
        }
×
194

195
        ns, err := props.GetProperty(RepoPropertyNamespace).AsString()
×
196
        if err != nil {
×
197
                return "", fmt.Errorf("namespace not found or invalid: %w", err)
×
198
        }
×
199

200
        projName, err := props.GetProperty(RepoPropertyProjectName).AsString()
×
201
        if err != nil {
×
202
                return "", fmt.Errorf("project name not found or invalid: %w", err)
×
203
        }
×
204

205
        return formatReleaseName(ns, projName, branch), nil
×
206
}
207

208
func gitlabReleaseToProperties(
209
        releaseID string, releaseTag string, proj *gitlablib.Project, branch string,
NEW
210
) *properties.Properties {
×
211
        ns, err := getGitlabProjectNamespace(proj)
×
212
        if err != nil {
×
NEW
213
                zerolog.Ctx(context.Background()).Error().Err(err).Msg("failed to get namespace")
×
NEW
214
                return properties.NewProperties(map[string]interface{}{})
×
215
        }
×
216

217
        projName := proj.Name
×
218

×
219
        return properties.NewProperties(map[string]interface{}{
×
220
                properties.PropertyUpstreamID: releaseID,
×
221
                ReleasePropertyProjectID:      FormatRepositoryUpstreamID(proj.ID),
×
222
                ReleasePropertyTag:            releaseTag,
×
223
                ReleasePropertyBranch:         branch,
×
224
                RepoPropertyNamespace:         ns,
×
225
                RepoPropertyProjectName:       projName,
×
226
        })
×
227
}
228

229
func guessBranchFromCommitRefs(refs []*gitlablib.CommitRef, tagName string) string {
×
230
        if len(refs) == 1 {
×
231
                return refs[0].Name
×
232
        }
×
233

234
        for _, ref := range refs {
×
235
                // simple, if the ref name is the same as the tag name, return it
×
236
                if ref.Name == tagName {
×
237
                        return ref.Name
×
238
                }
×
239

240
                // if the ref name starts with release, return it
241
                if strings.HasPrefix(ref.Name, "release-") || strings.HasPrefix(ref.Name, "release/") {
×
242
                        return ref.Name
×
243
                }
×
244

245
                // semver - match major and minor versions
246
                if semver.IsValid(ref.Name) && semver.IsValid(tagName) {
×
247
                        if semver.Major(ref.Name) == semver.Major(tagName) {
×
248
                                return ref.Name
×
249
                        }
×
250
                }
251

252
                // if the ref name starts with the tag name, return it
253
                if strings.HasPrefix(ref.Name, tagName) {
×
254
                        return ref.Name
×
255
                }
×
256

257
                if strings.HasPrefix(tagName, ref.Name) {
×
258
                        return ref.Name
×
259
                }
×
260

261
                // if it's `main` or `master`, return it
262
                if ref.Name == "main" || ref.Name == "master" {
×
263
                        return ref.Name
×
264
                }
×
265
        }
266

267
        // If we can't find a match, just return the first ref
268
        return refs[0].Name
×
269
}
270

271
func formatReleaseName(ns, projName, branch string) string {
×
272
        return fmt.Sprintf("%s/%s/%s", ns, projName, branch)
×
273
}
×
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