buildkite-builder 1.0.0.beta.4 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +4 -0
  3. data/README.md +118 -14
  4. data/VERSION +1 -0
  5. data/lib/buildkite/builder.rb +12 -18
  6. data/lib/buildkite/builder/commands/abstract.rb +49 -4
  7. data/lib/buildkite/builder/commands/preview.rb +1 -13
  8. data/lib/buildkite/builder/commands/run.rb +21 -1
  9. data/lib/buildkite/builder/context.rb +108 -0
  10. data/lib/buildkite/builder/loaders/abstract.rb +6 -10
  11. data/lib/buildkite/builder/loaders/manifests.rb +1 -1
  12. data/lib/buildkite/builder/loaders/processors.rb +2 -2
  13. data/lib/buildkite/builder/loaders/templates.rb +1 -1
  14. data/lib/buildkite/builder/manifest.rb +1 -1
  15. data/lib/buildkite/builder/processors/abstract.rb +7 -7
  16. data/lib/buildkite/builder/rainbow.rb +1 -1
  17. data/lib/buildkite/pipelines.rb +1 -0
  18. data/lib/buildkite/pipelines/pipeline.rb +20 -9
  19. data/lib/buildkite/pipelines/step_context.rb +25 -0
  20. data/lib/buildkite/pipelines/steps/abstract.rb +4 -3
  21. data/lib/buildkite/pipelines/steps/block.rb +1 -0
  22. metadata +110 -27
  23. data/lib/buildkite/builder/runner.rb +0 -114
  24. data/lib/vendor/rainbow/Changelog.md +0 -101
  25. data/lib/vendor/rainbow/Gemfile +0 -30
  26. data/lib/vendor/rainbow/LICENSE +0 -20
  27. data/lib/vendor/rainbow/README.markdown +0 -225
  28. data/lib/vendor/rainbow/Rakefile +0 -11
  29. data/lib/vendor/rainbow/lib/rainbow.rb +0 -13
  30. data/lib/vendor/rainbow/lib/rainbow/color.rb +0 -150
  31. data/lib/vendor/rainbow/lib/rainbow/ext/string.rb +0 -64
  32. data/lib/vendor/rainbow/lib/rainbow/global.rb +0 -25
  33. data/lib/vendor/rainbow/lib/rainbow/null_presenter.rb +0 -100
  34. data/lib/vendor/rainbow/lib/rainbow/presenter.rb +0 -144
  35. data/lib/vendor/rainbow/lib/rainbow/refinement.rb +0 -14
  36. data/lib/vendor/rainbow/lib/rainbow/string_utils.rb +0 -22
  37. data/lib/vendor/rainbow/lib/rainbow/version.rb +0 -5
  38. data/lib/vendor/rainbow/lib/rainbow/wrapper.rb +0 -22
  39. data/lib/vendor/rainbow/lib/rainbow/x11_color_names.rb +0 -153
  40. data/lib/vendor/rainbow/rainbow.gemspec +0 -23
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f1c9b9e735b6130fa12f1424bc3a6fecdb280edd9b600e003acb406c3462c114
4
- data.tar.gz: 82ab940a585b5021d0337dba8fda4a9a8c2d5b69214aae65271799c53918f406
3
+ metadata.gz: 6684f24ab3c156884bcd3055fbddbb7831afa03be9e0b0b2056fbedef023084b
4
+ data.tar.gz: 64753e7a15224a0b3c161335cfbc9046ecbaa5e015692ace78001eeef550eea3
5
5
  SHA512:
6
- metadata.gz: 39db63fa323f822549a64045d6100fb646956ffc077c16752af9b24f9e8467cf5d73538a074d3d91e94b526de8a6ad37da08125475b4fad9d80d6c61823711ed
7
- data.tar.gz: 700414499cec1e93162eddbe075015316e253ab9e2d73d328d8cc7a1a09bf5685f18d6dcdc5ef516651a9596302f8483a3dc73fa78419b601cb51422f30c01f8
6
+ metadata.gz: 21b7f849f79acd215f260644f75767143bb10f2eeab88e0edee7c3728024f128b12f421ddd632ed4163fc0501f44051bc065594906d28643642839be934ac152
7
+ data.tar.gz: 5bfe120d20cb914525c61dba9ddec9b298058e6ccb01a5b6a4a55cccc95cff1fa993753cf65ba1041615d9adcb735ffb4545c7a58987a004cc3638b11d4eeba3
data/CHANGELOG.md CHANGED
@@ -0,0 +1,4 @@
1
+ ## 1.3.0
2
+ * Add ability for step to store data in step context
3
+ * Move `upload` from `BuildKite::Builder::Commands::Run` to `BuildKite::Builder::Context`
4
+ * Add ability to set custom artifacts in context and uplaod before `pipeile upload`
data/README.md CHANGED
@@ -1,39 +1,143 @@
1
- # Buildkite::Builder
1
+ # Buildkite Builder [![Build status](https://badge.buildkite.com/a26bf804e9a93fb118d29824d5695a601a248ceec51591be23.svg?branch=main)](https://buildkite.com/gusto-open-source/buildkite-builder/builds?branch=main)
2
2
 
