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,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'set'
|
4
|
+
require 'pathname'
|
5
|
+
|
6
|
+
module Buildkite
|
7
|
+
module Builder
|
8
|
+
class Manifest::Rule
|
9
|
+
GLOB_OPTIONS = File::FNM_PATHNAME | File::FNM_DOTMATCH | File::FNM_EXTGLOB
|
10
|
+
|
11
|
+
attr_reader :exclude
|
12
|
+
attr_reader :glob
|
13
|
+
|
14
|
+
def initialize(root, pattern)
|
15
|
+
@root = Pathname.new(root)
|
16
|
+
@exclude = false
|
17
|
+
@glob = @root
|
18
|
+
|
19
|
+
if pattern[0] == '!'
|
20
|
+
@exclude = true
|
21
|
+
pattern = pattern[1..-1]
|
22
|
+
end
|
23
|
+
|
24
|
+
if pattern.start_with?('/')
|
25
|
+
pattern = pattern[1..-1]
|
26
|
+
else
|
27
|
+
@glob = @glob.join('**')
|
28
|
+
end
|
29
|
+
|
30
|
+
@glob = @glob.join(pattern).to_s
|
31
|
+
end
|
32
|
+
|
33
|
+
def files
|
34
|
+
@files ||= begin
|
35
|
+
matched = Dir.glob(glob, GLOB_OPTIONS)
|
36
|
+
matched.map! { |file| Pathname.new(file) }
|
37
|
+
matched.reject!(&:directory?)
|
38
|
+
matched.map! { |file| file.relative_path_from(Builder.root) }
|
39
|
+
SortedSet.new(matched)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def match?(file)
|
44
|
+
file = Pathname.new(file)
|
45
|
+
file = @root.join(file) unless file.absolute?
|
46
|
+
|
47
|
+
File.fnmatch?(glob, file.to_s, GLOB_OPTIONS)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Buildkite
|
4
|
+
module Builder
|
5
|
+
module Processors
|
6
|
+
class Abstract
|
7
|
+
include LoggingUtils
|
8
|
+
using Rainbow
|
9
|
+
|
10
|
+
def self.process(runner)
|
11
|
+
new(runner).run
|
12
|
+
end
|
13
|
+
|
14
|
+
def initialize(runner)
|
15
|
+
@runner = runner
|
16
|
+
end
|
17
|
+
|
18
|
+
def run
|
19
|
+
_log_run { process }
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
attr_reader :runner
|
25
|
+
|
26
|
+
def process
|
27
|
+
raise NotImplementedError
|
28
|
+
end
|
29
|
+
|
30
|
+
def log
|
31
|
+
runner.log
|
32
|
+
end
|
33
|
+
|
34
|
+
def pipeline
|
35
|
+
runner.pipeline
|
36
|
+
end
|
37
|
+
|
38
|
+
def buildkite
|
39
|
+
@buildkite ||= begin
|
40
|
+
unless Buildkite.env
|
41
|
+
raise 'Must be in Buildkite environment to access the Buildkite API'
|
42
|
+
end
|
43
|
+
|
44
|
+
Buildkite::Pipelines::Api.new(Buildkite.env.api_token)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def pipeline_steps(*types)
|
49
|
+
steps = pipeline.steps
|
50
|
+
types = types.flatten
|
51
|
+
steps = steps.select { |step| types.include?(step.class.to_sym) } if types.any?
|
52
|
+
steps
|
53
|
+
end
|
54
|
+
|
55
|
+
def _log_run
|
56
|
+
log.info "\nProcessing ".color(:dimgray) + self.class.name.color(:springgreen)
|
57
|
+
|
58
|
+
results = benchmark('└──'.color(:springgreen) + ' Finished in %s'.color(:dimgray)) do
|
59
|
+
formatter = log.formatter
|
60
|
+
log.formatter = proc do |_severity, _datetime, _progname, msg|
|
61
|
+
'│'.color(:springgreen) + " #{msg}\n"
|
62
|
+
end
|
63
|
+
|
64
|
+
begin
|
65
|
+
yield
|
66
|
+
ensure
|
67
|
+
log.formatter = formatter
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
log.info results
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,114 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bundler'
|
4
|
+
require 'pathname'
|
5
|
+
require 'tempfile'
|
6
|
+
|
7
|
+
module Buildkite
|
8
|
+
module Builder
|
9
|
+
class Runner
|
10
|
+
include Definition::Helper
|
11
|
+
include LoggingUtils
|
12
|
+
using Rainbow
|
13
|
+
|
14
|
+
PIPELINES_PATH = Pathname.new('.buildkite/pipelines').freeze
|
15
|
+
PIPELINE_DEFINITION_PATH = Pathname.new('pipeline.rb').freeze
|
16
|
+
|
17
|
+
attr_reader :options
|
18
|
+
|
19
|
+
# This entrypoint is for running on CI. It expects certain environment variables to
|
20
|
+
# be set.
|
21
|
+
def self.run
|
22
|
+
new(
|
23
|
+
upload: true,
|
24
|
+
pipeline: Buildkite.env.pipeline_slug
|
25
|
+
).run
|
26
|
+
end
|
27
|
+
|
28
|
+
def initialize(**options)
|
29
|
+
@options = {
|
30
|
+
verbose: true,
|
31
|
+
}.merge(options)
|
32
|
+
end
|
33
|
+
|
34
|
+
def run
|
35
|
+
log.info "#{'+++ ' if Buildkite.env}🧰 " + 'Buildkite-builder'.color(:springgreen) + " ─ #{@options[:pipeline].yellow}"
|
36
|
+
|
37
|
+
results = benchmark("\nDone (%s)".color(:springgreen)) do
|
38
|
+
load_manifests
|
39
|
+
load_templates
|
40
|
+
load_processors
|
41
|
+
load_pipeline
|
42
|
+
run_processors
|
43
|
+
end
|
44
|
+
log.info results
|
45
|
+
|
46
|
+
upload! if options[:upload]
|
47
|
+
# Always return the pipeline.
|
48
|
+
|
49
|
+
pipeline
|
50
|
+
end
|
51
|
+
|
52
|
+
def pipeline
|
53
|
+
@pipeline ||= Buildkite::Pipelines::Pipeline.new
|
54
|
+
end
|
55
|
+
|
56
|
+
def pipeline_definition
|
57
|
+
@pipeline_definition ||= begin
|
58
|
+
expected = Definition::Pipeline
|
59
|
+
load_definition(Buildkite::Builder.root.join(".buildkite/pipelines/#{options[:pipeline]}").join(PIPELINE_DEFINITION_PATH), expected)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def log
|
64
|
+
@log ||= begin
|
65
|
+
Logger.new(options[:verbose] ? $stdout : StringIO.new).tap do |lgr|
|
66
|
+
lgr.formatter = proc do |_severity, _datetime, _progname, msg|
|
67
|
+
"#{msg}\n"
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
private
|
74
|
+
|
75
|
+
def upload!
|
76
|
+
Tempfile.create(['pipeline', '.yml']) do |file|
|
77
|
+
file.sync = true
|
78
|
+
file.write(pipeline.to_yaml)
|
79
|
+
|
80
|
+
log.info '+++ :paperclip: Uploading artifact'
|
81
|
+
Buildkite::Pipelines::Command.artifact!(:upload, file.path)
|
82
|
+
log.info '+++ :pipeline: Uploading pipeline'
|
83
|
+
Buildkite::Pipelines::Command.pipeline!(:upload, file.path)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def load_manifests
|
88
|
+
Loaders::Manifests.load(options[:pipeline]).each do |name, asset|
|
89
|
+
Manifest[name] = asset
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def load_templates
|
94
|
+
Loaders::Templates.load(options[:pipeline]).each do |name, asset|
|
95
|
+
pipeline.template(name, &asset)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def load_processors
|
100
|
+
Loaders::Processors.load(options[:pipeline])
|
101
|
+
end
|
102
|
+
|
103
|
+
def run_processors
|
104
|
+
pipeline.processors.each do |processor|
|
105
|
+
processor.process(self)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def load_pipeline
|
110
|
+
pipeline.instance_eval(&pipeline_definition)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Buildkite
|
4
|
+
class Env
|
5
|
+
BUILDKITE = 'BUILDKITE'
|
6
|
+
PREFIX = "#{BUILDKITE}_"
|
7
|
+
|
8
|
+
module Fallback
|
9
|
+
def method_missing(method_name, *_args, &_block) # rubocop:disable Style/MethodMissingSuper
|
10
|
+
env_name = "#{PREFIX}#{method_name.to_s.gsub(/\?\z/, '').upcase}"
|
11
|
+
|
12
|
+
if method_name.to_s.end_with?('?')
|
13
|
+
@env.key?(env_name)
|
14
|
+
elsif @env.key?(env_name)
|
15
|
+
@env.fetch(env_name)
|
16
|
+
else
|
17
|
+
raise NoMethodError, "undefined method #{method_name} for #{self} because ENV[\"#{env_name}\"] is not defined"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def respond_to_missing?(method_name, include_private = false)
|
22
|
+
method_name.to_s.end_with?('?') || @env.key?("#{PREFIX}#{method_name.upcase}") || super
|
23
|
+
end
|
24
|
+
end
|
25
|
+
include Fallback
|
26
|
+
|
27
|
+
def self.load(env)
|
28
|
+
new(env) if env[BUILDKITE]
|
29
|
+
end
|
30
|
+
|
31
|
+
def initialize(env)
|
32
|
+
@env = env
|
33
|
+
end
|
34
|
+
|
35
|
+
def default_branch?
|
36
|
+
pipeline_default_branch == branch
|
37
|
+
end
|
38
|
+
|
39
|
+
def pull_request
|
40
|
+
super == 'false' ? false : Integer(super)
|
41
|
+
end
|
42
|
+
|
43
|
+
# Integer methods
|
44
|
+
%w(
|
45
|
+
build_number
|
46
|
+
).each do |meth|
|
47
|
+
define_method(meth) do
|
48
|
+
Integer(super())
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Buildkite
|
4
|
+
module Pipelines
|
5
|
+
autoload :Api, File.expand_path('pipelines/api', __dir__)
|
6
|
+
autoload :Attributes, File.expand_path('pipelines/attributes', __dir__)
|
7
|
+
autoload :Command, File.expand_path('pipelines/command', __dir__)
|
8
|
+
autoload :Helpers, File.expand_path('pipelines/helpers', __dir__)
|
9
|
+
autoload :Pipeline, File.expand_path('pipelines/pipeline', __dir__)
|
10
|
+
autoload :Plugin, File.expand_path('pipelines/plugin', __dir__)
|
11
|
+
autoload :Steps, File.expand_path('pipelines/steps', __dir__)
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,119 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'uri'
|
4
|
+
require 'net/http'
|
5
|
+
require 'json'
|
6
|
+
|
7
|
+
module Buildkite
|
8
|
+
module Pipelines
|
9
|
+
class Api
|
10
|
+
BASE_URI = URI('https://api.buildkite.com/v2').freeze
|
11
|
+
URI_PARTS = {
|
12
|
+
organization: 'organizations',
|
13
|
+
pipeline: 'pipelines',
|
14
|
+
pipelines: 'pipelines',
|
15
|
+
build: 'builds',
|
16
|
+
builds: 'builds',
|
17
|
+
access_token: 'access-token',
|
18
|
+
}.freeze
|
19
|
+
|
20
|
+
def initialize(token)
|
21
|
+
@token = token
|
22
|
+
end
|
23
|
+
|
24
|
+
def get_access_token
|
25
|
+
uri = uri_for(access_token: nil)
|
26
|
+
JSON.parse(get_request(uri).body)
|
27
|
+
end
|
28
|
+
|
29
|
+
def list_pipelines(organization)
|
30
|
+
raise NotImplementedError
|
31
|
+
end
|
32
|
+
|
33
|
+
def get_pipeline(organization, pipeline)
|
34
|
+
response = get_request(
|
35
|
+
uri_for(
|
36
|
+
organization: organization,
|
37
|
+
pipeline: pipeline
|
38
|
+
)
|
39
|
+
)
|
40
|
+
if response.is_a?(Net::HTTPSuccess)
|
41
|
+
JSON.parse(response.body)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def create_pipeline(organization, params)
|
46
|
+
uri = uri_for(
|
47
|
+
organization: organization,
|
48
|
+
pipelines: nil
|
49
|
+
)
|
50
|
+
JSON.parse(post_request(uri, params).body)
|
51
|
+
end
|
52
|
+
|
53
|
+
def get_pipeline_builds(organization, pipeline, **params)
|
54
|
+
uri = uri_for(params.merge(
|
55
|
+
organization: organization,
|
56
|
+
pipeline: pipeline,
|
57
|
+
builds: nil
|
58
|
+
))
|
59
|
+
JSON.parse(get_request(uri).body)
|
60
|
+
end
|
61
|
+
|
62
|
+
def get_build(organization, pipeline, build)
|
63
|
+
uri = uri_for(
|
64
|
+
organization: organization,
|
65
|
+
pipeline: pipeline,
|
66
|
+
build: build
|
67
|
+
)
|
68
|
+
JSON.parse(get_request(uri).body)
|
69
|
+
end
|
70
|
+
|
71
|
+
def create_build(organization, pipeline)
|
72
|
+
raise NotImplementedError
|
73
|
+
end
|
74
|
+
|
75
|
+
def cancel_build(organization, pipeline, build)
|
76
|
+
raise NotImplementedError
|
77
|
+
end
|
78
|
+
|
79
|
+
def rebuild_build(organization, pipeline, build)
|
80
|
+
raise NotImplementedError
|
81
|
+
end
|
82
|
+
|
83
|
+
private
|
84
|
+
|
85
|
+
def get_request(uri)
|
86
|
+
Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
|
87
|
+
request = prepare_request(Net::HTTP::Get.new(uri))
|
88
|
+
http.request(request)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def post_request(uri, data)
|
93
|
+
Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
|
94
|
+
request = prepare_request(Net::HTTP::Post.new(uri))
|
95
|
+
request.content_type = 'application/json'
|
96
|
+
request.body = data.to_json
|
97
|
+
http.request(request)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def uri_for(options)
|
102
|
+
uri_parts = URI_PARTS.each_with_object([]) do |(resource, path), parts|
|
103
|
+
if options.key?(resource)
|
104
|
+
parts << [path, options.delete(resource)].compact
|
105
|
+
end
|
106
|
+
end
|
107
|
+
uri = URI(uri_parts.flatten.unshift(BASE_URI).join('/'))
|
108
|
+
uri.query = URI.encode_www_form(options) if options.any?
|
109
|
+
uri
|
110
|
+
end
|
111
|
+
|
112
|
+
def prepare_request(request)
|
113
|
+
request['Authorization'] = "Bearer #{@token}"
|
114
|
+
request['Accept'] = 'application/json'
|
115
|
+
request
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|