buildkite-builder 1.0.0.beta.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|