3
- Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/buildkite/builder`. To experiment with that code, run `bin/console` for an interactive prompt.
3
+ ## Introduction
4
+ Buildkite Builder (BKB) is a Buildkite pipeline builder written in Ruby. It allows you to build your pipeline with a Ruby DSL for dynamically generated pipeline steps.
4
5
 
5
- TODO: Delete this and the text above, and describe your gem
6
+ ## Gem Installation (optional)
6
7
 
7
- ## Installation
8
+ There are 2 components to this toolkit. The `buildkite-builder` Rubygem and the `buildkite-builder` Docker image. You technically only need the image to use Buildkite Builder, but installing the gem in your repo helps you preview your pipeline during development.
8
9
 
9
- Add this line to your application's Gemfile:
10
+ To install the gem, add this line to your application's Gemfile:
10
11
 
11
12
  ```ruby
12
13
  gem 'buildkite-builder'
13
14
  ```
14
15
 
15
- And then execute:
16
+ The gem provides a command line tool that lets you perform various operations on your pipelines:
16
17
 
17
- $ bundle install
18
+ ```shell
19
+ $ buildkite-builder help
20
+ ```
21
+
22
+ ## Pipeline Installation
18
23
 
19
- Or install it yourself as:
24
+ As with every Buildkite pipeline, you'll need to define the initial pipeline step. You can do this directly in the Pipeline Settings or with a `.buildkite/pipeline.yml` file in your repository. You'll need to define a single step to kick off Buildkite Builder:
25
+
26
+ ```yaml
27
+ steps:
28
+ - label: ":toolbox:"
29
+ plugins:
30
+ - docker#v3.7.0:
31
+ image: gusto/buildkite-builder:1.2.0
32
+ propagate-environment: true
33
+ ```
20
34
 
21
- $ gem install buildkite-builder
35
+ Some things to note:
36
+ - The `label` can be whatever you like.
37
+ - You'll want to update the `docker` plugin version from time to time.
38
+ - You can update the `buildkite-builder` version by bumping the Docker image tag.
22
39
 
23
40
  ## Usage
24
41
 
25
- TODO: Write usage instructions here
42
+ 💡 We have a [Showcase pipeline](https://buildkite.com/gusto-open-source/showcase/builds/latest?branch=main) (defined in [`.buildkite/pipelines/showcase/pipeline.rb`](https://github.com/Gusto/buildkite-builder/blob/main/.buildkite/pipelines/showcase/pipeline.rb)) that, well, showcases some of the features and possibilities with Buildkite Builder. Sometimes the best way to learning something is seeing how it's used.
43
+
44
+ At its core, BKB is really just a YAML builder. This tool allows you to scale your needs when it comes to building a Buildkite pipeline. Your pipeline can be as straight forward as you'd like, or as complex as you need. Since you have Ruby at your disposal, you can do some cool things like:
45
+ - Perform pre-build code/diff analysis to determine whether or not to to add a step to the pipeline.
46
+ - Reorder pipeline steps dynamically.
47
+ - Augment your pipeline steps with BKB processors.
48
+
49
+ ### Pipeline Files
50
+ Your repo can contain as many pipeline definitions as you'd like. By convention, pipeline file structure are as such:
51
+
52
+ ```
53
+ .buildkite/
54
+ pipelines/
55
+ <your-pipeline1-slug>/
56
+ pipeline.rb
57
+ <your-pipeline2-slug>/
58
+ pipeline.rb
59
+ ```
60
+
61
+ For an example, refer to the [dummy pipeline in the fixtures directory](https://github.com/Gusto/buildkite-builder/blob/main/spec/fixtures/basic/.buildkite/pipelines/dummy/pipeline.rb).
62
+
63
+ ### Defining Steps
64
+
65
+ Buildkite Builder was designed to be as intuitive as possible by making DSL match Buildkite's attributes and [step types](https://buildkite.com/docs/pipelines/defining-steps#step-types). Here's a basic pipeline:
66
+
67
+ ```ruby
68
+ Buildkite::Builder.pipeline do
69
+ command do
70
+ label "Rspec", emoji: :rspec
71
+ command "bundle exec rspec"
72
+ end
73
+
74
+ wait
75
+
76
+ trigger do
77
+ trigger "deploy-pipeline"
78
+ end
79
+ end
80
+ ```
81
+
82
+ Which generates:
83
+
84
+ ```yaml
85
+ steps:
86
+ - label: ":rspec: RSpec"
87
+ command: "bundle exec rspec"
88
+ - wait
89
+ - trigger: deploy-pipeline
90
+ ```
91
+
92
+ If the step type or attribute exists in Buildkite docs, then it should exist in the DSL. **The only exception is the `if` attribute**. Since `if` is a ruby keyword, we've mapped it to `condition`.
93
+
94
+ ### Step Templates
95
+
96
+ If your pipeline has a lot of steps, you should consider using Step Templates. Templates allow you to break out your build steps into reusable template files.
97
+
98
+ ```
99
+ .buildkite/
100
+ pipelines/
101
+ foobar-widget/
102
+ pipeline.rb
103
+ templates/
104
+ rspec.rb
105
+ rubocop.rb
106
+ ```
107
+
108
+ A template is basically a step that was extracted from the pipeline:
109
+
110
+ `.buildkite/pipelines/foobar-widget/templates/rspec.rb`
111
+ ```ruby
112
+ Buildkite::Builder.template do
113
+ label "Rspec", emoji: :rspec
114
+ commmand "bundle exec rspec"
115
+ end
116
+ ```
117
+
118
+ You can then include the template into the the pipeline once or as many time as you need. The template name will be the name of the file (without the extension).
119
+
120
+ `.buildkite/pipelines/foobar-widget/pipeline.rb`
121
+ ```ruby
122
+ Buildkite::Builder.pipeline do
123
+ command(:rspec)
124
+
125
+ # Reuse and agument templates on the fly.
126
+ command(:rspec) do
127
+ label "Run RSpec again!"
128
+ end
129
+ end
130
+ ```
26
131
 
27
132
  ## Development
28
133
 
29
134
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
30
135
 
31
- To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
136
+ To install this gem onto your local machine, run `bundle exec rake install`.
32
137
 
33
138
  ## Contributing
34
139
 
35
- Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/buildkite-builder. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/[USERNAME]/buildkite-builder/blob/master/CODE_OF_CONDUCT.md).
36
-
140
+ Bug reports and pull requests are welcome on GitHub at https://github.com/gusto/buildkite-builder. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/gusto/buildkite-builder/blob/main/CODE_OF_CONDUCT.md).
37
141
 
38
142
  ## License
39
143
 
@@ -41,4 +145,4 @@ The gem is available as open source under the terms of the [MIT License](https:/
41
145
 
42
146
  ## Code of Conduct
43
147
 
44
- Everyone interacting in the Buildkite::Builder project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/[USERNAME]/buildkite-builder/blob/master/CODE_OF_CONDUCT.md).
148
+ Everyone interacting in the Buildkite::Builder project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/gusto/buildkite-builder/blob/main/CODE_OF_CONDUCT.md).
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 1.3.0
@@ -1,14 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'bundler'
4
- require 'logger'
5
3
  require 'pathname'
6
4
 
7
5
  module Buildkite
8
6
  module Builder
9
- VERSION = '1.0.0.beta.4'
10
-
11
7
  autoload :Commands, File.expand_path('builder/commands', __dir__)
8
+ autoload :Context, File.expand_path('builder/context', __dir__)
12
9
  autoload :Definition, File.expand_path('builder/definition', __dir__)
13
10
  autoload :FileResolver, File.expand_path('builder/file_resolver', __dir__)
14
11
  autoload :Github, File.expand_path('builder/github', __dir__)
@@ -17,9 +14,8 @@ module Buildkite
17
14
  autoload :Manifest, File.expand_path('builder/manifest', __dir__)
18
15
  autoload :Processors, File.expand_path('builder/processors', __dir__)
19
16
  autoload :Rainbow, File.expand_path('builder/rainbow', __dir__)
20
- autoload :Runner, File.expand_path('builder/runner', __dir__)
21
17
 
22
- BUILDKITE_DIRECTORY_NAME = '.buildkite/'
18
+ BUILDKITE_DIRECTORY_NAME = Pathname.new('.buildkite').freeze
23
19
 
24
20
  class << self
25
21
  def root(start_path: Dir.pwd, reset: false)
@@ -27,6 +23,16 @@ module Buildkite
27
23
  @root ||= find_buildkite_directory(start_path)
28
24
  end
29
25
 
26
+ def template(&block)
27
+ Definition::Template.new(&block) if block_given?
28
+ end
29
+
30
+ def pipeline(&block)
31
+ Definition::Pipeline.new(&block) if block_given?
32
+ end
33
+
34
+ private
35
+
30
36
  def find_buildkite_directory(start_path)
31
37
  path = Pathname.new(start_path)
32
38
  until path.join(BUILDKITE_DIRECTORY_NAME).exist? && path.join(BUILDKITE_DIRECTORY_NAME).directory?
@@ -37,18 +43,6 @@ module Buildkite
37
43
  path.expand_path
38
44
  end
39
45
 
40
- def expand_path(path)
41
- path = Pathname.new(path)
42
- path.absolute? ? path : root.join(path)
43
- end
44
-
45
- def template(&block)
46
- Definition::Template.new(&block) if block_given?
47
- end
48
-
49
- def pipeline(&block)
50
- Definition::Pipeline.new(&block) if block_given?
51
- end
52
46
  end
53
47
  end
54
48
  end
@@ -6,6 +6,18 @@ module Buildkite
6
6
  module Builder
7
7
  module Commands
8
8
  class Abstract
9
+ PIPELINES_DIRECTORY = 'pipelines'
10
+ POSSIBLE_PIPELINE_PATHS = [
11
+ File.join('.buildkite', Context::PIPELINE_DEFINITION_FILE),
12
+ File.join('buildkite', Context::PIPELINE_DEFINITION_FILE),
13
+ File.join(Context::PIPELINE_DEFINITION_FILE)
14
+ ].freeze
15
+ POSSIBLE_PIPELINES_PATHS = [
16
+ File.join('.buildkite', PIPELINES_DIRECTORY),
17
+ File.join('buildkite', PIPELINES_DIRECTORY),
18
+ File.join(PIPELINES_DIRECTORY)
19
+ ].freeze
20
+
9
21
  class << self
10
22
  attr_accessor :description
11
23
 
@@ -35,6 +47,8 @@ module Buildkite
35
47
  if options[:help]
36
48
  puts options[:help]
37
49
  return
50
+ elsif !pipeline_path
51
+ abort "Unable to find pipeline"
38
52
  end
39
53
 
40
54
  run
@@ -42,6 +56,10 @@ module Buildkite
42
56
 
43
57
  private
44
58
 
59
+ def pipeline_slug
60
+ ARGV.last || Buildkite.env&.pipeline_slug
61
+ end
62
+
45
63
  def command_name
46
64
  Commands::COMMANDS.key(self.class.name.split('::').last.to_sym)
47
65
  end
@@ -51,12 +69,39 @@ module Buildkite
51
69
  # Subclasses should override to parse options.
52
70
  end
53
71
 
54
- def available_pipelines
55
- @available_pipelines ||= pipelines_path.children.select(&:directory?).map { |dir| dir.basename.to_s }
72
+ def log
73
+ @log ||= begin
74
+ Logger.new($stdout).tap do |logger|
75
+ logger.formatter = proc do |_severity, _datetime, _progname, msg|
76
+ "#{msg}\n"
77
+ end
78
+ end
79
+ end
56
80
  end
57
81
 
58
- def pipelines_path
59
- Buildkite::Builder.root.join(Runner::PIPELINES_PATH)
82
+ def pipeline_path
83
+ @pipeline_path ||=
84
+ find_root_by_main_pipeline ||
85
+ find_root_by_multi_pipeline
86
+ end
87
+
88
+ def find_root_by_main_pipeline
89
+ POSSIBLE_PIPELINE_PATHS.map { |path| Builder.root.join(path) }.find(&:exist?)&.dirname
90
+ end
91
+
92
+ def find_root_by_multi_pipeline
93
+ pipelines_path = POSSIBLE_PIPELINES_PATHS.map { |path| Builder.root.join(path) }.find(&:directory?)
94
+
95
+ if pipelines_path
96
+ if pipeline_slug
97
+ path = pipelines_path.join(pipeline_slug)
98
+ path if path.directory?
99
+ elsif pipelines_path.children.one?
100
+ pipelines_path.children.first
101
+ else
102
+ raise 'Your project has multiple pipelines, please specify one.'
103
+ end
104
+ end
60
105
  end
61
106
  end
62
107
  end
@@ -9,19 +9,7 @@ module Buildkite
9
9
  self.description = 'Outputs the pipeline YAML.'
10
10
 
11
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
12
+ puts Context.build(pipeline_path).pipeline.to_yaml
25
13
  end
26
14
  end
27
15
  end
@@ -5,11 +5,31 @@ module Buildkite
5
5
  module Commands
6
6
  class Run < Abstract
7
7
  private
8
+ include LoggingUtils
9
+ using Rainbow
8
10
 
9
11
  self.description = 'Builds and uploads the generated pipeline.'
10
12
 
11
13
  def run
12
- Builder::Runner.run
14
+ relative_pipeline_path = pipeline_path.relative_path_from(Builder.root)
15
+
16
+ # This entrypoint is for running on CI. It expects certain environment
17
+ # variables to be set. It also uploads the pipeline to Buildkite.
18
+ log.info "#{'+++ ' if Buildkite.env}🧰 " + 'Buildkite Builder'.color(:springgreen) + " ─ #{relative_pipeline_path.to_s.yellow}"
19
+ Context.new(pipeline_path, logger: log).upload
20
+ end
21
+
22
+ private
23
+
24
+ def pipeline_path
25
+ pipeline_path_override || super
26
+ end
27
+
28
+ def pipeline_path_override
29
+ if ENV['BUILDKITE_BUILDER_PIPELINE_PATH']
30
+ path = Pathname.new(ENV['BUILDKITE_BUILDER_PIPELINE_PATH'])
31
+ path.absolute? ? path : Builder.root.join(path)
32
+ end
13
33
  end
14
34
  end
15
35
  end
@@ -0,0 +1,108 @@
1
+ require 'logger'
2
+ require 'tempfile'
3
+
4
+ module Buildkite
5
+ module Builder
6
+ class Context
7
+ include Definition::Helper
8
+ include LoggingUtils
9
+ using Rainbow
10
+
11
+ PIPELINE_DEFINITION_FILE = Pathname.new('pipeline.rb').freeze
12
+
13
+ attr_reader :logger
14
+ attr_reader :root
15
+ attr_reader :pipeline
16
+ attr_reader :artifacts
17
+
18
+ def self.build(root, logger: nil)
19
+ context = new(root, logger: logger)
20
+ context.build
21
+ context
22
+ end
23
+
24
+ def initialize(root, logger: nil)
25
+ @root = root
26
+ @logger = logger || Logger.new(File::NULL)
27
+ @artifacts = []
28
+ end
29
+
30
+ def build
31
+ results = benchmark("\nDone (%s)".color(:springgreen)) do
32
+ unless @pipeline
33
+ @pipeline = Pipelines::Pipeline.new
34
+
35
+ load_manifests
36
+ load_templates
37
+ load_processors
38
+ load_pipeline
39
+ run_processors
40
+ end
41
+ end
42
+ logger.info(results)
43
+
44
+ @pipeline
45
+ end
46
+
47
+ def upload
48
+ build unless @pipeline
49
+
50
+ logger.info '+++ :paperclip: Uploading artifacts'
51
+ upload_artifacts
52
+
53
+ # Upload the pipeline.
54
+ Tempfile.create(['pipeline', '.yml']) do |file|
55
+ file.sync = true
56
+ file.write(pipeline.to_yaml)
57
+
58
+ logger.info '+++ :paperclip: Uploading pipeline.yml as artifact'
59
+ Buildkite::Pipelines::Command.artifact!(:upload, file.path)
60
+ logger.info '+++ :pipeline: Uploading pipeline'
61
+ Buildkite::Pipelines::Command.pipeline!(:upload, file.path)
62
+ end
63
+ end
64
+
65
+ private
66
+
67
+ def load_manifests
68
+ Loaders::Manifests.load(root).each do |name, asset|
69
+ Manifest[name] = asset
70
+ end
71
+ end
72
+
73
+ def load_templates
74
+ Loaders::Templates.load(root).each do |name, asset|
75
+ pipeline.template(name, &asset)
76
+ end
77
+ end
78
+
79
+ def load_processors
80
+ Loaders::Processors.load(root)
81
+ end
82
+
83
+ def run_processors
84
+ pipeline.processors.each do |processor|
85
+ processor.process(self)
86
+ end
87
+ end
88
+
89
+ def upload_artifacts
90
+ return if artifacts.empty?
91
+
92
+ artifacts.each do |path|
93
+ if File.exist?(path)
94
+ Buildkite::Pipelines::Command.artifact!(:upload, path)
95
+ end
96
+ end
97
+ end
98
+
99
+ def load_pipeline
100
+ pipeline.instance_eval(&pipeline_definition)
101
+ end
102
+
103
+ def pipeline_definition
104
+ @pipeline_definition ||= load_definition(root.join(PIPELINE_DEFINITION_FILE), Definition::Pipeline)
105
+ end
106
+ end
107
+ end
108
+ end