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.
Files changed (79) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +11 -0
  3. data/.rspec +3 -0
  4. data/CHANGELOG.md +0 -0
  5. data/CODE_OF_CONDUCT.md +74 -0
  6. data/Gemfile +13 -0
  7. data/Gemfile.lock +56 -0
  8. data/LICENSE.txt +21 -0
  9. data/README.md +44 -0
  10. data/Rakefile +6 -0
  11. data/bin/buildkite-builder +6 -0
  12. data/bin/console +14 -0
  13. data/bin/setup +8 -0
  14. data/buildkite-builder.gemspec +28 -0
  15. data/lib/buildkite-builder.rb +14 -0
  16. data/lib/buildkite/builder.rb +54 -0
  17. data/lib/buildkite/builder/commands.rb +49 -0
  18. data/lib/buildkite/builder/commands/abstract.rb +64 -0
  19. data/lib/buildkite/builder/commands/files.rb +25 -0
  20. data/lib/buildkite/builder/commands/preview.rb +29 -0
  21. data/lib/buildkite/builder/definition.rb +24 -0
  22. data/lib/buildkite/builder/file_resolver.rb +59 -0
  23. data/lib/buildkite/builder/github.rb +71 -0
  24. data/lib/buildkite/builder/loaders.rb +12 -0
  25. data/lib/buildkite/builder/loaders/abstract.rb +40 -0
  26. data/lib/buildkite/builder/loaders/manifests.rb +23 -0
  27. data/lib/buildkite/builder/loaders/processors.rb +37 -0
  28. data/lib/buildkite/builder/loaders/templates.rb +25 -0
  29. data/lib/buildkite/builder/logging_utils.rb +24 -0
  30. data/lib/buildkite/builder/manifest.rb +89 -0
  31. data/lib/buildkite/builder/manifest/rule.rb +51 -0
  32. data/lib/buildkite/builder/processors.rb +9 -0
  33. data/lib/buildkite/builder/processors/abstract.rb +76 -0
  34. data/lib/buildkite/builder/rainbow.rb +9 -0
  35. data/lib/buildkite/builder/runner.rb +114 -0
  36. data/lib/buildkite/env.rb +52 -0
  37. data/lib/buildkite/pipelines.rb +13 -0
  38. data/lib/buildkite/pipelines/api.rb +119 -0
  39. data/lib/buildkite/pipelines/attributes.rb +137 -0
  40. data/lib/buildkite/pipelines/command.rb +59 -0
  41. data/lib/buildkite/pipelines/helpers.rb +43 -0
  42. data/lib/buildkite/pipelines/helpers/block.rb +18 -0
  43. data/lib/buildkite/pipelines/helpers/command.rb +21 -0
  44. data/lib/buildkite/pipelines/helpers/depends_on.rb +13 -0
  45. data/lib/buildkite/pipelines/helpers/key.rb +13 -0
  46. data/lib/buildkite/pipelines/helpers/label.rb +18 -0
  47. data/lib/buildkite/pipelines/helpers/plugins.rb +24 -0
  48. data/lib/buildkite/pipelines/helpers/retry.rb +20 -0
  49. data/lib/buildkite/pipelines/helpers/skip.rb +15 -0
  50. data/lib/buildkite/pipelines/helpers/soft_fail.rb +15 -0
  51. data/lib/buildkite/pipelines/helpers/timeout_in_minutes.rb +17 -0
  52. data/lib/buildkite/pipelines/pipeline.rb +129 -0
  53. data/lib/buildkite/pipelines/plugin.rb +23 -0
  54. data/lib/buildkite/pipelines/steps.rb +15 -0
  55. data/lib/buildkite/pipelines/steps/abstract.rb +26 -0
  56. data/lib/buildkite/pipelines/steps/block.rb +20 -0
  57. data/lib/buildkite/pipelines/steps/command.rb +30 -0
  58. data/lib/buildkite/pipelines/steps/input.rb +20 -0
  59. data/lib/buildkite/pipelines/steps/skip.rb +28 -0
  60. data/lib/buildkite/pipelines/steps/trigger.rb +22 -0
  61. data/lib/buildkite/pipelines/steps/wait.rb +18 -0
  62. data/lib/vendor/rainbow/Changelog.md +101 -0
  63. data/lib/vendor/rainbow/Gemfile +30 -0
  64. data/lib/vendor/rainbow/LICENSE +20 -0
  65. data/lib/vendor/rainbow/README.markdown +225 -0
  66. data/lib/vendor/rainbow/Rakefile +11 -0
  67. data/lib/vendor/rainbow/lib/rainbow.rb +13 -0
  68. data/lib/vendor/rainbow/lib/rainbow/color.rb +150 -0
  69. data/lib/vendor/rainbow/lib/rainbow/ext/string.rb +64 -0
  70. data/lib/vendor/rainbow/lib/rainbow/global.rb +25 -0
  71. data/lib/vendor/rainbow/lib/rainbow/null_presenter.rb +100 -0
  72. data/lib/vendor/rainbow/lib/rainbow/presenter.rb +144 -0
  73. data/lib/vendor/rainbow/lib/rainbow/refinement.rb +14 -0
  74. data/lib/vendor/rainbow/lib/rainbow/string_utils.rb +22 -0
  75. data/lib/vendor/rainbow/lib/rainbow/version.rb +5 -0
  76. data/lib/vendor/rainbow/lib/rainbow/wrapper.rb +22 -0
  77. data/lib/vendor/rainbow/lib/rainbow/x11_color_names.rb +153 -0
  78. data/lib/vendor/rainbow/rainbow.gemspec +23 -0
  79. metadata +126 -0
