buildkite-builder 1.0.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +118 -14
  3. data/VERSION +1 -1
  4. data/lib/buildkite/builder.rb +12 -14
  5. data/lib/buildkite/builder/commands/abstract.rb +1 -1
  6. data/lib/buildkite/builder/commands/preview.rb +10 -8
  7. data/lib/buildkite/builder/commands/run.rb +11 -1
  8. data/lib/buildkite/builder/context.rb +70 -0
  9. data/lib/buildkite/builder/loaders/abstract.rb +6 -10
  10. data/lib/buildkite/builder/loaders/manifests.rb +1 -1
  11. data/lib/buildkite/builder/loaders/processors.rb +2 -2
  12. data/lib/buildkite/builder/loaders/templates.rb +1 -1
  13. data/lib/buildkite/builder/manifest.rb +1 -1
  14. data/lib/buildkite/builder/processors/abstract.rb +7 -7
  15. data/lib/buildkite/builder/rainbow.rb +1 -1
  16. data/lib/buildkite/builder/runner.rb +25 -63
  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 +23 -0
  20. data/lib/buildkite/pipelines/steps/abstract.rb +4 -3
  21. data/lib/buildkite/pipelines/steps/block.rb +1 -0
  22. metadata +104 -21
  23. data/lib/vendor/rainbow/Changelog.md +0 -101
  24. data/lib/vendor/rainbow/Gemfile +0 -30
  25. data/lib/vendor/rainbow/LICENSE +0 -20
  26. data/lib/vendor/rainbow/README.markdown +0 -225
  27. data/lib/vendor/rainbow/Rakefile +0 -11
  28. data/lib/vendor/rainbow/lib/rainbow.rb +0 -13
  29. data/lib/vendor/rainbow/lib/rainbow/color.rb +0 -150
  30. data/lib/vendor/rainbow/lib/rainbow/ext/string.rb +0 -64
  31. data/lib/vendor/rainbow/lib/rainbow/global.rb +0 -25
  32. data/lib/vendor/rainbow/lib/rainbow/null_presenter.rb +0 -100
  33. data/lib/vendor/rainbow/lib/rainbow/presenter.rb +0 -144
  34. data/lib/vendor/rainbow/lib/rainbow/refinement.rb +0 -14
  35. data/lib/vendor/rainbow/lib/rainbow/string_utils.rb +0 -22
  36. data/lib/vendor/rainbow/lib/rainbow/version.rb +0 -5
  37. data/lib/vendor/rainbow/lib/rainbow/wrapper.rb +0 -22
  38. data/lib/vendor/rainbow/lib/rainbow/x11_color_names.rb +0 -153
  39. data/lib/vendor/rainbow/rainbow.gemspec +0 -23
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 99dafa9c69892d614803f254d86690b24cdca47b5c8d5e6355abfe29180df5e2
4
- data.tar.gz: 35e7a93c70449afa735b1ebde2979ecc63e51c8e549031674258083bcaa273f4
3
+ metadata.gz: 024ee5aff1b1a819fe74946becf1e3b1941c9f6e8ea86ba822a2d1411209e098
4
+ data.tar.gz: d9716d08bb4512fd0c9490ad26b1618a2b5e4f896693d5f5bca76024500fef70
5
5
  SHA512:
6
- metadata.gz: dd431d4a2db144be98214266408f4b6bbaf76ea354347b559fe78149f6225a1ab372b4aa3fe62b38ed1ce7eaf9204a68e81cd37c629d0698e28eaedcf2c0ff2c
7
- data.tar.gz: 4c57e2a787c47c77f68582690530ea232f14516256d54e8fb5ede6a3600fa6b83425fb1a5012a687d4caa9c2f12ee03350ba4227f55646cf169118d829775cd2
6
+ metadata.gz: 3d8130054bd4276b9d1c4505798985b9032ca406c126de026fa36bbb0fa919541c4c7d4cdd4c510b52e6554febb66c2e8a8bcc8e63aa54061fb727e6cbe8b752
7
+ data.tar.gz: 2492024dff09bfd31688c07ca712fed1b3191584f9b5917c4294775f687671decfcd3f0e0b20635bb3941c88b7097129e79700706a916b615ca11867e47a75e9
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.1.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 CHANGED
@@ -1 +1 @@
1
- 1.0.0
1
+ 1.1.0
@@ -1,11 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'bundler'
4
3
  require 'pathname'
