Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,19 @@ local pipedream_config = {
stage: 'example_stage',
# The elastic agent profile to run the rollback pipeline as
elastic_profile_id: 'example_profile',
# (Optional) The stage on the source-of-truth pipeline that the rollback
# material watches. Defaults to that pipeline's final stage
# (`pipeline-complete`). Set to an earlier stage (e.g. 'deploy-primary')
# so a SHA becomes rollback-eligible without waiting for the trailing
# noop stage.
# final_stage: 'deploy-primary',
# (Optional) Which group pipeline is the source of truth for rollback
# revisions. Defaults to the last group in the chain (e.g. `st`). Set to a
# group name (e.g. 'us') to anchor rollback eligibility on an earlier group
# when the tail group is flaky and would otherwise starve the rollback
# target pool. Trade-off: an upstream-anchored target may not have been
# validated on the downstream groups the rollback then deploys it to.
# final_pipeline: 'us',
},

# Set to true to auto-deploy changes (defaults to true)
Expand Down
17 changes: 16 additions & 1 deletion libs/pipedream.libsonnet
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,22 @@ local pipedream_trigger_pipeline(pipedream_config) =
local pipedream_rollback_pipeline(pipedream_config, service_pipelines, trigger_pipeline) =
if std.objectHas(pipedream_config, 'rollback') then
local name = pipedream_config.name;
local final_pipeline = service_pipelines[std.length(service_pipelines) - 1];
// The rollback material's "source of truth" pipeline. By default this is
// the last pipeline in the chain (the final group, e.g. `st`), so a SHA is
// only rollback-eligible once it has deployed all the way through. Set
// `rollback.final_pipeline` to a group name (e.g. 'us') to anchor rollback
// eligibility on an earlier group instead — useful when the tail group is
// flaky and would otherwise starve the rollback target pool. Note the
// trade-off: anchoring upstream means a rollback target may not have been
// validated on the downstream groups it then gets deployed to.
local final_pipeline =
if std.objectHas(pipedream_config.rollback, 'final_pipeline') then
local target = pipeline_name(name, pipedream_config.rollback.final_pipeline);
local matches = std.filter(function(p) p.name == target, service_pipelines);
assert std.length(matches) > 0 : "Rollback final_pipeline '" + target + "' not found in service pipelines";
matches[0]
else
service_pipelines[std.length(service_pipelines) - 1];

// Rollbacks work by calling two devinfra-deployment-infra scripts:
// gocd-pause-and-cancel-pipelines
Expand Down
34 changes: 34 additions & 0 deletions test/pipedream.js
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,40 @@ test("rollback: invalid final stage errors", (t) => {
);
});

test("rollback: final_pipeline anchors material on an earlier group", async (t) => {
const got = await render_fixture(
"pipedream/rollback-final-pipeline-override.jsonnet",
false,
);

const r = got.pipelines["rollback-example"];
t.truthy(r);
// Material watches the `us` group's final stage, not the default last group.
t.truthy(r.materials["deploy-example-us-pipeline-complete"]);
t.is(
r.materials["deploy-example-us-pipeline-complete"].pipeline,
"deploy-example-us",
);
t.is(
r.materials["deploy-example-us-pipeline-complete"].stage,
"pipeline-complete",
);
// Rollback still re-runs every region pipeline.
t.truthy(
r.environment_variables.REGION_PIPELINE_FLAGS.includes("deploy-example-st"),
);
});

test("rollback: unknown final_pipeline errors", (t) => {
const err = t.throws(() =>
get_fixture_content(
"pipedream/rollback-bad-final-pipeline.failing.jsonnet",
false,
),
);
t.true(err.message.includes("not found in service pipelines"));
});

test("conflicting stage properties across regions errors", (t) => {
const err = t.throws(() =>
get_fixture_content(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
local pipedream = import '../../../../libs/pipedream.libsonnet';

// final_pipeline names a group that doesn't exist -> should error at compile
// time rather than producing a rollback pipeline with a dangling material.

local pipedream_config = {
name: 'example',
auto_deploy: true,
rollback: {
material_name: 'example_repo',
stage: 'deploy',
elastic_profile_id: 'example',
final_pipeline: 'this-group-does-not-exist',
},
};

local sample = {
pipeline(region):: {
materials: {
example_repo: {
git: 'git@github.com:getsentry/example.git',
branch: 'master',
destination: 'example',
},
},
stages: [
{
deploy: {
jobs: {
deploy: {
elastic_profile_id: 'example',
tasks: [
{ script: './deploy.sh --region=' + region },
],
},
},
},
},
],
},
};

pipedream.render(pipedream_config, sample.pipeline)
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
local pipedream = import '../../../../libs/pipedream.libsonnet';

// Anchor the rollback material on the `us` group instead of the default last
// group (`st`). A SHA becomes rollback-eligible once `us` reaches its final
// stage, so a flaky tail group no longer starves the rollback target pool.

local pipedream_config = {
name: 'example',
auto_deploy: true,
rollback: {
material_name: 'example_repo',
stage: 'deploy',
elastic_profile_id: 'example',
final_pipeline: 'us',
},
};

local sample = {
pipeline(region):: {
materials: {
example_repo: {
git: 'git@github.com:getsentry/example.git',
branch: 'master',
destination: 'example',
},
},
stages: [
{
deploy: {
jobs: {
deploy: {
elastic_profile_id: 'example',
tasks: [
{ script: './deploy.sh --region=' + region },
],
},
},
},
},
],
},
};

pipedream.render(pipedream_config, sample.pipeline)
Loading