@@ -0,0 +1,137 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'set'
4
+
5
+ module Buildkite
6
+ module Pipelines
7
+ module Attributes
8
+ def self.included(base)
9
+ base.extend(ClassMethods)
10
+ end
11
+
12
+ def get(attr)
13
+ attributes[validate(attr)]
14
+ end
15
+
16
+ def set(attr, value)
17
+ attributes[validate(attr)] = value
18
+ end
19
+
20
+ def has?(attr)
21
+ # Don't validate for has? calls.
22
+ attributes.key?(attr.to_s)
23
+ end
24
+
25
+ def permits?(attr)
26
+ self.class.permits?(attr)
27
+ end
28
+
29
+ def append(attr, value)
30
+ ensure_array_value(attr)
31
+ get(attr).push(*[value].flatten)
32
+ value
33
+ end
34
+
35
+ def prepend(attr, value)
36
+ ensure_array_value(attr)
37
+ get(attr).unshift(*[value].flatten)
38
+ value
39
+ end
40
+
41
+ def permitted_attributes
42
+ self.class.permitted_attributes
43
+ end
44
+
45
+ def unset(attr)
46
+ attributes.delete(validate(attr))
47
+ end
48
+
49
+ def to_h
50
+ permitted_attributes.each_with_object({}) do |attr, hash|
51
+ hash[attr] = get(attr) if has?(attr)
52
+ end
53
+ end
54
+
55
+ module ClassMethods
56
+ def inherited(subclass)
57
+ subclass.permitted_attributes.merge(permitted_attributes)
58
+ end
59
+
60
+ def permitted_attributes
61
+ @permitted_attributes ||= Set.new
62
+ end
63
+
64
+ def permits?(attr)
65
+ @permitted_attributes.include?(attr.to_s)
66
+ end
67
+
68
+ def attribute(attribute_name, **options)
69
+ unless permitted_attributes.add?(attribute_name.to_s)
70
+ raise "Step already defined attribute: #{attribute_name}"
71
+ end
72
+
73
+ method_name = options.fetch(:as, attribute_name)
74
+
75
+ # Define the main helper method that sets or appends the attribute value.
76
+ define_method(method_name) do |*value|
77
+ if value.empty?
78
+ get(attribute_name)
79
+ elsif options[:append]
80
+ append(attribute_name, *value)
81
+ else
82
+ set(attribute_name, *value)
83
+ end
84
+ end
85
+
86
+ # Define a helper method that is equivalent to `||=` or `Set#add?`. It will
87
+ # set the attribute iff it hasn't been already set. It will return true/false
88
+ # for whether or not the value was set.
89
+ define_method("#{method_name}?") do |*args|
90
+ if args.empty?
91
+ raise ArgumentError, "`#{method_name}?` must be called with arguments"
92
+ elsif has?(method_name)
93
+ false
94
+ else
95
+ public_send(method_name, *args)
96
+ true
97
+ end
98
+ end
99
+
100
+ if options[:append]
101
+ # If this attribute appends by default, then provide a bang(!) helper method
102
+ # that allows you to clear and set the value in one go.
103
+ define_method("#{method_name}!") do |*args|
104
+ unset(attribute_name)
105
+ public_send(method_name, *args)
106
+ end
107
+ end
108
+
109
+ Helpers.prepend_attribute_helper(self, attribute_name)
110
+ end
111
+ end
112
+
113
+ private
114
+
115
+ def attributes
116
+ @attributes ||= {}
117
+ end
118
+
119
+ def ensure_array_value(attr)
120
+ if has?(attr)
121
+ set(attr, [get(attr)]) unless get(attr).is_a?(Array)
122
+ else
123
+ set(attr, [])
124
+ end
125
+ end
126
+
127
+ def validate(attr)
128
+ attr = attr.to_s
129
+ unless permits?(attr)
130
+ raise "Attribute not permitted on #{self.class.name} step: #{attr}"
131
+ end
132
+
133
+ attr
134
+ end
135
+ end
136
+ end
137
+ end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Buildkite
4
+ module Pipelines
5
+ class Command
6
+ BIN_PATH = 'buildkite-agent'
7
+
8
+ def self.pipeline!(*args)
9
+ abort unless pipeline(*args)
10
+ end
11
+
12
+ def self.pipeline(subcommand, *args)
13
+ new(:pipeline, subcommand, *args).run
14
+ end
15
+
16
+ def self.artifact!(*args)
17
+ abort unless artifact(*args)
18
+ end
19
+
20
+ def self.artifact(subcommand, *args)
21
+ new(:artifact, subcommand, *args).run
22
+ end
23
+
24
+ def initialize(command, subcommand, *args)
25
+ @command = command.to_s
26
+ @subcommand = subcommand.to_s
27
+ @options = extract_options(args)
28
+ @args = transform_args(args)
29
+ end
30
+
31
+ def run
32
+ system(*to_a)
33
+ end
34
+
35
+ private
36
+
37
+ def to_a
38
+ command = [BIN_PATH, @command, @subcommand]
39
+ command.concat(@options.to_a.flatten)
40
+ command.concat(@args)
41
+ end
42
+
43
+ def extract_options(args)
44
+ return {} unless args.first.is_a?(Hash)
45
+
46
+ args.shift.tap do |options|
47
+ options.transform_keys! do |key|
48
+ "--#{key.to_s.tr('_', '-')}"
49
+ end
50
+ options.transform_values!(&:to_s)
51
+ end
52
+ end
53
+
54
+ def transform_args(args)
55
+ args.map!(&:to_s)
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Buildkite
4
+ module Pipelines
5
+ module Helpers
6
+ ATTRIBUTE_HELPERS = {
7
+ block: :Block,
8
+ command: :Command,
9
+ depends_on: :DependsOn,
10
+ key: :Key,
11
+ label: :Label,
12
+ plugins: :Plugins,
13
+ retry: :Retry,
14
+ skip: :Skip,
15
+ soft_fail: :SoftFail,
16
+ timeout_in_minutes: :TimeoutInMinutes,
17
+ }.freeze
18
+
19
+ ATTRIBUTE_HELPERS.each do |name, mod|
20
+ autoload mod, File.expand_path("helpers/#{name}", __dir__)
21
+ end
22
+
23
+ def self.prepend_attribute_helper(step_class, attribute)
24
+ if ATTRIBUTE_HELPERS[attribute]
25
+ step_class.prepend(const_get(ATTRIBUTE_HELPERS[attribute]))
26
+ end
27
+ end
28
+
29
+ def self.sanitize(obj)
30
+ case obj
31
+ when Hash
32
+ obj.transform_keys(&:to_s).transform_values { |value| sanitize(value) }
33
+ when Array
34
+ obj.map { |value| sanitize(value) }
35
+ when Symbol, Pathname
36
+ obj.to_s
37
+ else
38
+ obj
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Buildkite
4
+ module Pipelines
5
+ module Helpers
6
+ module Block
7
+ def block(*args, emoji: nil)
8
+ if emoji
9
+ emoji = Array(emoji).map { |name| ":#{name}:" }.join
10
+ args[0] = [emoji, args.first].compact.join(' ')
11
+ end
12
+
13
+ super(*args)
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Buildkite
4
+ module Pipelines
5
+ module Helpers
6
+ module Command
7
+ def command(*values)
8
+ return super if values.empty?
9
+
10
+ values.flatten.each do |value|
11
+ if value == :noop
12
+ super('true')
13
+ else
14
+ super(value)
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Buildkite
4
+ module Pipelines
5
+ module Helpers
6
+ module DependsOn
7
+ def depends_on(*values)
8
+ values.flatten.each { |value| super(value) }
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Buildkite
4
+ module Pipelines
5
+ module Helpers
6
+ module Key
7
+ def identifier(arg)
8
+ key(arg)
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Buildkite
4
+ module Pipelines
5
+ module Helpers
6
+ module Label
7
+ def label(*args, emoji: nil)
8
+ if emoji
9
+ emoji = Array(emoji).map { |name| ":#{name}:" }.join
10
+ args[0] = [emoji, args.first].compact.join(' ')
11
+ end
12
+
13
+ super(*args)
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Buildkite
4
+ module Pipelines
5
+ module Helpers
6
+ module Plugins
7
+ def plugin(plugin_name, options = nil)
8
+ plugin_name = plugin_name.to_s
9
+ @plugins ||= {}
10
+
11
+ if @plugins.key?(plugin_name)
12
+ raise ArgumentError, "Plugin already used for command step: #{plugin_name}"
13
+ end
14
+
15
+ uri, version = pipeline.plugins.fetch(plugin_name)
16
+ new_plugin = Plugin.new(uri, version, options)
17
+ @plugins[plugin_name] = new_plugin
18
+
19
+ plugins(new_plugin.to_h)
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Buildkite
4
+ module Pipelines
5
+ module Helpers
6
+ module Retry
7
+ def automatically_retry(status:, limit:)
8
+ retry_value = get(:retry)&.[](:automatic)
9
+
10
+ unless retry_value.is_a?(Array)
11
+ retry_value = []
12
+ self.retry(automatic: retry_value)
13
+ end
14
+
15
+ retry_value.push(exit_status: status, limit: limit)
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Buildkite
4
+ module Pipelines
5
+ module Helpers
6
+ module Skip
7
+ # A helper method to see if the step is skipped.
8
+ # Skipped steps are `true` or a non-empty string.
9
+ def skipped?
10
+ get(:skip) != false && !get(:skip).to_s.empty?
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Buildkite
4
+ module Pipelines
5
+ module Helpers
6
+ module SoftFail
7
+ def soft_fail_on_status(*statuses)
8
+ statuses.each do |status|
9
+ soft_fail(exit_status: status)
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Buildkite
4
+ module Pipelines
5
+ module Helpers
6
+ module TimeoutInMinutes
7
+ def timeout(*args)
8
+ timeout_in_minutes(*args)
9
+ end
10
+
11
+ def timeout?(*args)
12
+ timeout_in_minutes?(*args)
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,129 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'yaml'
4
+
5
+ module Buildkite
6
+ module Pipelines
7
+ class Pipeline
8
+ attr_reader :steps
9
+ attr_reader :plugins
10
+ attr_reader :templates
11
+
12
+ def initialize(definition = nil, &block)
13
+ @env = {}
14
+ @steps = []
15
+ @plugins = {}
16
+ @templates = {}
17
+ @processors = []
18
+
19
+ instance_eval(&definition) if definition
20
+ instance_eval(&block) if block_given?
21
+ end
22
+
23
+ [
24
+ Steps::Block,
25
+ Steps::Command,
26
+ Steps::Input,
27
+ Steps::Trigger,
28
+ ].each do |type|
29
+ define_method(type.to_sym) do |template = nil, &block|
30
+ add(type, template, &block)
31
+ end
32
+ end
33
+
34
+ def env(*args)
35
+ if args.empty?
36
+ @env
37
+ elsif args.first.is_a?(Hash)
38
+ @env.merge!(args.first.transform_keys(&:to_s))
39
+ else
40
+ raise ArgumentError, 'value must be hash'
41
+ end
42
+ end
43
+
44
+ def skip(template = nil, &block)
45
+ step = add(Steps::Skip, template, &block)
46
+ # A skip step has a nil/noop command.
47
+ step.command(nil)
48
+ # Always set the skip attribute if it's in a falsey state.
49
+ step.skip(true) if !step.get(:skip) || step.skip.empty?
50
+ step
51
+ end
52
+
53
+ def wait(attributes = {}, &block)
54
+ step = add(Steps::Wait, &block)
55
+ step.wait(nil)
56
+ attributes.each do |key, value|
57
+ step.set(key, value)
58
+ end
59
+ step
60
+ end
61
+
62
+ def plugin(name, uri, version)
63
+ name = name.to_s
64
+
65
+ if plugins.key?(name)
66
+ raise ArgumentError, "Plugin already defined: #{name}"
67
+ end
68
+
69
+ @plugins[name] = [uri, version]
70
+ end
71
+
72
+ def template(name, &definition)
73
+ name = name.to_s
74
+
75
+ if templates.key?(name)
76
+ raise ArgumentError, "Template already defined: #{name}"
77
+ elsif !block_given?
78
+ raise ArgumentError, 'Template definition block must be given'
79
+ end
80
+
81
+ @templates[name.to_s] = definition
82
+ end
83
+
84
+ def processors(*processor_classes)
85
+ unless processor_classes.empty?
86
+ @processors.clear
87
+
88
+ processor_classes.flatten.each do |processor|
89
+ unless processor < Buildkite::Builder::Processors::Abstract
90
+ raise "#{processor} must inherit from Buildkite::Builder::Processors::Abstract"
91
+ end
92
+
93
+ @processors << processor
94
+ end
95
+ end
96
+
97
+ @processors
98
+ end
99
+
100
+ def to_h
101
+ pipeline = {}
102
+ if env.any?
103
+ pipeline[:env] = env
104
+ end
105
+ pipeline[:steps] = steps.map(&:to_h)
106
+
107
+ Helpers.sanitize(pipeline)
108
+ end
109
+
110
+ def to_yaml
111
+ YAML.dump(to_h)
112
+ end
113
+
114
+ private
115
+
116
+ def add(step_class, template = nil, &block)
117
+ steps.push(step_class.new(self, find_template(template), &block)).last
118
+ end
119
+
120
+ def find_template(name)
121
+ return unless name
122
+
123
+ templates[name.to_s] || begin
124
+ raise ArgumentError, "Template not defined: #{name}"
125
+ end
126
+ end
127
+ end
128
+ end
129
+ end