5
4
 
6
5
  module Buildkite
7
6
  module Builder
8
7
  autoload :Commands, File.expand_path('builder/commands', __dir__)
8
+ autoload :Context, File.expand_path('builder/context', __dir__)
9
9
  autoload :Definition, File.expand_path('builder/definition', __dir__)
10
10
  autoload :FileResolver, File.expand_path('builder/file_resolver', __dir__)
11
11
  autoload :Github, File.expand_path('builder/github', __dir__)
@@ -16,7 +16,7 @@ module Buildkite
16
16
  autoload :Rainbow, File.expand_path('builder/rainbow', __dir__)
17
17
  autoload :Runner, File.expand_path('builder/runner', __dir__)
18
18
 
19
- BUILDKITE_DIRECTORY_NAME = '.buildkite/'
19
+ BUILDKITE_DIRECTORY_NAME = Pathname.new('.buildkite').freeze
20
20
 
21
21
  class << self
22
22
  def root(start_path: Dir.pwd, reset: false)
@@ -24,6 +24,16 @@ module Buildkite
24
24
  @root ||= find_buildkite_directory(start_path)
25
25
  end
26
26
 
27
+ def template(&block)
28
+ Definition::Template.new(&block) if block_given?
29
+ end
30
+
31
+ def pipeline(&block)
32
+ Definition::Pipeline.new(&block) if block_given?
33
+ end
34
+
35
+ private
36
+
27
37
  def find_buildkite_directory(start_path)
28
38
  path = Pathname.new(start_path)
29
39
  until path.join(BUILDKITE_DIRECTORY_NAME).exist? && path.join(BUILDKITE_DIRECTORY_NAME).directory?
@@ -34,18 +44,6 @@ module Buildkite
34
44
  path.expand_path
35
45
  end
36
46
 
37
- def expand_path(path)
38
- path = Pathname.new(path)
39
- path.absolute? ? path : root.join(path)
40
- end
41
-
42
- def template(&block)
43
- Definition::Template.new(&block) if block_given?
44
- end
45
-
46
- def pipeline(&block)
47
- Definition::Pipeline.new(&block) if block_given?
48
- end
49
47
  end
50
48
  end
51
49
  end
@@ -56,7 +56,7 @@ module Buildkite
56
56
  end
57
57
 
58
58
  def pipelines_path
59
- Buildkite::Builder.root.join(Runner::PIPELINES_PATH)
59
+ Builder.root.join(Builder::BUILDKITE_DIRECTORY_NAME).join(Runner::PIPELINES_PATH)
60
60
  end
61
61
  end
62
62
  end
@@ -9,19 +9,21 @@ 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'
12
+ pipeline = ARGV.last
13
+
14
+ if !pipeline && !root_pipeline?
15
+ if available_pipelines.one?
16
+ pipeline = available_pipelines.first
17
+ else
18
+ raise 'You must specify a pipeline'
19
+ end
14
20
  end
15
21
 
16
22
  puts Runner.new(pipeline: pipeline).run.to_yaml
17
23
  end
18
24
 
19
- def pipeline
20
- @pipeline ||= ARGV.last || begin
21
- if available_pipelines.one?
22
- available_pipelines.first
23
- end
24
- end
25
+ def root_pipeline?
26
+ pipelines_path.join(Context::PIPELINE_DEFINITION_FILE).exist?
25
27
  end
26
28
  end
27
29
  end
@@ -9,7 +9,17 @@ module Buildkite
9
9
  self.description = 'Builds and uploads the generated pipeline.'
10
10
 
11
11
  def run
12
- Builder::Runner.run
12
+ # This entrypoint is for running on CI. It expects certain environment
13
+ # variables to be set.
14
+ options = {
15
+ upload: true
16
+ }
17
+
18
+ if available_pipelines.include?(Buildkite.env.pipeline_slug)
19
+ options[:pipeline] = Buildkite.env.pipeline_slug
20
+ end
21
+
22
+ Builder::Runner.new(**options).run
13
23
  end
14
24
  end
15
25
  end
