diff --git a/CHANGELOG.md b/CHANGELOG.md index b39ab1f2d..44df305cb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,8 @@ _None_ ### New Features -_None_ +- Add new `buildkite_annotate` action to add/remove annotations from the current build. [#442] +- Add new `buildkite_metadata` action to set/get metadata from the current build. [#442] ### Bug Fixes @@ -24,7 +25,7 @@ _None_ ### New Features -- Added Mac support to all `common` actions and any relevant `ios` actions [#439] +- Add Mac support to all `common` actions and any relevant `ios` actions [#439] ## 6.2.0 diff --git a/lib/fastlane/plugin/wpmreleasetoolkit/actions/common/buildkite_annotate_action.rb b/lib/fastlane/plugin/wpmreleasetoolkit/actions/common/buildkite_annotate_action.rb new file mode 100644 index 000000000..2b0b2f34f --- /dev/null +++ b/lib/fastlane/plugin/wpmreleasetoolkit/actions/common/buildkite_annotate_action.rb @@ -0,0 +1,85 @@ +module Fastlane + module Actions + class BuildkiteAnnotateAction < Action + def self.run(params) + message = params[:message] + context = params[:context] + style = params[:style] + + if message.nil? + # Delete an annotation, but swallow the error if the annotation didn't exist — to avoid having + # this action failing or printing a red log for no good reason — hence the `|| true` + ctx_param = "--context #{context.shellescape}" unless context.nil? + sh("buildkite-agent annotation remove #{ctx_param} || true") + else + # Add new annotation using `buildkite-agent` + extra_params = { + context: context, + style: style + }.compact.flat_map { |k, v| ["--#{k}", v] } + sh('buildkite-agent', 'annotate', *extra_params, params[:message]) + end + end + + ##################################################### + # @!group Documentation + ##################################################### + + def self.description + 'Add or remove annotations to the current Buildkite build' + end + + def self.details + <<~DETAILS + Add or remove annotations to the current Buildkite build. + + Has to be run on a CI job (where a `buildkite-agent` is running), e.g. typically by a lane + that is triggered as part of a Buildkite CI step. + + See https://buildkite.com/docs/agent/v3/cli-annotate + DETAILS + end + + def self.available_options + [ + FastlaneCore::ConfigItem.new( + key: :context, + env_name: 'BUILDKITE_ANNOTATION_CONTEXT', + description: 'The context of the annotation used to differentiate this annotation from others', + type: String, + optional: true + ), + FastlaneCore::ConfigItem.new( + key: :style, + env_name: 'BUILDKITE_ANNOTATION_STYLE', + description: 'The style of the annotation (`success`, `info`, `warning` or `error`)', + type: String, + optional: true, + verify_block: proc do |value| + valid_values = %w[success info warning error] + next if value.nil? || valid_values.include?(value) + + UI.user_error!("Invalid value `#{value}` for parameter `style`. Valid values are: #{valid_values.join(', ')}") + end + ), + FastlaneCore::ConfigItem.new( + key: :message, + description: 'The message to use in the new annotation. Supports GFM-Flavored Markdown. ' \ + + 'If message is nil, any existing annotation with the provided context will be deleted', + type: String, + optional: true, + default_value: nil # nil message = delete existing annotation if any + ), + ] + end + + def self.authors + ['Automattic'] + end + + def self.is_supported?(platform) + true + end + end + end +end diff --git a/lib/fastlane/plugin/wpmreleasetoolkit/actions/common/buildkite_metadata_action.rb b/lib/fastlane/plugin/wpmreleasetoolkit/actions/common/buildkite_metadata_action.rb new file mode 100644 index 000000000..15d182849 --- /dev/null +++ b/lib/fastlane/plugin/wpmreleasetoolkit/actions/common/buildkite_metadata_action.rb @@ -0,0 +1,67 @@ +module Fastlane + module Actions + class BuildkiteMetadataAction < Action + def self.run(params) + # Set/Add new metadata values + params[:set]&.each do |key, value| + sh('buildkite-agent', 'meta-data', 'set', key.to_s, value.to_s) + end + + # Return value of existing metadata key + sh('buildkite-agent', 'meta-data', 'get', params[:get].to_s) unless params[:get].nil? + end + + ##################################################### + # @!group Documentation + ##################################################### + + def self.description + 'Set/Get metadata to the current Buildkite build' + end + + def self.details + <<~DETAILS + Set and/or get metadata to the current Buildkite build. + + Has to be run on a CI job (where a `buildkite-agent` is running), e.g. typically by a lane + that is triggered as part of a Buildkite CI step. + + See https://buildkite.com/docs/agent/v3/cli-meta-data + DETAILS + end + + def self.available_options + [ + FastlaneCore::ConfigItem.new( + key: :set, + env_name: 'BUILDKITE_METADATA_SET', + description: 'The hash of key/value pairs of the meta-data to set', + type: Hash, + optional: true, + default_value: nil + ), + FastlaneCore::ConfigItem.new( + key: :get, + env_name: 'BUILDKITE_METADATA_GET', + description: 'The key of the metadata to get the value of', + type: String, + optional: true, + default_value: nil + ), + ] + end + + def self.return_value + 'The value of the Buildkite metadata corresponding to the provided `get` key. `nil` if no `get` parameter was provided.' + end + + def self.authors + ['Automattic'] + end + + def self.is_supported?(platform) + true + end + end + end +end diff --git a/spec/buildkite_annotate_action_spec.rb b/spec/buildkite_annotate_action_spec.rb new file mode 100644 index 000000000..f042acee7 --- /dev/null +++ b/spec/buildkite_annotate_action_spec.rb @@ -0,0 +1,82 @@ +require 'spec_helper' + +describe Fastlane::Actions::BuildkiteAnnotateAction do + describe '`style` parameter validation' do + it 'errors if we use an invalid style' do + expect(FastlaneCore::UI).to receive(:user_error!).with('Invalid value `failure` for parameter `style`. Valid values are: success, info, warning, error') + + run_described_fastlane_action( + context: 'ctx', + style: 'failure', + message: 'Fake message' + ) + end + + %w[success info warning error].each do |style| + it "accepts `#{style}` as a valid style" do + expect(FastlaneCore::UI).not_to receive(:user_error!) + cmd = run_described_fastlane_action( + context: 'ctx', + style: style, + message: 'message' + ) + expect(cmd).to eq("buildkite-agent annotate --context ctx --style #{style} message") + end + end + + it 'accepts `nil` as a valid style' do + expect(FastlaneCore::UI).not_to receive(:user_error!) + cmd = run_described_fastlane_action( + context: 'ctx', + message: 'message' + ) + expect(cmd).to eq('buildkite-agent annotate --context ctx message') + end + end + + describe 'annotation creation' do + it 'generates the right command to create an annotation when message is provided' do + cmd = run_described_fastlane_action( + context: 'ctx', + style: 'warning', + message: 'message' + ) + expect(cmd).to eq('buildkite-agent annotate --context ctx --style warning message') + end + + it 'properly escapes the message and context' do + cmd = run_described_fastlane_action( + context: 'some ctx', + style: 'warning', + message: 'a nice message; with fun characters & all…' + ) + expect(cmd).to eq('buildkite-agent annotate --context some\ ctx --style warning a\ \nice\\ message\;\ with\ fun\ characters\ \&\ all\…') + end + + it 'falls back to Buildkite\'s default `context` when none is provided' do + cmd = run_described_fastlane_action( + style: 'warning', + message: 'a nice message' + ) + expect(cmd).to eq('buildkite-agent annotate --style warning a\ nice\ message') + end + + it 'falls back to Buildkite\'s default `style` when none is provided' do + cmd = run_described_fastlane_action( + context: 'my-ctx', + message: 'a nice message' + ) + expect(cmd).to eq('buildkite-agent annotate --context my-ctx a\ nice\ message') + end + end + + describe 'annotation deletion' do + it 'generates the right command to delete an annotation when no message is provided' do + cmd = run_described_fastlane_action( + context: 'some ctx', + message: nil + ) + expect(cmd).to eq('buildkite-agent annotation remove --context some\ ctx || true') + end + end +end diff --git a/spec/buildkite_metadata_action_spec.rb b/spec/buildkite_metadata_action_spec.rb new file mode 100644 index 000000000..867acdea4 --- /dev/null +++ b/spec/buildkite_metadata_action_spec.rb @@ -0,0 +1,45 @@ +require 'spec_helper' + +describe Fastlane::Actions::BuildkiteMetadataAction do + it 'calls the right command to set a single metadata' do + expect(Fastlane::Action).to receive(:sh).with('buildkite-agent', 'meta-data', 'set', 'foo', 'bar') + + res = run_described_fastlane_action(set: { foo: 'bar' }) + expect(res).to be_nil + end + + it 'calls the commands as many times as necessary when we want to set multiple metadata at once' do + expect(Fastlane::Action).to receive(:sh).with('buildkite-agent', 'meta-data', 'set', 'key1', 'value1') + expect(Fastlane::Action).to receive(:sh).with('buildkite-agent', 'meta-data', 'set', 'key2', 'value2') + + metadata = { + key1: 'value1', + key2: 'value2' + } + run_described_fastlane_action(set: metadata) + end + + it 'calls the right command to get the value of metadata, and returns the right value' do + expect(Fastlane::Action).to receive(:sh).with('buildkite-agent', 'meta-data', 'get', 'foo') + allow(Fastlane::Action).to receive(:sh).with('buildkite-agent', 'meta-data', 'get', 'foo').and_return('foo value') + + res = run_described_fastlane_action(get: 'foo') + expect(res).to eq('foo value') + end + + it 'allows both setting and getting metadata in the same call' do + # Might not be the main way we intend to use this action… but it's still supported. + expect(Fastlane::Action).to receive(:sh).with('buildkite-agent', 'meta-data', 'set', 'key1', 'value1') + expect(Fastlane::Action).to receive(:sh).with('buildkite-agent', 'meta-data', 'set', 'key2', 'value2') + expect(Fastlane::Action).to receive(:sh).with('buildkite-agent', 'meta-data', 'get', 'key3') + allow(Fastlane::Action).to receive(:sh).with('buildkite-agent', 'meta-data', 'get', 'key3').and_return('value3') + + new_metadata = { + key1: 'value1', + key2: 'value2' + } + res = run_described_fastlane_action(set: new_metadata, get: 'key3') + + expect(res).to eq('value3') + end +end