gitlab-radiator 4.4.5 → 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,11 +1,13 @@
1
- import {config} from './config.js'
2
1
  import fs from 'fs'
3
2
  import less from 'less'
4
3
  import path from 'path'
4
+ import {config} from './config.ts'
5
+ import type {Config} from './config.ts'
6
+ import type {Request, Response} from 'express'
5
7
 
6
8
  const filename = path.join('public', 'client.less')
7
9
 
8
- export async function serveLessAsCss(req, res) {
10
+ export async function serveLessAsCss(_req: Request, res: Response) {
9
11
  try {
10
12
  const source = await fs.promises.readFile(filename, 'utf-8')
11
13
  const {css} = await less.render(withColorOverrides(source), {filename})
@@ -17,10 +19,15 @@ export async function serveLessAsCss(req, res) {
17
19
  }
18
20
  }
19
21
 
20
- function withColorOverrides(source) {
22
+ function withColorOverrides(source: string) {
23
+ const {colors} = config
24
+ if (!colors) {
25
+ return source
26
+ }
27
+
21
28
  let colorLess = ''
22
- Object.keys(config.colors).forEach((stateName) => {
23
- colorLess += `@${stateName}-color:${config.colors[stateName]};`
29
+ Object.keys(colors).forEach((stateName) => {
30
+ colorLess += `@${stateName}-color:${colors[stateName as keyof Config["colors"]]};`
24
31
  })
25
32
  return source + colorLess
26
33
  }
package/src/auth.js DELETED
@@ -1,21 +0,0 @@
1
- import authenticate from 'basic-auth'
2
-
3
- export function basicAuth(auth) {
4
- if (!auth || !auth.username || !auth.password) {
5
-
6
- console.log('No authentication configured')
7
- return (req, res, next) => next()
8
- }
9
-
10
-
11
- console.log('HTTP basic auth enabled')
12
- return (req, res, next) => {
13
- const {name, pass} = authenticate(req) || {}
14
- if (auth.username === name && auth.password === pass) {
15
- next()
16
- } else {
17
- res.setHeader('WWW-Authenticate', 'Basic realm="gitlab-radiator"')
18
- res.status(401).end()
19
- }
20
- }
21
- }
package/src/config.js DELETED
@@ -1,44 +0,0 @@
1
- import assert from 'assert'
2
- import fs from 'fs'
3
- import os from 'os'
4
- import yaml from 'js-yaml'
5
-
6
- const configFile = expandTilde(process.env.GITLAB_RADIATOR_CONFIG || '~/.gitlab-radiator.yml')
7
- const yamlContent = fs.readFileSync(configFile, 'utf8')
8
- export const config = validate(yaml.load(yamlContent))
9
-
10
- config.interval = Number(config.interval || 10) * 1000
11
- config.port = Number(config.port || 3000)
12
- config.zoom = Number(config.zoom || 1.0)
13
- config.columns = Number(config.columns || 1)
14
- config.horizontal = config.horizontal || false
15
- config.groupSuccessfulProjects = config.groupSuccessfulProjects || false
16
- config.projectsOrder = config.projectsOrder || ['name']
17
- config.gitlabs = config.gitlabs.map((gitlab) => {
18
- return {
19
- url: gitlab.url,
20
- ignoreArchived: gitlab.ignoreArchived === undefined ? true : gitlab.ignoreArchived,
21
- maxNonFailedJobsVisible: Number(gitlab.maxNonFailedJobsVisible || 999999),
22
- ca: gitlab.caFile && fs.existsSync(gitlab.caFile, 'utf-8') ? fs.readFileSync(gitlab.caFile) : undefined,
23
- 'access-token': gitlab['access-token'] || process.env.GITLAB_ACCESS_TOKEN,
24
- projects: {
25
- excludePipelineStatus: (gitlab.projects || {}).excludePipelineStatus || [],
26
- include: (gitlab.projects || {}).include || '',
27
- exclude: (gitlab.projects || {}).exclude || ''
28
- }
29
- }
30
- })
31
- config.colors = config.colors || {}
32
-
33
- function expandTilde(path) {
34
- return path.replace(/^~($|\/|\\)/, `${os.homedir()}$1`)
35
- }
36
-
37
- function validate(cfg) {
38
- assert.ok(cfg.gitlabs, 'Mandatory gitlab properties missing from configuration file')
39
- cfg.gitlabs.forEach((gitlab) => {
40
- assert.ok(gitlab.url, 'Mandatory gitlab url missing from configuration file')
41
- assert.ok(gitlab['access-token'] || process.env.GITLAB_ACCESS_TOKEN, 'Mandatory gitlab access token missing from configuration (and none present at GITLAB_ACCESS_TOKEN env variable)')
42
- })
43
- return cfg
44
- }
@@ -1,27 +0,0 @@
1
- import axios from 'axios'
2
- import https from 'https'
3
- import url from 'url'
4
-
5
- export function gitlabRequest(path, params, gitlab) {
6
- return lazyClient(gitlab).get(path, {params})
7
- }
8
-
9
- const clients = new Map()
10
-
11
- function lazyClient(gitlab) {
12
- const gitlabUrl = gitlab.url
13
- if (gitlabUrl === undefined) {
14
-
15
- console.log('Got undefined url for ' + JSON.stringify(gitlab))
16
- }
17
- if (!clients.get(gitlabUrl)) {
18
- const client = axios.create({
19
- baseURL: url.resolve(gitlabUrl, '/api/v4/'),
20
- headers: {'PRIVATE-TOKEN': gitlab['access-token']},
21
- httpsAgent: new https.Agent({keepAlive: true, ca: gitlab.ca}),
22
- timeout: 30 * 1000
23
- })
24
- clients.set(gitlabUrl, client)
25
- }
26
- return clients.get(gitlabUrl)
27
- }
@@ -1,55 +0,0 @@
1
- import {fetchLatestPipelines} from './pipelines.js'
2
- import {fetchProjects} from './projects.js'
3
-
4
- export async function update(config) {
5
- const projectsWithPipelines = await loadProjectsWithPipelines(config)
6
- return projectsWithPipelines
7
- .filter(project => project.pipelines.length > 0)
8
- }
9
-
10
- async function loadProjectsWithPipelines(config) {
11
- const allProjectsWithPipelines = []
12
- await Promise.all(config.gitlabs.map(async (gitlab) => {
13
- const projects = (await fetchProjects(gitlab))
14
- .map(project => ({
15
- ...project,
16
- maxNonFailedJobsVisible: gitlab.maxNonFailedJobsVisible
17
- }))
18
-
19
- for (const project of projects) {
20
- allProjectsWithPipelines.push(await projectWithPipelines(project, gitlab))
21
- }
22
- }))
23
- return allProjectsWithPipelines
24
- }
25
-
26
- async function projectWithPipelines(project, config) {
27
- const pipelines = filterOutEmpty(await fetchLatestPipelines(project.id, config))
28
- .filter(excludePipelineStatusFilter(config))
29
- const status = defaultBranchStatus(project, pipelines)
30
- return {
31
- ...project,
32
- pipelines,
33
- status
34
- }
35
- }
36
-
37
- function defaultBranchStatus(project, pipelines) {
38
- const [head] = pipelines
39
- .filter(({ref}) => ref === project.default_branch)
40
- .map(({status}) => status)
41
- return head
42
- }
43
-
44
- function filterOutEmpty(pipelines) {
45
- return pipelines.filter(pipeline => pipeline.stages)
46
- }
47
-
48
- function excludePipelineStatusFilter(config) {
49
- return pipeline => {
50
- if (config.projects && config.projects.excludePipelineStatus) {
51
- return !config.projects.excludePipelineStatus.includes(pipeline.status)
52
- }
53
- return true
54
- }
55
- }
@@ -1,119 +0,0 @@
1
- import _ from 'lodash'
2
- import {gitlabRequest} from './client.js'
3
-
4
- export async function fetchLatestPipelines(projectId, gitlab) {
5
- const pipelines = await fetchLatestAndMasterPipeline(projectId, gitlab)
6
-
7
- const pipelinesWithStages = []
8
- for (const {id, ref, status} of pipelines) {
9
- const {commit, stages} = await fetchJobs(projectId, id, gitlab)
10
- const downstreamStages = await fetchDownstreamJobs(projectId, id, gitlab)
11
- pipelinesWithStages.push({
12
- id,
13
- ref,
14
- status,
15
- commit,
16
- stages: stages.concat(downstreamStages)
17
- })
18
- }
19
- return pipelinesWithStages
20
- }
21
-
22
-
23
- async function fetchLatestAndMasterPipeline(projectId, config) {
24
- const pipelines = await fetchPipelines(projectId, config, {per_page: 100})
25
- if (pipelines.length === 0) {
26
- return []
27
- }
28
- const latestPipeline = _.take(pipelines, 1)
29
- if (latestPipeline[0].ref === 'master') {
30
- return latestPipeline
31
- }
32
- const latestMasterPipeline = _(pipelines).filter({ref: 'master'}).take(1).value()
33
- if (latestMasterPipeline.length > 0) {
34
- return latestPipeline.concat(latestMasterPipeline)
35
- }
36
- const masterPipelines = await fetchPipelines(projectId, config, {per_page: 50, ref: 'master'})
37
- return latestPipeline.concat(_.take(masterPipelines, 1))
38
- }
39
-
40
- async function fetchPipelines(projectId, config, options) {
41
- const {data: pipelines} = await gitlabRequest(`/projects/${projectId}/pipelines`, options, config)
42
- return pipelines.filter(pipeline => pipeline.status !== 'skipped')
43
- }
44
-
45
- async function fetchDownstreamJobs(projectId, pipelineId, config) {
46
- const {data: gitlabBridgeJobs} = await gitlabRequest(`/projects/${projectId}/pipelines/${pipelineId}/bridges`, {per_page: 100}, config)
47
- const childPipelines = gitlabBridgeJobs.filter(bridge => bridge.downstream_pipeline !== null && bridge.downstream_pipeline.status !== 'skipped')
48
-
49
- const downstreamStages = []
50
- for(const childPipeline of childPipelines) {
51
- const {stages} = await fetchJobs(childPipeline.downstream_pipeline.project_id, childPipeline.downstream_pipeline.id, config)
52
- downstreamStages.push(stages.map(stage => ({
53
- ...stage,
54
- name: `${childPipeline.stage}:${stage.name}`
55
- })))
56
- }
57
- return downstreamStages.flat()
58
- }
59
-
60
- async function fetchJobs(projectId, pipelineId, config) {
61
- const {data: gitlabJobs} = await gitlabRequest(`/projects/${projectId}/pipelines/${pipelineId}/jobs?include_retried=true`, {per_page: 100}, config)
62
- if (gitlabJobs.length === 0) {
63
- return {commit: undefined, stages: []}
64
- }
65
-
66
- const commit = findCommit(gitlabJobs)
67
- const stages = _(gitlabJobs)
68
- .map(job => ({
69
- id: job.id,
70
- status: job.status,
71
- stage: job.stage,
72
- name: job.name,
73
- startedAt: job.started_at,
74
- finishedAt: job.finished_at,
75
- url: job.web_url
76
- }))
77
- .orderBy('id')
78
- .groupBy('stage')
79
- .mapValues(mergeRetriedJobs)
80
- .mapValues(cleanup)
81
- .toPairs()
82
- .map(([name, jobs]) => ({name, jobs: _.sortBy(jobs, 'name')}))
83
- .value()
84
-
85
- return {
86
- commit,
87
- stages
88
- }
89
- }
90
-
91
- function findCommit(jobs) {
92
- const [job] = jobs.filter(j => j.commit)
93
- if (!job) {
94
- return null
95
- }
96
- return {
97
- title: job.commit.title,
98
- author: job.commit.author_name
99
- }
100
- }
101
-
102
- function mergeRetriedJobs(jobs) {
103
- return jobs.reduce((mergedJobs, job) => {
104
- const index = mergedJobs.findIndex(mergedJob => mergedJob.name === job.name)
105
- if (index >= 0) {
106
- mergedJobs[index] = job
107
- } else {
108
- mergedJobs.push(job)
109
- }
110
- return mergedJobs
111
- }, [])
112
- }
113
-
114
- function cleanup(jobs) {
115
- return _(jobs)
116
- .map(job => _.omitBy(job, _.isNull))
117
- .map(job => _.omit(job, 'stage'))
118
- .value()
119
- }
@@ -1,73 +0,0 @@
1
- import {gitlabRequest} from './client.js'
2
-
3
- export async function fetchProjects(gitlab) {
4
- const projects = await fetchOwnProjects(gitlab)
5
- return projects
6
- // Ignore projects for which CI/CD is not enabled
7
- .filter(project => project.jobs_enabled)
8
- .map(projectMapper)
9
- .filter(includeRegexFilter(gitlab))
10
- .filter(excludeRegexFilter(gitlab))
11
- .filter(archivedFilter(gitlab))
12
- }
13
-
14
- async function fetchOwnProjects(gitlab) {
15
- const projects = []
16
- const SAFETY_MAX_PAGE = 10
17
- for (let page = 1; page <= SAFETY_MAX_PAGE; page += 1) {
18
-
19
- const {data, headers} = await gitlabRequest('/projects', {page, per_page: 100, membership: true}, gitlab)
20
- projects.push(data)
21
- if (data.length === 0 || !headers['x-next-page']) {
22
- break
23
- }
24
- }
25
- return projects.flat()
26
- }
27
-
28
- function projectMapper(project) {
29
- return {
30
- id: project.id,
31
- name: project.path_with_namespace,
32
- nameWithoutNamespace: project.path,
33
- group: getGroupName(project),
34
- archived: project.archived,
35
- default_branch: project.default_branch || 'master',
36
- url: project.web_url,
37
- tags: (project.tag_list || []).map(t => t.toLowerCase())
38
- }
39
- }
40
-
41
- function getGroupName(project) {
42
- const pathWithNameSpace = project.path_with_namespace
43
- return pathWithNameSpace.split('/')[0]
44
- }
45
-
46
- function includeRegexFilter(config) {
47
- return project => {
48
- if (config.projects && config.projects.include) {
49
- const includeRegex = new RegExp(config.projects.include, "i")
50
- return includeRegex.test(project.name)
51
- }
52
- return true
53
- }
54
- }
55
-
56
- function excludeRegexFilter(config) {
57
- return project => {
58
- if (config.projects && config.projects.exclude) {
59
- const excludeRegex = new RegExp(config.projects.exclude, "i")
60
- return !excludeRegex.test(project.name)
61
- }
62
- return true
63
- }
64
- }
65
-
66
- function archivedFilter(config) {
67
- return project => {
68
- if (config.ignoreArchived) {
69
- return !project.archived
70
- }
71
- return true
72
- }
73
- }
@@ -1,18 +0,0 @@
1
- import {gitlabRequest} from './client.js'
2
-
3
- export async function fetchOfflineRunners(gitlab) {
4
- const runners = await fetchRunners(gitlab)
5
- const offline = runners.filter(r => r.status === 'offline')
6
- return {
7
- offline,
8
- totalCount: runners.length
9
- }
10
- }
11
-
12
- async function fetchRunners(gitlab) {
13
- const {data: runners} = await gitlabRequest('/runners', {}, gitlab)
14
- return runners.map(r => ({
15
- name: r.description || r.id,
16
- status: r.status
17
- }))
18
- }