@@ -0,0 +1,70 @@
1
+ module Buildkite
2
+ module Builder
3
+ class Context
4
+ include Definition::Helper
5
+
6
+ PIPELINE_DEFINITION_FILE = Pathname.new('pipeline.rb').freeze
7
+
8
+ attr_reader :logger
9
+ attr_reader :root
10
+ attr_reader :pipeline
11
+
12
+ def self.build(root, logger: nil)
13
+ context = new(root, logger: logger)
14
+ context.build
15
+ context
16
+ end
17
+
18
+ def initialize(root, logger: nil)
19
+ @root = root
20
+ @logger = logger || Logger.new(File::NULL)
21
+ end
22
+
23
+ def build
24
+ unless @pipeline
25
+ @pipeline = Pipelines::Pipeline.new
26
+
27
+ load_manifests
28
+ load_templates
29
+ load_processors
30
+ load_pipeline
31
+ run_processors
32
+ end
33
+
34
+ @pipeline
35
+ end
36
+
37
+ private
38
+
39
+ def load_manifests
40
+ Loaders::Manifests.load(root).each do |name, asset|
41
+ Manifest[name] = asset
42
+ end
43
+ end
44
+
45
+ def load_templates
46
+ Loaders::Templates.load(root).each do |name, asset|
47
+ pipeline.template(name, &asset)
48
+ end
49
+ end
50
+
51
+ def load_processors
52
+ Loaders::Processors.load(root)
53
+ end
54
+
55
+ def run_processors
56
+ pipeline.processors.each do |processor|
57
+ processor.process(self)
58
+ end
59
+ end
60
+
61
+ def load_pipeline
62
+ pipeline.instance_eval(&pipeline_definition)
63
+ end
64
+
65
+ def pipeline_definition
66
+ @pipeline_definition ||= load_definition(root.join(PIPELINE_DEFINITION_FILE), Definition::Pipeline)
67
+ end
68
+ end
69
+ end
70
+ end
@@ -5,14 +5,14 @@ module Buildkite
5
5
  module Loaders
6
6
  class Abstract
7
7
  attr_reader :assets
8
- attr_reader :pipeline
8
+ attr_reader :root
9
9
 
10
- def self.load(pipeline)
11
- new(pipeline).assets
10
+ def self.load(root)
11
+ new(root).assets
12
12
  end
13
13
 
14
- def initialize(pipeline)
15
- @pipeline = pipeline
14
+ def initialize(root)
15
+ @root = root
16
16
  @assets = {}
17
17
  load
18
18
  end
@@ -20,11 +20,7 @@ module Buildkite
20
20
  private
21
21
 
22
22
  def buildkite_path
23
- Buildkite::Builder.root.join('.buildkite')
24
- end
25
-
26
- def pipeline_path
27
- buildkite_path.join("pipelines/#{pipeline}")
23
+ Builder.root.join(Builder::BUILDKITE_DIRECTORY_NAME)
28
24
  end
29
25
 
30
26
  def load
@@ -15,7 +15,7 @@ module Buildkite
15
15
  end
16
16
 
17
17
  def manifests_path
18
- pipeline_path.join(MANIFESTS_PATH)
18
+ root.join(MANIFESTS_PATH)
19
19
  end
20
20
  end
21
21
  end
@@ -19,7 +19,7 @@ module Buildkite
19
19
  return unless path.directory?
20
20
 
21
21
  path.children.map do |file|
22
- required_status = require(file)
22
+ required_status = require(file.to_s)
23
23
  add(file.basename, { required: required_status })
24
24
  end
25
25
  end
@@ -29,7 +29,7 @@ module Buildkite
29
29
  end
30
30
 
31
31
  def pipeline_processors_path
32
- pipeline_path.join(PROCESSORS_PATH)
32
+ root.join(PROCESSORS_PATH)
33
33
  end
34
34
  end
35
35
  end
@@ -17,7 +17,7 @@ module Buildkite
17
17
  end
18
18
 
19
19
  def templates_path
20
- pipeline_path.join(TEMPLATES_PATH)
20
+ root.join(TEMPLATES_PATH)
21
21
  end
22
22
  end
23
23
  end
@@ -2,7 +2,7 @@
2
2
 
3
3
  require 'digest/md5'
4
4
  require 'pathname'
5
- require 'set'
5
+ require 'sorted_set'
6
6
 
7
7
  module Buildkite
8
8
  module Builder