Skip to content

Commit b8748c6

Browse files
authored
Add buildkite_add_trigger_step action (#648)
2 parents 4319437 + 3568553 commit b8748c6

File tree

3 files changed

+604
-1
lines changed

3 files changed

+604
-1
lines changed

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ _None_
1010

1111
### New Features
1212

13-
_None_
13+
- Introduce `buildkite_add_trigger_step` action, to insert a `trigger:` step in the current pipeline to trigger a child build from the current build. [#648]
1414

1515
### Bug Fixes
1616

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
# frozen_string_literal: true
2+
3+
require 'open3'
4+
5+
module Fastlane
6+
module Actions
7+
class BuildkiteAddTriggerStepAction < Action
8+
BUILDKITE_ENV_ERROR_MESSAGE = 'This action can only be run from within a Buildkite build'
9+
10+
def self.run(params)
11+
unless ENV.key?('BUILDKITE_JOB_ID')
12+
UI.user_error!(BUILDKITE_ENV_ERROR_MESSAGE)
13+
end
14+
15+
# Extract parameters
16+
pipeline_file = params[:pipeline_file]
17+
build_name = File.basename(pipeline_file, '.yml')
18+
message = params[:message] || build_name
19+
branch = params[:branch] || sh('git', 'rev-parse', '--abbrev-ref', 'HEAD').strip
20+
environment = params[:environment] || {}
21+
buildkite_pipeline_slug = params[:buildkite_pipeline_slug]
22+
async = params[:async]
23+
label = params[:label] || ":buildkite: Trigger #{build_name} on #{branch}"
24+
depends_on = params[:depends_on]&.then { |v| v.empty? ? nil : Array(v) }
25+
26+
# Add the PIPELINE environment variable to the environment hash
27+
environment = environment.merge('PIPELINE' => pipeline_file)
28+
29+
# Create the trigger step YAML
30+
trigger_yaml = {
31+
'steps' => [
32+
{
33+
'trigger' => buildkite_pipeline_slug,
34+
'label' => label,
35+
'async' => async,
36+
'build' => {
37+
'branch' => branch,
38+
'message' => message,
39+
'env' => environment
40+
},
41+
'depends_on' => depends_on
42+
}.compact,
43+
]
44+
}.to_yaml
45+
46+
# Use buildkite-agent to upload the pipeline
47+
_stdout, stderr, status = Open3.capture3('buildkite-agent', 'pipeline', 'upload', stdin_data: trigger_yaml)
48+
49+
# Check for errors
50+
UI.user_error!("Failed to upload pipeline: #{stderr}") unless status.success?
51+
52+
# Log success
53+
UI.success("Added a trigger step to the current Buildkite build to start a new build for #{pipeline_file} on branch #{branch}")
54+
end
55+
56+
def self.description
57+
'Adds a trigger step to the current Buildkite pipeline to start a new build from this one'
58+
end
59+
60+
def self.details
61+
<<~DETAILS
62+
This action adds a `trigger` step to the current Buildkite build, to start a separate build from the current one.
63+
64+
This is slightly different from `buildkite-agent pipeline upload`-ing the YAML steps of the build directly to the current build,
65+
as this approach ensures the triggered build starts from a clean and independent context.
66+
- This is particularly important if the build being triggered rely on the fact that the current build pushed new commits
67+
to the Git branch and we want the new build's steps to start from the new commit.
68+
- This is also necessary for cases where we run builds on a mirror of the original Git repo (e.g. for pipelines of repos hosted
69+
in a private GitHub Enterprise server, mirrored on GitHub.com for CI building purposes) and we want the new build being triggered
70+
to initiate a new sync of the Git repo as part of the CI build bootstrap process, to get the latest commits.
71+
DETAILS
72+
end
73+
74+
def self.available_options
75+
[
76+
FastlaneCore::ConfigItem.new(
77+
key: :buildkite_pipeline_slug,
78+
env_name: 'BUILDKITE_PIPELINE_SLUG',
79+
description: 'The slug of the Buildkite pipeline to trigger. Defaults to the same slug as the current pipeline, so usually not necessary to provide explicitly',
80+
type: String,
81+
optional: false # But most likely to be auto-provided by the ENV var of the current build and thus not needed to be provided explicitly
82+
),
83+
FastlaneCore::ConfigItem.new(
84+
key: :label,
85+
description: 'Custom label for the trigger step. If not provided, defaults to ":buildkite: Trigger {`pipeline_file`\'s basename} on {branch}"',
86+
type: String,
87+
optional: true
88+
),
89+
FastlaneCore::ConfigItem.new(
90+
key: :pipeline_file,
91+
description: 'The path (relative to `.buildkite/`) to the pipeline YAML file to use for the triggered build',
92+
type: String,
93+
optional: false
94+
),
95+
FastlaneCore::ConfigItem.new(
96+
key: :branch,
97+
description: 'The branch to trigger the build on. Defaults to the Git branch currently checked out at the time of running the action (which is not necessarily the same as the `BUILDKITE_BRANCH` the current build initially started on)',
98+
type: String,
99+
optional: true
100+
),
101+
FastlaneCore::ConfigItem.new(
102+
key: :message,
103+
description: 'The message / title to use for the triggered build. If not provided, defaults to the `pipeline_file`\'s basename',
104+
type: String,
105+
optional: true
106+
),
107+
FastlaneCore::ConfigItem.new(
108+
key: :environment,
109+
description: 'Environment variables to pass to the triggered build (in addition to the PIPELINE={pipeline_file} that will be automatically injected)',
110+
type: Hash,
111+
default_value: {},
112+
optional: true
113+
),
114+
FastlaneCore::ConfigItem.new(
115+
key: :async,
116+
description: 'Whether to trigger the build asynchronously (true) or wait for it to complete (false). Defaults to false',
117+
type: Boolean,
118+
default_value: false
119+
),
120+
FastlaneCore::ConfigItem.new(
121+
key: :depends_on,
122+
env_name: 'BUILDKITE_STEP_KEY', # This is the env var that Buildkite sets for the current step
123+
description: 'The steps to depend on before triggering the build. Defaults to the current step this action is called from, if said step has a `key:` attribute set. Use an empty array to explicitly not depend on any step even if the current step has a `key`',
124+
type: Array,
125+
default_value: [],
126+
optional: true
127+
),
128+
]
129+
end
130+
131+
def self.return_value
132+
'Returns true if the pipeline was successfully uploaded. Throws a `user_error!` if it failed to upload the `trigger` step to the current build'
133+
end
134+
135+
def self.authors
136+
['Automattic']
137+
end
138+
139+
def self.is_supported?(platform)
140+
true
141+
end
142+
143+
def self.example_code
144+
[
145+
<<~CODE,
146+
# Use default/inferred values for most parameters
147+
buildkite_add_trigger_step(
148+
pipeline_file: "release-build.yml",
149+
environment: { "RELEASE_VERSION" => "1.2.3" },
150+
)
151+
CODE
152+
<<~CODE,
153+
# Use custom values for most parameters
154+
buildkite_add_trigger_step(
155+
label: "🚀 Trigger Release Build",
156+
pipeline_file: "release-build.yml",
157+
branch: "release/1.2.3",
158+
message: "Release Build (123)",
159+
environment: { "RELEASE_VERSION" => "1.2.3" },
160+
async: false,
161+
)
162+
CODE
163+
]
164+
end
165+
166+
def self.category
167+
:building
168+
end
169+
end
170+
end
171+
end

0 commit comments

Comments
 (0)