oss-stats 0.0.1
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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +5 -0
- data/CONTRIBUTING.md +32 -0
- data/Gemfile +11 -0
- data/LICENSE +201 -0
- data/README.md +110 -0
- data/bin/meeting_stats +450 -0
- data/bin/pipeline_visibility_stats +636 -0
- data/bin/promise_stats +312 -0
- data/bin/repo_stats +113 -0
- data/docs/MeetingStats.md +69 -0
- data/docs/PipelineVisibilityStats.md +51 -0
- data/docs/PromiseStats.md +56 -0
- data/docs/RepoStats.md +130 -0
- data/examples/meeting_stats_config.rb +22 -0
- data/examples/promise_stats_config.rb +23 -0
- data/examples/repo_stats_config.rb +49 -0
- data/initialization_data/Gemfile +3 -0
- data/initialization_data/README.md +20 -0
- data/initialization_data/rubocop.yml +2 -0
- data/lib/oss_stats/buildkite_client.rb +252 -0
- data/lib/oss_stats/buildkite_token.rb +15 -0
- data/lib/oss_stats/config/meeting_stats.rb +36 -0
- data/lib/oss_stats/config/promise_stats.rb +22 -0
- data/lib/oss_stats/config/repo_stats.rb +47 -0
- data/lib/oss_stats/config/shared.rb +43 -0
- data/lib/oss_stats/github_client.rb +55 -0
- data/lib/oss_stats/github_token.rb +23 -0
- data/lib/oss_stats/log.rb +25 -0
- data/lib/oss_stats/repo_stats.rb +1048 -0
- data/lib/oss_stats/version.rb +3 -0
- data/oss-stats.gemspec +39 -0
- data/spec/buildkite_client_spec.rb +171 -0
- data/spec/repo_stats_spec.rb +1242 -0
- metadata +181 -0
data/bin/promise_stats
ADDED
@@ -0,0 +1,312 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'sqlite3'
|
4
|
+
require 'optparse'
|
5
|
+
require 'date'
|
6
|
+
|
7
|
+
require_relative '../lib/oss_stats/log'
|
8
|
+
require_relative '../lib/oss_stats/config/promise_stats'
|
9
|
+
|
10
|
+
log.level = :info
|
11
|
+
|
12
|
+
def initialize_db(path)
|
13
|
+
db = SQLite3::Database.new(path)
|
14
|
+
db.execute <<-SQL
|
15
|
+
CREATE TABLE IF NOT EXISTS promises (
|
16
|
+
id INTEGER PRIMARY KEY,
|
17
|
+
description TEXT NOT NULL,
|
18
|
+
promised_on DATE NOT NULL,
|
19
|
+
resolved_on DATE,
|
20
|
+
reference TEXT,
|
21
|
+
status TEXT DEFAULT 'pending'
|
22
|
+
);
|
23
|
+
SQL
|
24
|
+
db.close
|
25
|
+
end
|
26
|
+
|
27
|
+
def parse_date(str)
|
28
|
+
Date.parse(str)
|
29
|
+
rescue ArgumentError
|
30
|
+
puts "Invalid date: #{str}"
|
31
|
+
exit 1
|
32
|
+
end
|
33
|
+
|
34
|
+
def add_promise(config, desc, date, ref)
|
35
|
+
db = SQLite3::Database.new(config.db_file)
|
36
|
+
db.execute(
|
37
|
+
"INSERT INTO promises (description, promised_on, reference)
|
38
|
+
VALUES (?, ?, ?)",
|
39
|
+
[desc, date.to_s, ref],
|
40
|
+
)
|
41
|
+
db.close
|
42
|
+
puts 'Promise added.'
|
43
|
+
end
|
44
|
+
|
45
|
+
def resolve_promise(config, date)
|
46
|
+
update_promise_status(config, date, 'resolved')
|
47
|
+
end
|
48
|
+
|
49
|
+
def abandon_promise(config, date)
|
50
|
+
update_promise_status(config, date, 'abandoned')
|
51
|
+
end
|
52
|
+
|
53
|
+
def update_promise_status(config, date, new_status)
|
54
|
+
db = SQLite3::Database.new(config.db_file)
|
55
|
+
rows = db.execute(
|
56
|
+
"SELECT id, description FROM promises WHERE status = 'pending'",
|
57
|
+
)
|
58
|
+
if rows.empty?
|
59
|
+
puts 'No pending promises.'
|
60
|
+
db.close
|
61
|
+
return
|
62
|
+
end
|
63
|
+
|
64
|
+
puts 'Pending promises:'
|
65
|
+
rows.each_with_index do |(id, desc), _i|
|
66
|
+
puts "#{id}. #{desc}"
|
67
|
+
end
|
68
|
+
|
69
|
+
print 'Enter ID to update: '
|
70
|
+
chosen_id = gets.strip.to_i
|
71
|
+
if rows.any? { |r| r[0] == chosen_id }
|
72
|
+
if new_status == 'resolved'
|
73
|
+
db.execute(
|
74
|
+
"UPDATE promises SET resolved_on = ?, status = 'resolved' " +
|
75
|
+
'WHERE id = ?',
|
76
|
+
[date.to_s, chosen_id],
|
77
|
+
)
|
78
|
+
elsif new_status == 'abandoned'
|
79
|
+
db.execute(
|
80
|
+
"UPDATE promises SET resolved_on = ?, status = 'abandoned' " +
|
81
|
+
'WHERE id = ?',
|
82
|
+
[date.to_s, chosen_id],
|
83
|
+
)
|
84
|
+
end
|
85
|
+
puts "Promise marked as #{new_status}."
|
86
|
+
else
|
87
|
+
puts 'Invalid ID.'
|
88
|
+
end
|
89
|
+
db.close
|
90
|
+
end
|
91
|
+
|
92
|
+
def edit_promise(config)
|
93
|
+
db = SQLite3::Database.new(config.db_file)
|
94
|
+
rows = db.execute(
|
95
|
+
'SELECT id, description, promised_on, reference FROM promises',
|
96
|
+
)
|
97
|
+
if rows.empty?
|
98
|
+
puts 'No promises available.'
|
99
|
+
db.close
|
100
|
+
return
|
101
|
+
end
|
102
|
+
|
103
|
+
puts 'All promises:'
|
104
|
+
rows.each_with_index do |(id, desc, date, ref), i|
|
105
|
+
puts "#{i + 1}. #{desc} (ID: #{id}, Date: #{date}, Ref: #{ref})"
|
106
|
+
end
|
107
|
+
|
108
|
+
print 'Enter ID to edit: '
|
109
|
+
chosen_id = gets.strip.to_i
|
110
|
+
entry = rows.find { |r| r[0] == chosen_id }
|
111
|
+
|
112
|
+
unless entry
|
113
|
+
puts 'Invalid ID.'
|
114
|
+
db.close
|
115
|
+
return
|
116
|
+
end
|
117
|
+
|
118
|
+
print "New description [#{entry[1]}]: "
|
119
|
+
new_desc = gets.strip
|
120
|
+
new_desc = entry[1] if new_desc.empty?
|
121
|
+
|
122
|
+
print "New date (YYYY-MM-DD) [#{entry[2]}]: "
|
123
|
+
new_date = gets.strip
|
124
|
+
new_date = entry[2] if new_date.empty?
|
125
|
+
new_date = parse_date(new_date)
|
126
|
+
|
127
|
+
print "New reference [#{entry[3]}]: "
|
128
|
+
new_ref = gets.strip
|
129
|
+
new_ref = entry[3] if new_ref.empty?
|
130
|
+
|
131
|
+
db.execute(
|
132
|
+
"UPDATE promises
|
133
|
+
SET description = ?, promised_on = ?, reference = ?
|
134
|
+
WHERE id = ?",
|
135
|
+
[new_desc, new_date.to_s, new_ref, chosen_id],
|
136
|
+
)
|
137
|
+
puts 'Promise updated.'
|
138
|
+
db.close
|
139
|
+
end
|
140
|
+
|
141
|
+
def output(fh, msg)
|
142
|
+
if fh
|
143
|
+
fh.write("#{msg}\n")
|
144
|
+
else
|
145
|
+
log.info(msg)
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
def show_status(config, include_abandoned: false)
|
150
|
+
db = SQLite3::Database.new(config.db_file)
|
151
|
+
query = "SELECT description, promised_on, reference, status
|
152
|
+
FROM promises WHERE status = 'pending'"
|
153
|
+
if include_abandoned
|
154
|
+
query += " OR status = 'abandoned'"
|
155
|
+
end
|
156
|
+
rows = db.execute(query)
|
157
|
+
db.close
|
158
|
+
|
159
|
+
fh = nil
|
160
|
+
if config.output
|
161
|
+
fh = open(config.output, 'w')
|
162
|
+
log.info("Generating report and writing to #{config.output}")
|
163
|
+
end
|
164
|
+
|
165
|
+
output(fh, config.header)
|
166
|
+
|
167
|
+
if rows.empty?
|
168
|
+
output(fh, 'No matching promises.')
|
169
|
+
return
|
170
|
+
end
|
171
|
+
|
172
|
+
today = Date.today
|
173
|
+
rows.each do |desc, promised_on, ref, status|
|
174
|
+
days = (today - Date.parse(promised_on)).to_i
|
175
|
+
label = "#{desc} (#{days} days ago)"
|
176
|
+
label += " [ref: #{ref}]" unless ref.empty?
|
177
|
+
label += " [#{status}]" if status == 'abandoned'
|
178
|
+
output(fh, "* #{label}")
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
def prompt_date(txt = 'Date')
|
183
|
+
parse_date(prompt(txt, Date.today.to_s))
|
184
|
+
end
|
185
|
+
|
186
|
+
def prompt(txt, default = nil)
|
187
|
+
p = txt
|
188
|
+
if default
|
189
|
+
p << " [#{default}]"
|
190
|
+
end
|
191
|
+
print "#{p}: "
|
192
|
+
resp = gets.strip
|
193
|
+
if resp.empty? && default
|
194
|
+
return default
|
195
|
+
end
|
196
|
+
resp
|
197
|
+
end
|
198
|
+
|
199
|
+
def main
|
200
|
+
if ARGV.empty? || %w{--help -h}.include?(ARGV[0])
|
201
|
+
puts <<~HELP
|
202
|
+
Usage: #{$PROGRAM_NAME} [subcommand] [options]
|
203
|
+
|
204
|
+
Subcommands:
|
205
|
+
add-promise Add a new promise
|
206
|
+
resolve-promise Resolve a pending promise
|
207
|
+
abandon-promise Mark a promise as abandoned
|
208
|
+
edit-promise Edit an existing promise
|
209
|
+
status Show unresolved promises
|
210
|
+
|
211
|
+
Options:
|
212
|
+
--date=YYYY-MM-DD Date of action
|
213
|
+
--promise="text" Promise description
|
214
|
+
--reference="text" Optional reference
|
215
|
+
--db-file=FILE SQLite3 DB file
|
216
|
+
--include-abandoned Show abandoned in status
|
217
|
+
|
218
|
+
Example:
|
219
|
+
#{$PROGRAM_NAME} add-promise --promise="Call mom" --date=2025-05-08
|
220
|
+
HELP
|
221
|
+
exit
|
222
|
+
end
|
223
|
+
|
224
|
+
options = {}
|
225
|
+
OptionParser.new do |opts|
|
226
|
+
opts.on('--date=DATE', 'Date (YYYY-MM-DD)') do |d|
|
227
|
+
options[:date] = parse_date(d)
|
228
|
+
end
|
229
|
+
|
230
|
+
opts.on('--promise=TEXT', 'Promise text') do |p|
|
231
|
+
options[:promise] = p
|
232
|
+
end
|
233
|
+
|
234
|
+
opts.on('--reference=TEXT', 'Optional reference') do |r|
|
235
|
+
options[:reference] = r
|
236
|
+
end
|
237
|
+
|
238
|
+
opts.on(
|
239
|
+
'-l LEVEL',
|
240
|
+
'--log-level LEVEL',
|
241
|
+
'Set logging level to LEVEL. [default: info]',
|
242
|
+
) do |level|
|
243
|
+
options[:log_level] = level.to_sym
|
244
|
+
end
|
245
|
+
|
246
|
+
opts.on('--db-file=FILE', 'Path to DB file') do |f|
|
247
|
+
options[:db_file] = f
|
248
|
+
end
|
249
|
+
|
250
|
+
opts.on('--include-abandoned', 'Include abandoned in status') do
|
251
|
+
options[:include_abandoned] = true
|
252
|
+
end
|
253
|
+
|
254
|
+
opts.on(
|
255
|
+
'-o FILE',
|
256
|
+
'--output FILE',
|
257
|
+
'Write the output to FILE',
|
258
|
+
) { |v| options[:output] = v }
|
259
|
+
|
260
|
+
opts.on('-m MODE', '--mode MODE',
|
261
|
+
%w{add resolve abandon edit status},
|
262
|
+
'Mode to operate in') do |m|
|
263
|
+
options[:mode] = m
|
264
|
+
end
|
265
|
+
|
266
|
+
opts.on('-h', '--help', 'Show help') do
|
267
|
+
puts opts
|
268
|
+
exit
|
269
|
+
end
|
270
|
+
end.parse!
|
271
|
+
log.level = options[:log_level] if options[:log_level]
|
272
|
+
config = OssStats::Config::Promises
|
273
|
+
|
274
|
+
if options[:config]
|
275
|
+
expanded_config = File.expand_path(options[:config])
|
276
|
+
else
|
277
|
+
f = config.config_file
|
278
|
+
expanded_config = File.expand_path(f) if f
|
279
|
+
end
|
280
|
+
|
281
|
+
if expanded_config && File.exist?(expanded_config)
|
282
|
+
log.debug("Loading config from #{expanded_config}")
|
283
|
+
config.from_file(expanded_config)
|
284
|
+
end
|
285
|
+
config.merge!(options)
|
286
|
+
log.level = config.log_level
|
287
|
+
|
288
|
+
initialize_db(config.db_file)
|
289
|
+
|
290
|
+
case config.mode
|
291
|
+
when 'add'
|
292
|
+
desc = options[:promise] || prompt('Promise')
|
293
|
+
date = options[:date] || promt_date
|
294
|
+
ref = options[:reference] || prompt('Reference (optional)')
|
295
|
+
add_promise(config, desc, date, ref)
|
296
|
+
when 'resolve'
|
297
|
+
date = options[:date] || prompt_date('Resolution date')
|
298
|
+
resolve_promise(config, date)
|
299
|
+
when 'abandon'
|
300
|
+
date = options[:date] || prompt_date('Resolution date')
|
301
|
+
abandon_promise(config, date)
|
302
|
+
when 'edit'
|
303
|
+
edit_promise(config)
|
304
|
+
when 'status'
|
305
|
+
show_status(config, include_abandoned: options[:include_abandoned])
|
306
|
+
else
|
307
|
+
puts "Unknown mode: #{config.mode}"
|
308
|
+
exit 1
|
309
|
+
end
|
310
|
+
end
|
311
|
+
|
312
|
+
main if __FILE__ == $PROGRAM_NAME
|
data/bin/repo_stats
ADDED
@@ -0,0 +1,113 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require_relative '../lib/oss_stats/repo_stats'
|
4
|
+
|
5
|
+
# quick hack to rename this without .rb and jam methods in lib
|
6
|
+
extend OssStats::RepoStats
|
7
|
+
|
8
|
+
def main
|
9
|
+
parse_options
|
10
|
+
config = OssStats::Config::RepoStats
|
11
|
+
mode = config.mode
|
12
|
+
mode = %w{ci pr issue} if mode.include?('all')
|
13
|
+
|
14
|
+
organizations_to_process = determine_orgs_to_process
|
15
|
+
|
16
|
+
# Prepare list of repositories to process based on configuration
|
17
|
+
repos_to_process = []
|
18
|
+
if organizations_to_process.empty?
|
19
|
+
log.warn('No organizations or repositories configured to process. Exiting.')
|
20
|
+
exit 0
|
21
|
+
end
|
22
|
+
|
23
|
+
# get tokens early so we fail if we're missing them
|
24
|
+
gh_token = get_github_token!(config)
|
25
|
+
gh_client = Octokit::Client.new(
|
26
|
+
access_token: gh_token,
|
27
|
+
api_endpoint: config.github_api_endpoint,
|
28
|
+
)
|
29
|
+
|
30
|
+
if mode.include?('ci') && config.buildkite_org
|
31
|
+
bk_token = get_buildkite_token!(config)
|
32
|
+
bk_client = OssStats::BuildkiteClient.new(bk_token)
|
33
|
+
bk_pipelines_by_repo = bk_client.pipelines_by_repo(config.buildkite_org)
|
34
|
+
end
|
35
|
+
|
36
|
+
organizations_to_process.each do |org_name, org_level_config|
|
37
|
+
log.debug("Processing configuration for organization: #{org_name}")
|
38
|
+
repos = org_level_config['repositories'] || {}
|
39
|
+
repos.each do |repo_name, repo_level_config|
|
40
|
+
log.debug("Processing configuration for repository: #{repo_name}")
|
41
|
+
repos_to_process << get_effective_repo_settings(
|
42
|
+
org_name, repo_name, org_level_config, repo_level_config
|
43
|
+
)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
if repos_to_process.empty?
|
48
|
+
log.info(
|
49
|
+
'No repositories found to process after evaluating configurations.',
|
50
|
+
)
|
51
|
+
exit 0
|
52
|
+
end
|
53
|
+
|
54
|
+
# Process each configured repository
|
55
|
+
all_repo_stats = []
|
56
|
+
repos_to_process.each do |settings|
|
57
|
+
repo_full_name = "#{settings[:org]}/#{settings[:repo]}"
|
58
|
+
repo_url = "https://github.com/#{repo_full_name}"
|
59
|
+
|
60
|
+
repo_data = {
|
61
|
+
name: repo_full_name,
|
62
|
+
url: repo_url,
|
63
|
+
settings:,
|
64
|
+
pr_issue_stats: nil,
|
65
|
+
ci_failures: nil,
|
66
|
+
}
|
67
|
+
|
68
|
+
# Fetch PR and Issue stats if PR or Issue mode is active
|
69
|
+
if %w{pr issue}.any? { |m| mode.include?(m) }
|
70
|
+
repo_data[:pr_issue_stats] = get_pr_and_issue_stats(gh_client, settings)
|
71
|
+
end
|
72
|
+
|
73
|
+
# Fetch CI stats if CI mode is active
|
74
|
+
if mode.include?('ci')
|
75
|
+
repo_data[:ci_failures] = get_failed_tests_from_ci(
|
76
|
+
gh_client, bk_client, settings, bk_pipelines_by_repo
|
77
|
+
)
|
78
|
+
end
|
79
|
+
all_repo_stats << repo_data
|
80
|
+
end
|
81
|
+
|
82
|
+
filtered_repos = filter_repositories(all_repo_stats, config)
|
83
|
+
|
84
|
+
filtered_repos.each do |repo_data|
|
85
|
+
if OssStats::Config::RepoStats.no_links
|
86
|
+
log.info(
|
87
|
+
"\n* #{repo_data[:name]} Stats (Last #{repo_data[:settings][:days]}" +
|
88
|
+
' days) *',
|
89
|
+
)
|
90
|
+
else
|
91
|
+
log.info(
|
92
|
+
"\n*_[#{repo_data[:name]}](#{repo_data[:url]}) Stats " +
|
93
|
+
"(Last #{repo_data[:settings][:days]} days)_*",
|
94
|
+
)
|
95
|
+
end
|
96
|
+
|
97
|
+
if repo_data[:pr_issue_stats]
|
98
|
+
%w{PR Issue}.each do |type|
|
99
|
+
next unless mode.include?(type.downcase)
|
100
|
+
print_pr_or_issue_stats(
|
101
|
+
repo_data[:pr_issue_stats], type, config.include_list
|
102
|
+
)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
next unless repo_data[:ci_failures]
|
107
|
+
# Ensure CI mode was active for this data to be present and printed
|
108
|
+
next unless mode.include?('ci')
|
109
|
+
print_ci_status(repo_data[:ci_failures])
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
main if __FILE__ == $PROGRAM_NAME
|
@@ -0,0 +1,69 @@
|
|
1
|
+
# MeetingStats
|
2
|
+
|
3
|
+
Many open source projects have weekly meetings and it's important to know
|
4
|
+
that the relevant teams are showing up and reporting the expected data.
|
5
|
+
|
6
|
+
[meeting_stats](../bin/meeting_stats.rb) will allow you to record the results
|
7
|
+
of a meeting and then generate a report, including images with trends over
|
8
|
+
time.
|
9
|
+
|
10
|
+
You will **definitely** want a config file to make this useful for your
|
11
|
+
project, and you can see
|
12
|
+
[examples/meeting_stats_config.rb](../examples/meeting_stats_config.rb) for an
|
13
|
+
example.
|
14
|
+
|
15
|
+
It keeps all data in a SQLite DB. You can specify where this is with
|
16
|
+
`--db-file`, and the default is `./data/meeting_data.sqlite3`.
|
17
|
+
|
18
|
+
All CLI options can be specified in the config file.
|
19
|
+
|
20
|
+
There are several modes, discussed below.
|
21
|
+
|
22
|
+
## Modes
|
23
|
+
|
24
|
+
### Generate
|
25
|
+
|
26
|
+
This mode generates the markdown file with a table for every meeting and graphs
|
27
|
+
at the top to show statistics over time.The markdown file is written to stdout
|
28
|
+
by default, unless `--output` is specified.
|
29
|
+
|
30
|
+
This mode is activated with `--mode generate`.
|
31
|
+
|
32
|
+
This mode implicitly runs the `generate_plot` mode to regenerate the graph
|
33
|
+
images. See the section on that mode for more details.
|
34
|
+
|
35
|
+
### Generate Plot
|
36
|
+
|
37
|
+
This mode regenerate the plot graphs that show data over time (which are linked
|
38
|
+
in the Markdown generated by `generate` mode). These graphs are written to
|
39
|
+
`./images` by default, but you can change this with `--image-dir`.
|
40
|
+
|
41
|
+
This mode is activated with `--mode generate_plot`.
|
42
|
+
|
43
|
+
### Summary
|
44
|
+
|
45
|
+
This mode print short summary of the last three meetings in Slack-friendly
|
46
|
+
Markdown format. Sample output is:
|
47
|
+
|
48
|
+
```markdown
|
49
|
+
* 2025-06-05:
|
50
|
+
* Teams reported: 7 out of 7 (100%)
|
51
|
+
* Teams reporting build status: 6 out of 7 (85.71%)
|
52
|
+
* 2025-05-29:
|
53
|
+
* Teams reported: 7 out of 7 (100%)
|
54
|
+
* Teams reporting build status: 6 out of 7 (85.71%)
|
55
|
+
* 2025-05-22:
|
56
|
+
* Teams reported: 7 out of 7 (100%)
|
57
|
+
* Teams reporting build status: 6 out of 7 (85.71%)
|
58
|
+
```
|
59
|
+
|
60
|
+
### Record
|
61
|
+
|
62
|
+
This mode is how you feed data into the database. It will prompt you to select
|
63
|
+
a team, and then ask information such as if they were present, and if they
|
64
|
+
reported their build status. Then it'll ask you to pick another team, and will
|
65
|
+
repeat until you hit `q`, at which point it'll prompt to commit the new meeting
|
66
|
+
information to the database.
|
67
|
+
|
68
|
+
It defaults to the most recent Thursday for historical reasons, use `--date` to
|
69
|
+
specify the date of the meeting.
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# Pipeline visibility stats
|
2
|
+
|
3
|
+
[pipeline_visibility_stats](../bin/pipeline_visibility_stats.rb) is a tool
|
4
|
+
which walks Buildkite pipelines associated with your public GitHub repositories
|
5
|
+
to ensure they are visible to contributors. It has a variety of options to
|
6
|
+
exclude pipelines intended to be private (for example, pipelines that may have
|
7
|
+
secrets to do pushes).
|
8
|
+
|
9
|
+
There are two providers: buildkite and expeditor. Expeditor is deprecated
|
10
|
+
and will go away.
|
11
|
+
|
12
|
+
## Buildkite Provider
|
13
|
+
|
14
|
+
This attempts to find improperly configured pipelines in two ways:
|
15
|
+
|
16
|
+
* Given the buildkite repo, gets a list of all pipelines and builds a
|
17
|
+
map of GitHub Repos to pipelines. Then, it walks all GH repos
|
18
|
+
repos (either in the GH Org, or specified in the config), and checks
|
19
|
+
to see if there are buildkite repos associated with it, and if there are,
|
20
|
+
checks their visibility settings
|
21
|
+
* Walks the most recent 10 PRs, and checks for any status checks that are
|
22
|
+
on buildkite, and checks if it can see them, and if so, checks their
|
23
|
+
visibility (it reporst them as private if it cannot see them)
|
24
|
+
|
25
|
+
This is likely to include pipelines expected to be public such as those
|
26
|
+
added adhoc to specific PRs to do builds. You can use --skip to add skip
|
27
|
+
patterns (partial-match text) to avoid counting those.
|
28
|
+
|
29
|
+
Example output looks like (this is truncated for brevity):
|
30
|
+
|
31
|
+
```markdown
|
32
|
+
# Chef Pipeline Visibility Report 2025-06-14
|
33
|
+
|
34
|
+
* [chef/chef-cli](https://github.com/chef/chef-cli)
|
35
|
+
* chef/chef-chef-cli-main-habitat-test
|
36
|
+
* [chef/chef-foundation](https://github.com/chef/chef-foundation)
|
37
|
+
* chef/chef-chef-foundation-main-verify
|
38
|
+
* [chef/chef-powershell-shim](https://github.com/chef/chef-powershell-shim)
|
39
|
+
* chef/chef-chef-powershell-shim-pipeline-18-stable-habitat-build
|
40
|
+
* chef/chef-chef-powershell-shim-pipeline-stable-18-verify
|
41
|
+
```
|
42
|
+
|
43
|
+
At a minimum you will need the optoins `--github-org` and `--buildkite-org`.
|
44
|
+
|
45
|
+
## Expeditor Provider
|
46
|
+
|
47
|
+
The Expeditor Provider parses expeditor configs, which is Chef-specific and not
|
48
|
+
open source. However, much of the code is generic and this could be adapted to
|
49
|
+
other things.
|
50
|
+
|
51
|
+
If you do want to use it, you can do so with `--provider=expeditor`.
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# Promises
|
2
|
+
|
3
|
+
[promises](../bin/promises.rb) allows you to add, edit, resolve, abandon, and
|
4
|
+
report on promises made. This can be useful for both promises made to the
|
5
|
+
community or promises made between teams.
|
6
|
+
|
7
|
+
You likely will probably want a config file for this; a sample
|
8
|
+
is provided in [../examples/promises_config.rb](../examples/promises_config.rb).
|
9
|
+
|
10
|
+
There are several sub-commands, discussed below.
|
11
|
+
|
12
|
+
## Subcommands
|
13
|
+
|
14
|
+
### add-promise
|
15
|
+
|
16
|
+
This subcommand allows you to add a new promise. By default the data will be
|
17
|
+
assumed to be today, but it may be changed with `--date`. You will be prompted
|
18
|
+
for the promise, but you can specify it with `--promise`.
|
19
|
+
|
20
|
+
Promises have 3 pieces of data associated with them:
|
21
|
+
|
22
|
+
* `promise` - what was actually promised
|
23
|
+
* `date` - the date on which it was promised
|
24
|
+
* `reference` - additional reference about this promise. This could be
|
25
|
+
a link to the message, post, notes, etc. in which the promise was made,
|
26
|
+
for example. It is arbitrary text and may be whatever you wish.
|
27
|
+
|
28
|
+
### resolve-promise
|
29
|
+
|
30
|
+
Mark a promise as resolved. It will no longer be reported on by default, and
|
31
|
+
the date it was resolved will be recorded in the database.
|
32
|
+
|
33
|
+
### abandon-promise
|
34
|
+
|
35
|
+
This marks a promise as abandoned and is useful in the case of a promise that
|
36
|
+
is either no longer relevant or is not expected to be resolved.
|
37
|
+
|
38
|
+
### edit-promise
|
39
|
+
|
40
|
+
If you want to alter information about a promise in the database, this will
|
41
|
+
re-prompt you for the information and update the database accordingly.
|
42
|
+
|
43
|
+
### status
|
44
|
+
|
45
|
+
This will output information about the promises in a Slack-friendly Markdown
|
46
|
+
format. It will list all open promises and how long they've been open. Example
|
47
|
+
output is:
|
48
|
+
|
49
|
+
```markdown
|
50
|
+
# Promises Report 2025-06-14
|
51
|
+
|
52
|
+
* Publish Chef 19 / 2025 plan (247 days ago)
|
53
|
+
* Fedora 41+ support (227 days ago)
|
54
|
+
```
|
55
|
+
|
56
|
+
You can include abandoned promises with `--include-abandoned`.
|