buildkite-builder 1.0.0.beta.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/.gitignore +11 -0
- data/.rspec +3 -0
- data/CHANGELOG.md +0 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +13 -0
- data/Gemfile.lock +56 -0
- data/LICENSE.txt +21 -0
- data/README.md +44 -0
- data/Rakefile +6 -0
- data/bin/buildkite-builder +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/buildkite-builder.gemspec +28 -0
- data/lib/buildkite-builder.rb +14 -0
- data/lib/buildkite/builder.rb +54 -0
- data/lib/buildkite/builder/commands.rb +49 -0
- data/lib/buildkite/builder/commands/abstract.rb +64 -0
- data/lib/buildkite/builder/commands/files.rb +25 -0
- data/lib/buildkite/builder/commands/preview.rb +29 -0
- data/lib/buildkite/builder/definition.rb +24 -0
- data/lib/buildkite/builder/file_resolver.rb +59 -0
- data/lib/buildkite/builder/github.rb +71 -0
- data/lib/buildkite/builder/loaders.rb +12 -0
- data/lib/buildkite/builder/loaders/abstract.rb +40 -0
- data/lib/buildkite/builder/loaders/manifests.rb +23 -0
- data/lib/buildkite/builder/loaders/processors.rb +37 -0
- data/lib/buildkite/builder/loaders/templates.rb +25 -0
- data/lib/buildkite/builder/logging_utils.rb +24 -0
- data/lib/buildkite/builder/manifest.rb +89 -0
- data/lib/buildkite/builder/manifest/rule.rb +51 -0
- data/lib/buildkite/builder/processors.rb +9 -0
- data/lib/buildkite/builder/processors/abstract.rb +76 -0
- data/lib/buildkite/builder/rainbow.rb +9 -0
- data/lib/buildkite/builder/runner.rb +114 -0
- data/lib/buildkite/env.rb +52 -0
- data/lib/buildkite/pipelines.rb +13 -0
- data/lib/buildkite/pipelines/api.rb +119 -0
- data/lib/buildkite/pipelines/attributes.rb +137 -0
- data/lib/buildkite/pipelines/command.rb +59 -0
- data/lib/buildkite/pipelines/helpers.rb +43 -0
- data/lib/buildkite/pipelines/helpers/block.rb +18 -0
- data/lib/buildkite/pipelines/helpers/command.rb +21 -0
- data/lib/buildkite/pipelines/helpers/depends_on.rb +13 -0
- data/lib/buildkite/pipelines/helpers/key.rb +13 -0
- data/lib/buildkite/pipelines/helpers/label.rb +18 -0
- data/lib/buildkite/pipelines/helpers/plugins.rb +24 -0
- data/lib/buildkite/pipelines/helpers/retry.rb +20 -0
- data/lib/buildkite/pipelines/helpers/skip.rb +15 -0
- data/lib/buildkite/pipelines/helpers/soft_fail.rb +15 -0
- data/lib/buildkite/pipelines/helpers/timeout_in_minutes.rb +17 -0
- data/lib/buildkite/pipelines/pipeline.rb +129 -0
- data/lib/buildkite/pipelines/plugin.rb +23 -0
- data/lib/buildkite/pipelines/steps.rb +15 -0
- data/lib/buildkite/pipelines/steps/abstract.rb +26 -0
- data/lib/buildkite/pipelines/steps/block.rb +20 -0
- data/lib/buildkite/pipelines/steps/command.rb +30 -0
- data/lib/buildkite/pipelines/steps/input.rb +20 -0
- data/lib/buildkite/pipelines/steps/skip.rb +28 -0
- data/lib/buildkite/pipelines/steps/trigger.rb +22 -0
- data/lib/buildkite/pipelines/steps/wait.rb +18 -0
- data/lib/vendor/rainbow/Changelog.md +101 -0
- data/lib/vendor/rainbow/Gemfile +30 -0
- data/lib/vendor/rainbow/LICENSE +20 -0
- data/lib/vendor/rainbow/README.markdown +225 -0
- data/lib/vendor/rainbow/Rakefile +11 -0
- data/lib/vendor/rainbow/lib/rainbow.rb +13 -0
- data/lib/vendor/rainbow/lib/rainbow/color.rb +150 -0
- data/lib/vendor/rainbow/lib/rainbow/ext/string.rb +64 -0
- data/lib/vendor/rainbow/lib/rainbow/global.rb +25 -0
- data/lib/vendor/rainbow/lib/rainbow/null_presenter.rb +100 -0
- data/lib/vendor/rainbow/lib/rainbow/presenter.rb +144 -0
- data/lib/vendor/rainbow/lib/rainbow/refinement.rb +14 -0
- data/lib/vendor/rainbow/lib/rainbow/string_utils.rb +22 -0
- data/lib/vendor/rainbow/lib/rainbow/version.rb +5 -0
- data/lib/vendor/rainbow/lib/rainbow/wrapper.rb +22 -0
- data/lib/vendor/rainbow/lib/rainbow/x11_color_names.rb +153 -0
- data/lib/vendor/rainbow/rainbow.gemspec +23 -0
- metadata +126 -0
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Buildkite
|
4
|
+
module Builder
|
5
|
+
module Commands
|
6
|
+
class Files < Abstract
|
7
|
+
private
|
8
|
+
|
9
|
+
self.description = 'Outputs files that match the specified manifest.'
|
10
|
+
|
11
|
+
def run
|
12
|
+
pipeline, manifest = ARGV.first.to_s.split('/')
|
13
|
+
if !pipeline || !manifest
|
14
|
+
raise 'You must specify a pipeline and a manifest (eg "mypipeline/mymanifest")'
|
15
|
+
end
|
16
|
+
|
17
|
+
manifests = Loaders::Manifests.load(pipeline)
|
18
|
+
manifests[manifest].files.each do |file|
|
19
|
+
puts file
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Buildkite
|
4
|
+
module Builder
|
5
|
+
module Commands
|
6
|
+
class Preview < Abstract
|
7
|
+
private
|
8
|
+
|
9
|
+
self.description = 'Outputs the pipeline YAML.'
|
10
|
+
|
11
|
+
def run
|
12
|
+
unless pipeline
|
13
|
+
raise 'You must specify a pipeline'
|
14
|
+
end
|
15
|
+
|
16
|
+
puts Runner.new(pipeline: pipeline).run.to_yaml
|
17
|
+
end
|
18
|
+
|
19
|
+
def pipeline
|
20
|
+
@pipeline ||= ARGV.last || begin
|
21
|
+
if available_pipelines.one?
|
22
|
+
available_pipelines.first
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Buildkite
|
4
|
+
module Builder
|
5
|
+
module Definition
|
6
|
+
module Helper
|
7
|
+
def load_definition(file, expected)
|
8
|
+
result = eval(file.read, TOPLEVEL_BINDING.dup, file.to_s) # rubocop:disable Security/Eval
|
9
|
+
unless result.is_a?(expected)
|
10
|
+
raise "#{file} must return a valid definition (#{expected}); got #{result.class}"
|
11
|
+
end
|
12
|
+
|
13
|
+
result
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
class Pipeline < Proc
|
18
|
+
end
|
19
|
+
|
20
|
+
class Template < Proc
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'open3'
|
4
|
+
require 'set'
|
5
|
+
|
6
|
+
module Buildkite
|
7
|
+
module Builder
|
8
|
+
class FileResolver
|
9
|
+
@cache = true
|
10
|
+
|
11
|
+
attr_reader :modified_files
|
12
|
+
|
13
|
+
class << self
|
14
|
+
attr_accessor :cache
|
15
|
+
|
16
|
+
def resolve(reset = false)
|
17
|
+
@resolve = nil if !cache || reset
|
18
|
+
@resolve ||= new
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def initialize
|
23
|
+
@modified_files = SortedSet.new(pull_request? ? files_from_pull_request : files_from_git)
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def files_from_pull_request
|
29
|
+
Github.pull_request_files.map { |f| f.fetch('filename') }
|
30
|
+
end
|
31
|
+
|
32
|
+
def files_from_git
|
33
|
+
if Buildkite.env
|
34
|
+
changed_files = command("git diff-tree --no-commit-id --name-only -r #{Buildkite.env.commit}")
|
35
|
+
else
|
36
|
+
default_branch = command('git symbolic-ref refs/remotes/origin/HEAD').strip
|
37
|
+
changed_files = command("git diff --name-only #{default_branch}")
|
38
|
+
changed_files << command('git diff --name-only')
|
39
|
+
end
|
40
|
+
|
41
|
+
changed_files.split.uniq.sort
|
42
|
+
end
|
43
|
+
|
44
|
+
def pull_request?
|
45
|
+
Buildkite.env&.pull_request
|
46
|
+
end
|
47
|
+
|
48
|
+
def command(cmd)
|
49
|
+
output, status = Open3.capture2(*cmd.split)
|
50
|
+
|
51
|
+
if status.success?
|
52
|
+
output
|
53
|
+
else
|
54
|
+
raise "Command failed (exit #{status.exitstatus}): #{cmd}"
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
require 'net/http'
|
5
|
+
require 'uri'
|
6
|
+
|
7
|
+
module Buildkite
|
8
|
+
module Builder
|
9
|
+
class Github
|
10
|
+
BASE_URI = URI('https://api.github.com').freeze
|
11
|
+
ACCEPT_HEADER = 'application/vnd.github.v3+json'
|
12
|
+
LINK_HEADER = 'link'
|
13
|
+
NEXT_LINK_REGEX = /<(?<uri>.+)>; rel="next"/.freeze
|
14
|
+
REPO_REGEX = /github\.com(?::|\/)(.*)\.git\z/.freeze
|
15
|
+
PER_PAGE = 100
|
16
|
+
|
17
|
+
def self.pull_request_files
|
18
|
+
new.pull_request_files
|
19
|
+
end
|
20
|
+
|
21
|
+
def initialize(env = ENV)
|
22
|
+
@env = env
|
23
|
+
end
|
24
|
+
|
25
|
+
def pull_request_files
|
26
|
+
files = []
|
27
|
+
next_uri = URI.join(BASE_URI, "repos/#{repo}/pulls/#{pull_request_number}/files?per_page=#{PER_PAGE}")
|
28
|
+
|
29
|
+
while next_uri
|
30
|
+
response = request(next_uri)
|
31
|
+
files.concat(JSON.parse(response.body))
|
32
|
+
next_uri = parse_next_uri(response)
|
33
|
+
end
|
34
|
+
|
35
|
+
files
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def repo
|
41
|
+
Buildkite.env.repo[REPO_REGEX, 1]
|
42
|
+
end
|
43
|
+
|
44
|
+
def token
|
45
|
+
@env.fetch('GITHUB_API_TOKEN')
|
46
|
+
end
|
47
|
+
|
48
|
+
def pull_request_number
|
49
|
+
Buildkite.env.pull_request
|
50
|
+
end
|
51
|
+
|
52
|
+
def request(uri)
|
53
|
+
Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
|
54
|
+
request = Net::HTTP::Get.new(uri)
|
55
|
+
request['Authorization'] = "token #{token}"
|
56
|
+
request['Accept'] = ACCEPT_HEADER
|
57
|
+
|
58
|
+
http.request(request)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def parse_next_uri(response)
|
63
|
+
links = response[LINK_HEADER]
|
64
|
+
return unless links
|
65
|
+
|
66
|
+
matches = links.match(NEXT_LINK_REGEX)
|
67
|
+
URI.parse(matches[:uri]) if matches
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Buildkite
|
4
|
+
module Builder
|
5
|
+
module Loaders
|
6
|
+
autoload :Abstract, File.expand_path('loaders/abstract', __dir__)
|
7
|
+
autoload :Manifests, File.expand_path('loaders/manifests', __dir__)
|
8
|
+
autoload :Templates, File.expand_path('loaders/templates', __dir__)
|
9
|
+
autoload :Processors, File.expand_path('loaders/processors', __dir__)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Buildkite
|
4
|
+
module Builder
|
5
|
+
module Loaders
|
6
|
+
class Abstract
|
7
|
+
attr_reader :assets
|
8
|
+
attr_reader :pipeline
|
9
|
+
|
10
|
+
def self.load(pipeline)
|
11
|
+
new(pipeline).assets
|
12
|
+
end
|
13
|
+
|
14
|
+
def initialize(pipeline)
|
15
|
+
@pipeline = pipeline
|
16
|
+
@assets = {}
|
17
|
+
load
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def buildkite_path
|
23
|
+
Buildkite::Builder.root.join('.buildkite')
|
24
|
+
end
|
25
|
+
|
26
|
+
def pipeline_path
|
27
|
+
buildkite_path.join("pipelines/#{pipeline}")
|
28
|
+
end
|
29
|
+
|
30
|
+
def load
|
31
|
+
raise NotImplementedError
|
32
|
+
end
|
33
|
+
|
34
|
+
def add(name, asset)
|
35
|
+
@assets[name.to_s] = asset
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Buildkite
|
4
|
+
module Builder
|
5
|
+
module Loaders
|
6
|
+
class Manifests < Abstract
|
7
|
+
MANIFESTS_PATH = Pathname.new('manifests').freeze
|
8
|
+
|
9
|
+
def load
|
10
|
+
return unless manifests_path.directory?
|
11
|
+
|
12
|
+
manifests_path.children.map do |file|
|
13
|
+
add(file.basename, Manifest.new(Buildkite::Builder.root, file.readlines))
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def manifests_path
|
18
|
+
pipeline_path.join(MANIFESTS_PATH)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'pathname'
|
4
|
+
|
5
|
+
module Buildkite
|
6
|
+
module Builder
|
7
|
+
module Loaders
|
8
|
+
class Processors < Abstract
|
9
|
+
PROCESSORS_PATH = Pathname.new('processors').freeze
|
10
|
+
|
11
|
+
def load
|
12
|
+
load_processors_from_path(global_processors_path)
|
13
|
+
load_processors_from_path(pipeline_processors_path)
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def load_processors_from_path(path)
|
19
|
+
return unless path.directory?
|
20
|
+
|
21
|
+
path.children.map do |file|
|
22
|
+
required_status = require(file)
|
23
|
+
add(file.basename, { required: required_status })
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def global_processors_path
|
28
|
+
buildkite_path.join(PROCESSORS_PATH)
|
29
|
+
end
|
30
|
+
|
31
|
+
def pipeline_processors_path
|
32
|
+
pipeline_path.join(PROCESSORS_PATH)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Buildkite
|
4
|
+
module Builder
|
5
|
+
module Loaders
|
6
|
+
class Templates < Abstract
|
7
|
+
include Definition::Helper
|
8
|
+
|
9
|
+
TEMPLATES_PATH = Pathname.new('templates').freeze
|
10
|
+
|
11
|
+
def load
|
12
|
+
return unless templates_path.directory?
|
13
|
+
|
14
|
+
templates_path.children.sort.each do |file|
|
15
|
+
add(file.basename('.rb'), load_definition(file, Definition::Template))
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def templates_path
|
20
|
+
pipeline_path.join(TEMPLATES_PATH)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'benchmark'
|
4
|
+
|
5
|
+
module Buildkite
|
6
|
+
module Builder
|
7
|
+
module LoggingUtils
|
8
|
+
def benchmark(output, &block)
|
9
|
+
time = Benchmark.realtime(&block)
|
10
|
+
output % [pluralize(time.round(2), 'second')]
|
11
|
+
end
|
12
|
+
|
13
|
+
def pluralize(count, singular, plural = nil)
|
14
|
+
if count == 1
|
15
|
+
"#{count} #{singular}"
|
16
|
+
elsif plural
|
17
|
+
"#{count} #{plural}"
|
18
|
+
else
|
19
|
+
"#{count} #{singular}s"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'digest/md5'
|
4
|
+
require 'pathname'
|
5
|
+
require 'set'
|
6
|
+
|
7
|
+
module Buildkite
|
8
|
+
module Builder
|
9
|
+
class Manifest
|
10
|
+
autoload :Rule, File.expand_path('manifest/rule', __dir__)
|
11
|
+
|
12
|
+
class << self
|
13
|
+
def resolve(root, patterns)
|
14
|
+
new(root, Array(patterns)).modified?
|
15
|
+
end
|
16
|
+
|
17
|
+
def manifests
|
18
|
+
@manifests ||= {}
|
19
|
+
end
|
20
|
+
|
21
|
+
def [](name)
|
22
|
+
manifests[name.to_s]
|
23
|
+
end
|
24
|
+
|
25
|
+
def []=(name, manifest)
|
26
|
+
name = name.to_s
|
27
|
+
if manifests.key?(name)
|
28
|
+
raise ArgumentError, "manifest #{name} already exists"
|
29
|
+
end
|
30
|
+
|
31
|
+
manifests[name] = manifest
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
attr_reader :root
|
36
|
+
|
37
|
+
def initialize(root, patterns)
|
38
|
+
@root = Pathname.new(root)
|
39
|
+
@root = Buildkite::Builder.root.join(@root) unless @root.absolute?
|
40
|
+
@patterns = patterns.map(&:to_s)
|
41
|
+
end
|
42
|
+
|
43
|
+
def modified?
|
44
|
+
# DO NOT intersect FileResolver with manifest files. If the manifest is
|
45
|
+
# large, the operation can be expensive. It's always cheaper to loop
|
46
|
+
# through the changed files and compare them against the rules.
|
47
|
+
unless defined?(@modified)
|
48
|
+
@modified = FileResolver.resolve.modified_files.any? do |file|
|
49
|
+
file = Buildkite::Builder.root.join(file)
|
50
|
+
inclusion_rules.any? { |rule| rule.match?(file) } &&
|
51
|
+
exclusion_rules.none? { |rule| rule.match?(file) }
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
@modified
|
56
|
+
end
|
57
|
+
|
58
|
+
def files
|
59
|
+
@files ||= inclusion_rules.map(&:files).reduce(SortedSet.new, :merge) - exclusion_rules.map(&:files).reduce(SortedSet.new, :merge)
|
60
|
+
end
|
61
|
+
|
62
|
+
def digest
|
63
|
+
@digest ||= begin
|
64
|
+
digests = files.map { |file| Digest::MD5.file(Buildkite::Builder.root.join(file)).hexdigest }
|
65
|
+
Digest::MD5.hexdigest(digests.join)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
private
|
70
|
+
|
71
|
+
def rules
|
72
|
+
@rules ||= @patterns.each_with_object([]) do |pattern, rules|
|
73
|
+
pattern = pattern.strip
|
74
|
+
unless pattern.match?(/\A(#|\z)/)
|
75
|
+
rules << Rule.new(root, pattern)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def inclusion_rules
|
81
|
+
@inclusion_rules ||= rules.reject(&:exclude)
|
82
|
+
end
|
83
|
+
|
84
|
+
def exclusion_rules
|
85
|
+
@exclusion_rules ||= rules.select(&:exclude)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|