Skip to content

Commit c611c23

Browse files
BreeeLoeffler, Julianmblaschke
authored
feat(issue-94): allow fetching all builds and other than completed (#102)
* feat(issue-94): allow fetching all builds and other than completed * add handling for all case * explicit handling of all case * filter for fetching all builds * readme * Allow uuid and names * filter builds by mintime in the case of statusFilter=all * must be startTime because builds could be running and not be finished --------- Co-authored-by: Loeffler, Julian <[email protected]> Co-authored-by: Markus Blaschke <[email protected]>
1 parent 274d1fe commit c611c23

File tree

6 files changed

+94
-19
lines changed

6 files changed

+94
-19
lines changed

.dockerignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
/azure-devops-exporter
22
/release-assets
3+
.env

README.md

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -42,28 +42,24 @@ Application Options:
4242
--azuredevops.agentpool= Enable scrape metrics for agent pool (IDs) [$AZURE_DEVOPS_AGENTPOOL]
4343
--whitelist.project= Filter projects (UUIDs) [$AZURE_DEVOPS_FILTER_PROJECT]
4444
--blacklist.project= Filter projects (UUIDs) [$AZURE_DEVOPS_BLACKLIST_PROJECT]
45+
--timeline.state= Filter timeline states (completed, inProgress, pending) (default: completed) [$AZURE_DEVOPS_FILTER_TIMELINE_STATE]
46+
--builds.all.project= Fetch all builds (even if they are not finished) [$AZURE_DEVOPS_FETCH_ALL_BUILDS_FILTER_PROJECT]
4547
--list.query= Pairs of query and project UUIDs in the form: '<queryId>@<projectId>' [$AZURE_DEVOPS_QUERIES]
46-
--tags.schema= Tags to be extracted from builds in the format 'tagName:type' with following types: number,
47-
info, bool [$AZURE_DEVOPS_TAG_SCHEMA]
48+
--tags.schema= Tags to be extracted from builds in the format 'tagName:type' with following types: number, info, bool [$AZURE_DEVOPS_TAG_SCHEMA]
4849
--tags.build.definition= Build definition ids to query tags (IDs) [$AZURE_DEVOPS_TAG_BUILD_DEFINITION]
49-
--cache.path= Cache path (to folder, file://path... or
50-
azblob://storageaccount.blob.core.windows.net/containername or
51-
k8scm://{namespace}/{configmap}}) [$CACHE_PATH]
50+
--cache.path= Cache path (to folder, file://path... or azblob://storageaccount.blob.core.windows.net/containername or k8scm://{namespace}/{configmap}}) [$CACHE_PATH]
5251
--request.concurrency= Number of concurrent requests against dev.azure.com (default: 10) [$REQUEST_CONCURRENCY]
5352
--request.retries= Number of retried requests against dev.azure.com (default: 3) [$REQUEST_RETRIES]
54-
--servicediscovery.refresh= Refresh duration for servicediscovery (time.duration) (default: 30m)
55-
[$SERVICEDISCOVERY_REFRESH]
53+
--servicediscovery.refresh= Refresh duration for servicediscovery (time.duration) (default: 30m) [$SERVICEDISCOVERY_REFRESH]
5654
--limit.project= Limit number of projects (default: 100) [$LIMIT_PROJECT]
5755
--limit.builds-per-project= Limit builds per project (default: 100) [$LIMIT_BUILDS_PER_PROJECT]
5856
--limit.builds-per-definition= Limit builds per definition (default: 10) [$LIMIT_BUILDS_PER_DEFINITION]
5957
--limit.releases-per-project= Limit releases per project (default: 100) [$LIMIT_RELEASES_PER_PROJECT]
6058
--limit.releases-per-definition= Limit releases per definition (default: 100) [$LIMIT_RELEASES_PER_DEFINITION]
6159
--limit.deployments-per-definition= Limit deployments per definition (default: 100) [$LIMIT_DEPLOYMENTS_PER_DEFINITION]
6260
--limit.releasedefinitions-per-project= Limit builds per definition (default: 100) [$LIMIT_RELEASEDEFINITION_PER_PROJECT]
63-
--limit.build-history-duration= Time (time.Duration) how long the exporter should look back for builds (default: 48h)
64-
[$LIMIT_BUILD_HISTORY_DURATION]
65-
--limit.release-history-duration= Time (time.Duration) how long the exporter should look back for releases (default: 48h)
66-
[$LIMIT_RELEASE_HISTORY_DURATION]
61+
--limit.build-history-duration= Time (time.Duration) how long the exporter should look back for builds (default: 48h) [$LIMIT_BUILD_HISTORY_DURATION]
62+
--limit.release-history-duration= Time (time.Duration) how long the exporter should look back for releases (default: 48h) [$LIMIT_RELEASE_HISTORY_DURATION]
6763
--server.bind= Server address (default: :8080) [$SERVER_BIND]
6864
--server.timeout.read= Server read timeout (default: 5s) [$SERVER_TIMEOUT_READ]
6965
--server.timeout.write= Server write timeout (default: 10s) [$SERVER_TIMEOUT_WRITE]

azure-devops-client/build.go

Lines changed: 33 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ type TimelineRecord struct {
5353
Result string `json:"result"`
5454
WorkerName string `json:"workerName"`
5555
Identifier string `json:"identifier"`
56+
State string `json:"state"`
5657
StartTime time.Time
5758
FinishTime time.Time
5859
}
@@ -195,14 +196,26 @@ func (c *AzureDevopsClient) ListBuildHistoryWithStatus(project string, minTime t
195196
defer c.concurrencyUnlock()
196197
c.concurrencyLock()
197198

198-
url := fmt.Sprintf(
199-
"%v/_apis/build/builds?api-version=%v&minTime=%s&statusFilter=%v",
200-
url.QueryEscape(project),
201-
url.QueryEscape(c.ApiVersion),
202-
url.QueryEscape(minTime.Format(time.RFC3339)),
203-
url.QueryEscape(statusFilter),
204-
)
205-
response, err := c.rest().R().Get(url)
199+
requestUrl := ""
200+
201+
if statusFilter == "all" {
202+
requestUrl = fmt.Sprintf(
203+
"%v/_apis/build/builds?api-version=%v&statusFilter=%v",
204+
url.QueryEscape(project),
205+
url.QueryEscape(c.ApiVersion),
206+
url.QueryEscape(statusFilter),
207+
)
208+
} else {
209+
requestUrl = fmt.Sprintf(
210+
"%v/_apis/build/builds?api-version=%v&minTime=%s&statusFilter=%v",
211+
url.QueryEscape(project),
212+
url.QueryEscape(c.ApiVersion),
213+
url.QueryEscape(minTime.Format(time.RFC3339)),
214+
url.QueryEscape(statusFilter),
215+
)
216+
}
217+
218+
response, err := c.rest().R().Get(requestUrl)
206219
if err := c.checkResponse(response, err); err != nil {
207220
error = err
208221
return
@@ -214,6 +227,18 @@ func (c *AzureDevopsClient) ListBuildHistoryWithStatus(project string, minTime t
214227
return
215228
}
216229

230+
// if the status filter is "all", we need to filter the builds by minTime manually because Azure DevOps API does not support it
231+
if statusFilter == "all" {
232+
var filteredList BuildList
233+
for _, build := range list.List {
234+
if build.StartTime.After(minTime) {
235+
filteredList.List = append(filteredList.List, build)
236+
}
237+
}
238+
filteredList.Count = len(filteredList.List)
239+
list = filteredList
240+
}
241+
217242
return
218243
}
219244

compose.yaml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
services:
2+
exporter:
3+
build: .
4+
ports:
5+
- "8000:8000"
6+
environment:
7+
SERVER_BIND: ${SERVER_BIND}
8+
AZURE_DEVOPS_URL: ${AZURE_DEVOPS_URL}
9+
AZURE_DEVOPS_ORGANISATION: ${AZURE_DEVOPS_ORGANISATION}
10+
AZURE_DEVOPS_ACCESS_TOKEN: ${AZURE_DEVOPS_ACCESS_TOKEN}
11+
AZURE_DEVOPS_FILTER_PROJECT: 72690669-de93-4a98-84a9-8300ce32a2f2
12+
LIMIT_BUILDS_PER_PROJECT: 500
13+
LIMIT_BUILDS_PER_DEFINITION: 100
14+
SERVER_TIMEOUT_READ: 15s
15+
AZURE_DEVOPS_FETCH_ALL_BUILDS: "true"
16+
#AZURE_DEVOPS_FILTER_TIMELINE_STATE: "completed inProgress pending"

config/opts.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,9 @@ type (
5858
FilterProjects []string `long:"whitelist.project" env:"AZURE_DEVOPS_FILTER_PROJECT" env-delim:" " description:"Filter projects (UUIDs)"`
5959
BlacklistProjects []string `long:"blacklist.project" env:"AZURE_DEVOPS_BLACKLIST_PROJECT" env-delim:" " description:"Filter projects (UUIDs)"`
6060

61+
FilterTimelineState []string `long:"timeline.state" env:"AZURE_DEVOPS_FILTER_TIMELINE_STATE" env-delim:" " description:"Filter timeline states (completed, inProgress, pending)" default:"completed"`
62+
FetchAllBuildsFilter []string `long:"builds.all.project" env:"AZURE_DEVOPS_FETCH_ALL_BUILDS_FILTER_PROJECT" env-delim:" " description:"Fetch all builds from projects (UUIDs or names)"`
63+
6164
// query settings
6265
QueriesWithProjects []string `long:"list.query" env:"AZURE_DEVOPS_QUERIES" env-delim:" " description:"Pairs of query and project UUIDs in the form: '<queryId>@<projectId>'"`
6366

metrics_build.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -303,8 +303,20 @@ func (m *MetricsCollectorBuild) collectBuilds(ctx context.Context, logger *zap.S
303303
}
304304

305305
func (m *MetricsCollectorBuild) collectBuildsTimeline(ctx context.Context, logger *zap.SugaredLogger, callback chan<- func(), project devopsClient.Project) {
306+
<<<<<<< main
307+
minTime := time.Now().Add(-opts.Limit.BuildHistoryDuration)
308+
309+
statusFilter := "completed"
310+
if arrayStringContains(opts.AzureDevops.FetchAllBuildsFilter, project.Name) || arrayStringContains(opts.AzureDevops.FetchAllBuildsFilter, project.Id) {
311+
logger.Info("fetching all builds for project " + project.Name)
312+
statusFilter = "all"
313+
}
314+
315+
list, err := AzureDevopsClient.ListBuildHistoryWithStatus(project.Id, minTime, statusFilter)
316+
=======
306317
minTime := time.Now().Add(-Opts.Limit.BuildHistoryDuration)
307318
list, err := AzureDevopsClient.ListBuildHistoryWithStatus(project.Id, minTime, "completed")
319+
>>>>>>> main
308320
if err != nil {
309321
logger.Error(err)
310322
return
@@ -316,8 +328,18 @@ func (m *MetricsCollectorBuild) collectBuildsTimeline(ctx context.Context, logge
316328
buildTaskMetric := m.Collector.GetMetricList("buildTask")
317329

318330
for _, build := range list.List {
331+
319332
timelineRecordList, _ := AzureDevopsClient.ListBuildTimeline(project.Id, int64ToString(build.Id))
320333
for _, timelineRecord := range timelineRecordList.List {
334+
335+
if opts.AzureDevops.FilterTimelineState != nil && !arrayStringContains(opts.AzureDevops.FilterTimelineState, timelineRecord.State) {
336+
continue
337+
}
338+
339+
if timelineRecord.Result == "" {
340+
timelineRecord.Result = "unknown"
341+
}
342+
321343
recordType := timelineRecord.RecordType
322344
switch strings.ToLower(recordType) {
323345
case "stage":
@@ -635,8 +657,20 @@ func (m *MetricsCollectorBuild) collectBuildsTimeline(ctx context.Context, logge
635657
}
636658

637659
func (m *MetricsCollectorBuild) collectBuildsTags(ctx context.Context, logger *zap.SugaredLogger, callback chan<- func(), project devopsClient.Project) {
660+
<<<<<<< main
661+
minTime := time.Now().Add(-opts.Limit.BuildHistoryDuration)
662+
663+
statusFilter := "completed"
664+
if arrayStringContains(opts.AzureDevops.FetchAllBuildsFilter, project.Name) || arrayStringContains(opts.AzureDevops.FetchAllBuildsFilter, project.Id) {
665+
logger.Info("fetching all builds for project " + project.Name)
666+
statusFilter = "all"
667+
}
668+
669+
list, err := AzureDevopsClient.ListBuildHistoryWithStatus(project.Id, minTime, statusFilter)
670+
=======
638671
minTime := time.Now().Add(-Opts.Limit.BuildHistoryDuration)
639672
list, err := AzureDevopsClient.ListBuildHistoryWithStatus(project.Id, minTime, "completed")
673+
>>>>>>> main
640674
if err != nil {
641675
logger.Error(err)
642676
return

0 commit comments

Comments
 (0)