buildkite-builder 1.0.0.beta.1

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