diff --git a/README.md b/README.md index 0c2d6fd..125cf6c 100644 --- a/README.md +++ b/README.md @@ -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) diff --git a/libs/bash/rollback.sh b/libs/bash/rollback.sh index 32fbf89..c78bc4e 100755 --- a/libs/bash/rollback.sh +++ b/libs/bash/rollback.sh @@ -1,9 +1,9 @@ #!/bin/bash -# Note: $REGION_PIPELINE_FLAGS has no quoting, for word expansion +# Note: $ALLOW_MISSING_FLAGS and $REGION_PIPELINE_FLAGS have no quoting, for word expansion # shellcheck disable=SC2086 -if [[ "${REGION_PIPELINE_FLAGS:-}" ]]; then - set -- $REGION_PIPELINE_FLAGS +if [[ "${REGION_PIPELINE_FLAGS:-}" || "${ALLOW_MISSING_FLAGS:-}" ]]; then + set -- $ALLOW_MISSING_FLAGS $REGION_PIPELINE_FLAGS fi # The user that triggered the rollback diff --git a/libs/pipedream.libsonnet b/libs/pipedream.libsonnet index 3c4434d..49c5111 100644 --- a/libs/pipedream.libsonnet +++ b/libs/pipedream.libsonnet @@ -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 @@ -154,6 +169,10 @@ local pipedream_rollback_pipeline(pipedream_config, service_pipelines, trigger_p region_pipeline_flags else region_pipeline_flags + ' --pipeline=' + trigger_pipeline.name; + local allow_missing_flags = if std.objectHas(pipedream_config.rollback, 'final_pipeline') then + '--allow-missing' + else + ''; // If we ever change the final stage in pipedream (i.e. add or remove a // final stage) we may want the material for the rollback pipeline to look @@ -182,6 +201,7 @@ local pipedream_rollback_pipeline(pipedream_config, service_pipelines, trigger_p ROLLBACK_STAGE: pipedream_config.rollback.stage, REGION_PIPELINE_FLAGS: region_pipeline_flags, ALL_PIPELINE_FLAGS: all_pipeline_flags, + ALLOW_MISSING_FLAGS: allow_missing_flags, TRIGGERED_BY: '', }, materials: { diff --git a/test/pipedream.js b/test/pipedream.js index 109ac59..ea508e7 100644 --- a/test/pipedream.js +++ b/test/pipedream.js @@ -150,6 +150,41 @@ 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"), + ); + t.is(r.environment_variables.ALLOW_MISSING_FLAGS, "--allow-missing"); +}); + +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( diff --git a/test/testdata/fixtures/pipedream/rollback-bad-final-pipeline.failing.jsonnet b/test/testdata/fixtures/pipedream/rollback-bad-final-pipeline.failing.jsonnet new file mode 100644 index 0000000..9545972 --- /dev/null +++ b/test/testdata/fixtures/pipedream/rollback-bad-final-pipeline.failing.jsonnet @@ -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) diff --git a/test/testdata/fixtures/pipedream/rollback-final-pipeline-override.jsonnet b/test/testdata/fixtures/pipedream/rollback-final-pipeline-override.jsonnet new file mode 100644 index 0000000..23e98b2 --- /dev/null +++ b/test/testdata/fixtures/pipedream/rollback-final-pipeline-override.jsonnet @@ -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) diff --git a/test/testdata/goldens/pipedream/rollback-final-pipeline-override.jsonnet_output-files.golden b/test/testdata/goldens/pipedream/rollback-final-pipeline-override.jsonnet_output-files.golden new file mode 100644 index 0000000..229089b --- /dev/null +++ b/test/testdata/goldens/pipedream/rollback-final-pipeline-override.jsonnet_output-files.golden @@ -0,0 +1,372 @@ +{ + "deploy-example-de.yaml": { + "format_version": 10, + "pipelines": { + "deploy-example-de": { + "display_order": 3, + "environment_variables": { + "PIPEDREAM_GROUP_REGIONS": "de" + }, + "group": "example", + "materials": { + "deploy-example-s4s2-pipeline-complete": { + "pipeline": "deploy-example-s4s2", + "stage": "pipeline-complete" + }, + "example_repo": { + "branch": "master", + "destination": "example", + "git": "git@github.com:getsentry/example.git" + } + }, + "stages": [ + { + "deploy": { + "jobs": { + "deploy": { + "elastic_profile_id": "example", + "tasks": [ + { + "script": "if [ -n \"${PIPEDREAM_GROUP_REGIONS:-}\" ] && [ -n \"${SENTRY_REGION:-}\" ]; then\n case \",${PIPEDREAM_GROUP_REGIONS},\" in\n *\",${SENTRY_REGION},\"*) ;;\n *)\n echo \"Skipping $SENTRY_REGION (not in PIPEDREAM_GROUP_REGIONS=$PIPEDREAM_GROUP_REGIONS)\"\n exit 0\n ;;\n esac\nfi\n./deploy.sh --region=de" + } + ] + } + } + } + }, + { + "pipeline-complete": { + "fetch_materials": false, + "jobs": { + "pipeline-complete": { + "tasks": [ + { + "exec": { + "command": true + } + } + ] + } + } + } + } + ] + } + } + }, + "deploy-example-s4s2.yaml": { + "format_version": 10, + "pipelines": { + "deploy-example-s4s2": { + "display_order": 2, + "environment_variables": { + "PIPEDREAM_GROUP_REGIONS": "s4s2" + }, + "group": "example", + "materials": { + "example_repo": { + "branch": "master", + "destination": "example", + "git": "git@github.com:getsentry/example.git" + } + }, + "stages": [ + { + "deploy": { + "jobs": { + "deploy": { + "elastic_profile_id": "example", + "tasks": [ + { + "script": "if [ -n \"${PIPEDREAM_GROUP_REGIONS:-}\" ] && [ -n \"${SENTRY_REGION:-}\" ]; then\n case \",${PIPEDREAM_GROUP_REGIONS},\" in\n *\",${SENTRY_REGION},\"*) ;;\n *)\n echo \"Skipping $SENTRY_REGION (not in PIPEDREAM_GROUP_REGIONS=$PIPEDREAM_GROUP_REGIONS)\"\n exit 0\n ;;\n esac\nfi\n./deploy.sh --region=s4s2" + } + ] + } + } + } + }, + { + "pipeline-complete": { + "fetch_materials": false, + "jobs": { + "pipeline-complete": { + "tasks": [ + { + "exec": { + "command": true + } + } + ] + } + } + } + } + ] + } + } + }, + "deploy-example-st.yaml": { + "format_version": 10, + "pipelines": { + "deploy-example-st": { + "display_order": 6, + "environment_variables": { + "PIPEDREAM_GROUP_REGIONS": "customer-1,customer-2,customer-4,customer-7" + }, + "group": "example", + "materials": { + "deploy-example-us2-pipeline-complete": { + "pipeline": "deploy-example-us2", + "stage": "pipeline-complete" + }, + "example_repo": { + "branch": "master", + "destination": "example", + "git": "git@github.com:getsentry/example.git" + } + }, + "stages": [ + { + "deploy": { + "jobs": { + "deploy-customer-1": { + "elastic_profile_id": "example", + "tasks": [ + { + "script": "if [ -n \"${PIPEDREAM_GROUP_REGIONS:-}\" ] && [ -n \"${SENTRY_REGION:-}\" ]; then\n case \",${PIPEDREAM_GROUP_REGIONS},\" in\n *\",${SENTRY_REGION},\"*) ;;\n *)\n echo \"Skipping $SENTRY_REGION (not in PIPEDREAM_GROUP_REGIONS=$PIPEDREAM_GROUP_REGIONS)\"\n exit 0\n ;;\n esac\nfi\n./deploy.sh --region=customer-1" + } + ] + }, + "deploy-customer-2": { + "elastic_profile_id": "example", + "tasks": [ + { + "script": "if [ -n \"${PIPEDREAM_GROUP_REGIONS:-}\" ] && [ -n \"${SENTRY_REGION:-}\" ]; then\n case \",${PIPEDREAM_GROUP_REGIONS},\" in\n *\",${SENTRY_REGION},\"*) ;;\n *)\n echo \"Skipping $SENTRY_REGION (not in PIPEDREAM_GROUP_REGIONS=$PIPEDREAM_GROUP_REGIONS)\"\n exit 0\n ;;\n esac\nfi\n./deploy.sh --region=customer-2" + } + ] + }, + "deploy-customer-4": { + "elastic_profile_id": "example", + "tasks": [ + { + "script": "if [ -n \"${PIPEDREAM_GROUP_REGIONS:-}\" ] && [ -n \"${SENTRY_REGION:-}\" ]; then\n case \",${PIPEDREAM_GROUP_REGIONS},\" in\n *\",${SENTRY_REGION},\"*) ;;\n *)\n echo \"Skipping $SENTRY_REGION (not in PIPEDREAM_GROUP_REGIONS=$PIPEDREAM_GROUP_REGIONS)\"\n exit 0\n ;;\n esac\nfi\n./deploy.sh --region=customer-4" + } + ] + }, + "deploy-customer-7": { + "elastic_profile_id": "example", + "tasks": [ + { + "script": "if [ -n \"${PIPEDREAM_GROUP_REGIONS:-}\" ] && [ -n \"${SENTRY_REGION:-}\" ]; then\n case \",${PIPEDREAM_GROUP_REGIONS},\" in\n *\",${SENTRY_REGION},\"*) ;;\n *)\n echo \"Skipping $SENTRY_REGION (not in PIPEDREAM_GROUP_REGIONS=$PIPEDREAM_GROUP_REGIONS)\"\n exit 0\n ;;\n esac\nfi\n./deploy.sh --region=customer-7" + } + ] + } + } + } + }, + { + "pipeline-complete": { + "fetch_materials": false, + "jobs": { + "pipeline-complete": { + "tasks": [ + { + "exec": { + "command": true + } + } + ] + } + } + } + } + ] + } + } + }, + "deploy-example-us.yaml": { + "format_version": 10, + "pipelines": { + "deploy-example-us": { + "display_order": 4, + "environment_variables": { + "PIPEDREAM_GROUP_REGIONS": "us" + }, + "group": "example", + "materials": { + "deploy-example-de-pipeline-complete": { + "pipeline": "deploy-example-de", + "stage": "pipeline-complete" + }, + "example_repo": { + "branch": "master", + "destination": "example", + "git": "git@github.com:getsentry/example.git" + } + }, + "stages": [ + { + "deploy": { + "jobs": { + "deploy": { + "elastic_profile_id": "example", + "tasks": [ + { + "script": "if [ -n \"${PIPEDREAM_GROUP_REGIONS:-}\" ] && [ -n \"${SENTRY_REGION:-}\" ]; then\n case \",${PIPEDREAM_GROUP_REGIONS},\" in\n *\",${SENTRY_REGION},\"*) ;;\n *)\n echo \"Skipping $SENTRY_REGION (not in PIPEDREAM_GROUP_REGIONS=$PIPEDREAM_GROUP_REGIONS)\"\n exit 0\n ;;\n esac\nfi\n./deploy.sh --region=us" + } + ] + } + } + } + }, + { + "pipeline-complete": { + "fetch_materials": false, + "jobs": { + "pipeline-complete": { + "tasks": [ + { + "exec": { + "command": true + } + } + ] + } + } + } + } + ] + } + } + }, + "deploy-example-us2.yaml": { + "format_version": 10, + "pipelines": { + "deploy-example-us2": { + "display_order": 5, + "environment_variables": { + "PIPEDREAM_GROUP_REGIONS": "us2" + }, + "group": "example", + "materials": { + "deploy-example-us-pipeline-complete": { + "pipeline": "deploy-example-us", + "stage": "pipeline-complete" + }, + "example_repo": { + "branch": "master", + "destination": "example", + "git": "git@github.com:getsentry/example.git" + } + }, + "stages": [ + { + "deploy": { + "jobs": { + "deploy": { + "elastic_profile_id": "example", + "tasks": [ + { + "script": "if [ -n \"${PIPEDREAM_GROUP_REGIONS:-}\" ] && [ -n \"${SENTRY_REGION:-}\" ]; then\n case \",${PIPEDREAM_GROUP_REGIONS},\" in\n *\",${SENTRY_REGION},\"*) ;;\n *)\n echo \"Skipping $SENTRY_REGION (not in PIPEDREAM_GROUP_REGIONS=$PIPEDREAM_GROUP_REGIONS)\"\n exit 0\n ;;\n esac\nfi\n./deploy.sh --region=us2" + } + ] + } + } + } + }, + { + "pipeline-complete": { + "fetch_materials": false, + "jobs": { + "pipeline-complete": { + "tasks": [ + { + "exec": { + "command": true + } + } + ] + } + } + } + } + ] + } + } + }, + "rollback-example.yaml": { + "format_version": 10, + "pipelines": { + "rollback-example": { + "display_order": 1, + "environment_variables": { + "ALLOW_MISSING_FLAGS": "--allow-missing", + "ALL_PIPELINE_FLAGS": "--pipeline=deploy-example-s4s2 --pipeline=deploy-example-de --pipeline=deploy-example-us --pipeline=deploy-example-us2 --pipeline=deploy-example-st", + "GOCD_ACCESS_TOKEN": "{{SECRET:[devinfra][gocd_access_token]}}", + "REGION_PIPELINE_FLAGS": "--pipeline=deploy-example-s4s2 --pipeline=deploy-example-de --pipeline=deploy-example-us --pipeline=deploy-example-us2 --pipeline=deploy-example-st", + "ROLLBACK_MATERIAL_NAME": "example_repo", + "ROLLBACK_STAGE": "deploy", + "TRIGGERED_BY": "" + }, + "group": "example", + "lock_behavior": "unlockWhenFinished", + "materials": { + "deploy-example-us-pipeline-complete": { + "pipeline": "deploy-example-us", + "stage": "pipeline-complete" + } + }, + "stages": [ + { + "pause_pipelines": { + "approval": { + "type": "manual" + }, + "jobs": { + "rollback": { + "elastic_profile_id": "example", + "tasks": [ + { + "script": "##!/bin/bash\n\n## Note: $ALL_PIPELINE_FLAGS has no quoting, for word expansion\n## shellcheck disable=SC2086\nif [[ \"${ALL_PIPELINE_FLAGS:-}\" ]]; then\n set -- $ALL_PIPELINE_FLAGS\nfi\n\n## The user that triggered the rollback\nTRIGGERED_BY=\"${TRIGGERED_BY:-}\"\n\npause_message='This pipeline is being rolled back, please check with team before un-pausing.'\n\n## Include triggered by in the pause message if it is not empty\nif [ -n \"$TRIGGERED_BY\" ]; then\n pause_message=\"$pause_message Triggered by: $TRIGGERED_BY\"\nfi\n\n## Pause all pipelines in the pipedream\ngocd-pause-and-cancel-pipelines \\\n --pause-message=\"$pause_message\" \\\n \"$@\"\n" + } + ] + } + } + } + }, + { + "start_rollback": { + "jobs": { + "rollback": { + "elastic_profile_id": "example", + "tasks": [ + { + "script": "##!/bin/bash\n\n## Note: $ALLOW_MISSING_FLAGS and $REGION_PIPELINE_FLAGS have no quoting, for word expansion\n## shellcheck disable=SC2086\nif [[ \"${REGION_PIPELINE_FLAGS:-}\" || \"${ALLOW_MISSING_FLAGS:-}\" ]]; then\n set -- $ALLOW_MISSING_FLAGS $REGION_PIPELINE_FLAGS\nfi\n\n## The user that triggered the rollback\nTRIGGERED_BY=\"${TRIGGERED_BY:-}\"\n\npause_message='This pipeline was rolled back, please check with team before un-pausing.'\n\n## Include triggered by in the pause message if it is not empty\nif [ -n \"$TRIGGERED_BY\" ]; then\n pause_message=\"$pause_message Triggered by: $TRIGGERED_BY\"\nfi\n\n## Get sha from the given pipeline run to deploy to all pipedream pipelines.\nsha=$(gocd-sha-for-pipeline --material-name=\"${ROLLBACK_MATERIAL_NAME}\")\n\necho \"📑 Rolling back to sha: ${sha}\"\n\ngocd-emergency-deploy \\\n --material-name=\"${ROLLBACK_MATERIAL_NAME}\" \\\n --commit-sha=\"${sha}\" \\\n --deploy-stage=\"${ROLLBACK_STAGE}\" \\\n --pause-message=\"$pause_message\" \\\n \"$@\"\n" + } + ] + } + } + } + }, + { + "incident_resolved": { + "approval": { + "type": "manual" + }, + "jobs": { + "rollback": { + "elastic_profile_id": "example", + "tasks": [ + { + "script": "##!/bin/bash\n\n## Note: $ALL_PIPELINE_FLAGS has no quoting, for word expansion\n## shellcheck disable=SC2086\nif [[ \"${ALL_PIPELINE_FLAGS:-}\" ]]; then\n set -- $ALL_PIPELINE_FLAGS\nfi\n\n## Unpause and unlock all pipelines in the pipedream\ngocd-unpause-and-unlock-pipelines \\\n \"$@\"\n" + } + ] + } + } + } + } + ] + } + } + } +} diff --git a/test/testdata/goldens/pipedream/rollback-final-pipeline-override.jsonnet_single-file.golden b/test/testdata/goldens/pipedream/rollback-final-pipeline-override.jsonnet_single-file.golden new file mode 100644 index 0000000..5d2cd33 --- /dev/null +++ b/test/testdata/goldens/pipedream/rollback-final-pipeline-override.jsonnet_single-file.golden @@ -0,0 +1,345 @@ +{ + "format_version": 10, + "pipelines": { + "deploy-example-de": { + "display_order": 3, + "environment_variables": { + "PIPEDREAM_GROUP_REGIONS": "de" + }, + "group": "example", + "materials": { + "deploy-example-s4s2-pipeline-complete": { + "pipeline": "deploy-example-s4s2", + "stage": "pipeline-complete" + }, + "example_repo": { + "branch": "master", + "destination": "example", + "git": "git@github.com:getsentry/example.git" + } + }, + "stages": [ + { + "deploy": { + "jobs": { + "deploy": { + "elastic_profile_id": "example", + "tasks": [ + { + "script": "if [ -n \"${PIPEDREAM_GROUP_REGIONS:-}\" ] && [ -n \"${SENTRY_REGION:-}\" ]; then\n case \",${PIPEDREAM_GROUP_REGIONS},\" in\n *\",${SENTRY_REGION},\"*) ;;\n *)\n echo \"Skipping $SENTRY_REGION (not in PIPEDREAM_GROUP_REGIONS=$PIPEDREAM_GROUP_REGIONS)\"\n exit 0\n ;;\n esac\nfi\n./deploy.sh --region=de" + } + ] + } + } + } + }, + { + "pipeline-complete": { + "fetch_materials": false, + "jobs": { + "pipeline-complete": { + "tasks": [ + { + "exec": { + "command": true + } + } + ] + } + } + } + } + ] + }, + "deploy-example-s4s2": { + "display_order": 2, + "environment_variables": { + "PIPEDREAM_GROUP_REGIONS": "s4s2" + }, + "group": "example", + "materials": { + "example_repo": { + "branch": "master", + "destination": "example", + "git": "git@github.com:getsentry/example.git" + } + }, + "stages": [ + { + "deploy": { + "jobs": { + "deploy": { + "elastic_profile_id": "example", + "tasks": [ + { + "script": "if [ -n \"${PIPEDREAM_GROUP_REGIONS:-}\" ] && [ -n \"${SENTRY_REGION:-}\" ]; then\n case \",${PIPEDREAM_GROUP_REGIONS},\" in\n *\",${SENTRY_REGION},\"*) ;;\n *)\n echo \"Skipping $SENTRY_REGION (not in PIPEDREAM_GROUP_REGIONS=$PIPEDREAM_GROUP_REGIONS)\"\n exit 0\n ;;\n esac\nfi\n./deploy.sh --region=s4s2" + } + ] + } + } + } + }, + { + "pipeline-complete": { + "fetch_materials": false, + "jobs": { + "pipeline-complete": { + "tasks": [ + { + "exec": { + "command": true + } + } + ] + } + } + } + } + ] + }, + "deploy-example-st": { + "display_order": 6, + "environment_variables": { + "PIPEDREAM_GROUP_REGIONS": "customer-1,customer-2,customer-4,customer-7" + }, + "group": "example", + "materials": { + "deploy-example-us2-pipeline-complete": { + "pipeline": "deploy-example-us2", + "stage": "pipeline-complete" + }, + "example_repo": { + "branch": "master", + "destination": "example", + "git": "git@github.com:getsentry/example.git" + } + }, + "stages": [ + { + "deploy": { + "jobs": { + "deploy-customer-1": { + "elastic_profile_id": "example", + "tasks": [ + { + "script": "if [ -n \"${PIPEDREAM_GROUP_REGIONS:-}\" ] && [ -n \"${SENTRY_REGION:-}\" ]; then\n case \",${PIPEDREAM_GROUP_REGIONS},\" in\n *\",${SENTRY_REGION},\"*) ;;\n *)\n echo \"Skipping $SENTRY_REGION (not in PIPEDREAM_GROUP_REGIONS=$PIPEDREAM_GROUP_REGIONS)\"\n exit 0\n ;;\n esac\nfi\n./deploy.sh --region=customer-1" + } + ] + }, + "deploy-customer-2": { + "elastic_profile_id": "example", + "tasks": [ + { + "script": "if [ -n \"${PIPEDREAM_GROUP_REGIONS:-}\" ] && [ -n \"${SENTRY_REGION:-}\" ]; then\n case \",${PIPEDREAM_GROUP_REGIONS},\" in\n *\",${SENTRY_REGION},\"*) ;;\n *)\n echo \"Skipping $SENTRY_REGION (not in PIPEDREAM_GROUP_REGIONS=$PIPEDREAM_GROUP_REGIONS)\"\n exit 0\n ;;\n esac\nfi\n./deploy.sh --region=customer-2" + } + ] + }, + "deploy-customer-4": { + "elastic_profile_id": "example", + "tasks": [ + { + "script": "if [ -n \"${PIPEDREAM_GROUP_REGIONS:-}\" ] && [ -n \"${SENTRY_REGION:-}\" ]; then\n case \",${PIPEDREAM_GROUP_REGIONS},\" in\n *\",${SENTRY_REGION},\"*) ;;\n *)\n echo \"Skipping $SENTRY_REGION (not in PIPEDREAM_GROUP_REGIONS=$PIPEDREAM_GROUP_REGIONS)\"\n exit 0\n ;;\n esac\nfi\n./deploy.sh --region=customer-4" + } + ] + }, + "deploy-customer-7": { + "elastic_profile_id": "example", + "tasks": [ + { + "script": "if [ -n \"${PIPEDREAM_GROUP_REGIONS:-}\" ] && [ -n \"${SENTRY_REGION:-}\" ]; then\n case \",${PIPEDREAM_GROUP_REGIONS},\" in\n *\",${SENTRY_REGION},\"*) ;;\n *)\n echo \"Skipping $SENTRY_REGION (not in PIPEDREAM_GROUP_REGIONS=$PIPEDREAM_GROUP_REGIONS)\"\n exit 0\n ;;\n esac\nfi\n./deploy.sh --region=customer-7" + } + ] + } + } + } + }, + { + "pipeline-complete": { + "fetch_materials": false, + "jobs": { + "pipeline-complete": { + "tasks": [ + { + "exec": { + "command": true + } + } + ] + } + } + } + } + ] + }, + "deploy-example-us": { + "display_order": 4, + "environment_variables": { + "PIPEDREAM_GROUP_REGIONS": "us" + }, + "group": "example", + "materials": { + "deploy-example-de-pipeline-complete": { + "pipeline": "deploy-example-de", + "stage": "pipeline-complete" + }, + "example_repo": { + "branch": "master", + "destination": "example", + "git": "git@github.com:getsentry/example.git" + } + }, + "stages": [ + { + "deploy": { + "jobs": { + "deploy": { + "elastic_profile_id": "example", + "tasks": [ + { + "script": "if [ -n \"${PIPEDREAM_GROUP_REGIONS:-}\" ] && [ -n \"${SENTRY_REGION:-}\" ]; then\n case \",${PIPEDREAM_GROUP_REGIONS},\" in\n *\",${SENTRY_REGION},\"*) ;;\n *)\n echo \"Skipping $SENTRY_REGION (not in PIPEDREAM_GROUP_REGIONS=$PIPEDREAM_GROUP_REGIONS)\"\n exit 0\n ;;\n esac\nfi\n./deploy.sh --region=us" + } + ] + } + } + } + }, + { + "pipeline-complete": { + "fetch_materials": false, + "jobs": { + "pipeline-complete": { + "tasks": [ + { + "exec": { + "command": true + } + } + ] + } + } + } + } + ] + }, + "deploy-example-us2": { + "display_order": 5, + "environment_variables": { + "PIPEDREAM_GROUP_REGIONS": "us2" + }, + "group": "example", + "materials": { + "deploy-example-us-pipeline-complete": { + "pipeline": "deploy-example-us", + "stage": "pipeline-complete" + }, + "example_repo": { + "branch": "master", + "destination": "example", + "git": "git@github.com:getsentry/example.git" + } + }, + "stages": [ + { + "deploy": { + "jobs": { + "deploy": { + "elastic_profile_id": "example", + "tasks": [ + { + "script": "if [ -n \"${PIPEDREAM_GROUP_REGIONS:-}\" ] && [ -n \"${SENTRY_REGION:-}\" ]; then\n case \",${PIPEDREAM_GROUP_REGIONS},\" in\n *\",${SENTRY_REGION},\"*) ;;\n *)\n echo \"Skipping $SENTRY_REGION (not in PIPEDREAM_GROUP_REGIONS=$PIPEDREAM_GROUP_REGIONS)\"\n exit 0\n ;;\n esac\nfi\n./deploy.sh --region=us2" + } + ] + } + } + } + }, + { + "pipeline-complete": { + "fetch_materials": false, + "jobs": { + "pipeline-complete": { + "tasks": [ + { + "exec": { + "command": true + } + } + ] + } + } + } + } + ] + }, + "rollback-example": { + "display_order": 1, + "environment_variables": { + "ALLOW_MISSING_FLAGS": "--allow-missing", + "ALL_PIPELINE_FLAGS": "--pipeline=deploy-example-s4s2 --pipeline=deploy-example-de --pipeline=deploy-example-us --pipeline=deploy-example-us2 --pipeline=deploy-example-st", + "GOCD_ACCESS_TOKEN": "{{SECRET:[devinfra][gocd_access_token]}}", + "REGION_PIPELINE_FLAGS": "--pipeline=deploy-example-s4s2 --pipeline=deploy-example-de --pipeline=deploy-example-us --pipeline=deploy-example-us2 --pipeline=deploy-example-st", + "ROLLBACK_MATERIAL_NAME": "example_repo", + "ROLLBACK_STAGE": "deploy", + "TRIGGERED_BY": "" + }, + "group": "example", + "lock_behavior": "unlockWhenFinished", + "materials": { + "deploy-example-us-pipeline-complete": { + "pipeline": "deploy-example-us", + "stage": "pipeline-complete" + } + }, + "stages": [ + { + "pause_pipelines": { + "approval": { + "type": "manual" + }, + "jobs": { + "rollback": { + "elastic_profile_id": "example", + "tasks": [ + { + "script": "##!/bin/bash\n\n## Note: $ALL_PIPELINE_FLAGS has no quoting, for word expansion\n## shellcheck disable=SC2086\nif [[ \"${ALL_PIPELINE_FLAGS:-}\" ]]; then\n set -- $ALL_PIPELINE_FLAGS\nfi\n\n## The user that triggered the rollback\nTRIGGERED_BY=\"${TRIGGERED_BY:-}\"\n\npause_message='This pipeline is being rolled back, please check with team before un-pausing.'\n\n## Include triggered by in the pause message if it is not empty\nif [ -n \"$TRIGGERED_BY\" ]; then\n pause_message=\"$pause_message Triggered by: $TRIGGERED_BY\"\nfi\n\n## Pause all pipelines in the pipedream\ngocd-pause-and-cancel-pipelines \\\n --pause-message=\"$pause_message\" \\\n \"$@\"\n" + } + ] + } + } + } + }, + { + "start_rollback": { + "jobs": { + "rollback": { + "elastic_profile_id": "example", + "tasks": [ + { + "script": "##!/bin/bash\n\n## Note: $ALLOW_MISSING_FLAGS and $REGION_PIPELINE_FLAGS have no quoting, for word expansion\n## shellcheck disable=SC2086\nif [[ \"${REGION_PIPELINE_FLAGS:-}\" || \"${ALLOW_MISSING_FLAGS:-}\" ]]; then\n set -- $ALLOW_MISSING_FLAGS $REGION_PIPELINE_FLAGS\nfi\n\n## The user that triggered the rollback\nTRIGGERED_BY=\"${TRIGGERED_BY:-}\"\n\npause_message='This pipeline was rolled back, please check with team before un-pausing.'\n\n## Include triggered by in the pause message if it is not empty\nif [ -n \"$TRIGGERED_BY\" ]; then\n pause_message=\"$pause_message Triggered by: $TRIGGERED_BY\"\nfi\n\n## Get sha from the given pipeline run to deploy to all pipedream pipelines.\nsha=$(gocd-sha-for-pipeline --material-name=\"${ROLLBACK_MATERIAL_NAME}\")\n\necho \"📑 Rolling back to sha: ${sha}\"\n\ngocd-emergency-deploy \\\n --material-name=\"${ROLLBACK_MATERIAL_NAME}\" \\\n --commit-sha=\"${sha}\" \\\n --deploy-stage=\"${ROLLBACK_STAGE}\" \\\n --pause-message=\"$pause_message\" \\\n \"$@\"\n" + } + ] + } + } + } + }, + { + "incident_resolved": { + "approval": { + "type": "manual" + }, + "jobs": { + "rollback": { + "elastic_profile_id": "example", + "tasks": [ + { + "script": "##!/bin/bash\n\n## Note: $ALL_PIPELINE_FLAGS has no quoting, for word expansion\n## shellcheck disable=SC2086\nif [[ \"${ALL_PIPELINE_FLAGS:-}\" ]]; then\n set -- $ALL_PIPELINE_FLAGS\nfi\n\n## Unpause and unlock all pipelines in the pipedream\ngocd-unpause-and-unlock-pipelines \\\n \"$@\"\n" + } + ] + } + } + } + } + ] + } + } +} diff --git a/test/testdata/goldens/pipedream/rollback-final-stage-override.jsonnet_output-files.golden b/test/testdata/goldens/pipedream/rollback-final-stage-override.jsonnet_output-files.golden index 46cbd20..51c2df0 100644 --- a/test/testdata/goldens/pipedream/rollback-final-stage-override.jsonnet_output-files.golden +++ b/test/testdata/goldens/pipedream/rollback-final-stage-override.jsonnet_output-files.golden @@ -300,6 +300,7 @@ "rollback-example": { "display_order": 1, "environment_variables": { + "ALLOW_MISSING_FLAGS": "", "ALL_PIPELINE_FLAGS": "--pipeline=deploy-example-s4s2 --pipeline=deploy-example-de --pipeline=deploy-example-us --pipeline=deploy-example-us2 --pipeline=deploy-example-st", "GOCD_ACCESS_TOKEN": "{{SECRET:[devinfra][gocd_access_token]}}", "REGION_PIPELINE_FLAGS": "--pipeline=deploy-example-s4s2 --pipeline=deploy-example-de --pipeline=deploy-example-us --pipeline=deploy-example-us2 --pipeline=deploy-example-st", @@ -340,7 +341,7 @@ "elastic_profile_id": "example", "tasks": [ { - "script": "##!/bin/bash\n\n## Note: $REGION_PIPELINE_FLAGS has no quoting, for word expansion\n## shellcheck disable=SC2086\nif [[ \"${REGION_PIPELINE_FLAGS:-}\" ]]; then\n set -- $REGION_PIPELINE_FLAGS\nfi\n\n## The user that triggered the rollback\nTRIGGERED_BY=\"${TRIGGERED_BY:-}\"\n\npause_message='This pipeline was rolled back, please check with team before un-pausing.'\n\n## Include triggered by in the pause message if it is not empty\nif [ -n \"$TRIGGERED_BY\" ]; then\n pause_message=\"$pause_message Triggered by: $TRIGGERED_BY\"\nfi\n\n## Get sha from the given pipeline run to deploy to all pipedream pipelines.\nsha=$(gocd-sha-for-pipeline --material-name=\"${ROLLBACK_MATERIAL_NAME}\")\n\necho \"📑 Rolling back to sha: ${sha}\"\n\ngocd-emergency-deploy \\\n --material-name=\"${ROLLBACK_MATERIAL_NAME}\" \\\n --commit-sha=\"${sha}\" \\\n --deploy-stage=\"${ROLLBACK_STAGE}\" \\\n --pause-message=\"$pause_message\" \\\n \"$@\"\n" + "script": "##!/bin/bash\n\n## Note: $ALLOW_MISSING_FLAGS and $REGION_PIPELINE_FLAGS have no quoting, for word expansion\n## shellcheck disable=SC2086\nif [[ \"${REGION_PIPELINE_FLAGS:-}\" || \"${ALLOW_MISSING_FLAGS:-}\" ]]; then\n set -- $ALLOW_MISSING_FLAGS $REGION_PIPELINE_FLAGS\nfi\n\n## The user that triggered the rollback\nTRIGGERED_BY=\"${TRIGGERED_BY:-}\"\n\npause_message='This pipeline was rolled back, please check with team before un-pausing.'\n\n## Include triggered by in the pause message if it is not empty\nif [ -n \"$TRIGGERED_BY\" ]; then\n pause_message=\"$pause_message Triggered by: $TRIGGERED_BY\"\nfi\n\n## Get sha from the given pipeline run to deploy to all pipedream pipelines.\nsha=$(gocd-sha-for-pipeline --material-name=\"${ROLLBACK_MATERIAL_NAME}\")\n\necho \"📑 Rolling back to sha: ${sha}\"\n\ngocd-emergency-deploy \\\n --material-name=\"${ROLLBACK_MATERIAL_NAME}\" \\\n --commit-sha=\"${sha}\" \\\n --deploy-stage=\"${ROLLBACK_STAGE}\" \\\n --pause-message=\"$pause_message\" \\\n \"$@\"\n" } ] } diff --git a/test/testdata/goldens/pipedream/rollback-final-stage-override.jsonnet_single-file.golden b/test/testdata/goldens/pipedream/rollback-final-stage-override.jsonnet_single-file.golden index 2a40173..5c80f11 100644 --- a/test/testdata/goldens/pipedream/rollback-final-stage-override.jsonnet_single-file.golden +++ b/test/testdata/goldens/pipedream/rollback-final-stage-override.jsonnet_single-file.golden @@ -274,6 +274,7 @@ "rollback-example": { "display_order": 1, "environment_variables": { + "ALLOW_MISSING_FLAGS": "", "ALL_PIPELINE_FLAGS": "--pipeline=deploy-example-s4s2 --pipeline=deploy-example-de --pipeline=deploy-example-us --pipeline=deploy-example-us2 --pipeline=deploy-example-st", "GOCD_ACCESS_TOKEN": "{{SECRET:[devinfra][gocd_access_token]}}", "REGION_PIPELINE_FLAGS": "--pipeline=deploy-example-s4s2 --pipeline=deploy-example-de --pipeline=deploy-example-us --pipeline=deploy-example-us2 --pipeline=deploy-example-st", @@ -314,7 +315,7 @@ "elastic_profile_id": "example", "tasks": [ { - "script": "##!/bin/bash\n\n## Note: $REGION_PIPELINE_FLAGS has no quoting, for word expansion\n## shellcheck disable=SC2086\nif [[ \"${REGION_PIPELINE_FLAGS:-}\" ]]; then\n set -- $REGION_PIPELINE_FLAGS\nfi\n\n## The user that triggered the rollback\nTRIGGERED_BY=\"${TRIGGERED_BY:-}\"\n\npause_message='This pipeline was rolled back, please check with team before un-pausing.'\n\n## Include triggered by in the pause message if it is not empty\nif [ -n \"$TRIGGERED_BY\" ]; then\n pause_message=\"$pause_message Triggered by: $TRIGGERED_BY\"\nfi\n\n## Get sha from the given pipeline run to deploy to all pipedream pipelines.\nsha=$(gocd-sha-for-pipeline --material-name=\"${ROLLBACK_MATERIAL_NAME}\")\n\necho \"📑 Rolling back to sha: ${sha}\"\n\ngocd-emergency-deploy \\\n --material-name=\"${ROLLBACK_MATERIAL_NAME}\" \\\n --commit-sha=\"${sha}\" \\\n --deploy-stage=\"${ROLLBACK_STAGE}\" \\\n --pause-message=\"$pause_message\" \\\n \"$@\"\n" + "script": "##!/bin/bash\n\n## Note: $ALLOW_MISSING_FLAGS and $REGION_PIPELINE_FLAGS have no quoting, for word expansion\n## shellcheck disable=SC2086\nif [[ \"${REGION_PIPELINE_FLAGS:-}\" || \"${ALLOW_MISSING_FLAGS:-}\" ]]; then\n set -- $ALLOW_MISSING_FLAGS $REGION_PIPELINE_FLAGS\nfi\n\n## The user that triggered the rollback\nTRIGGERED_BY=\"${TRIGGERED_BY:-}\"\n\npause_message='This pipeline was rolled back, please check with team before un-pausing.'\n\n## Include triggered by in the pause message if it is not empty\nif [ -n \"$TRIGGERED_BY\" ]; then\n pause_message=\"$pause_message Triggered by: $TRIGGERED_BY\"\nfi\n\n## Get sha from the given pipeline run to deploy to all pipedream pipelines.\nsha=$(gocd-sha-for-pipeline --material-name=\"${ROLLBACK_MATERIAL_NAME}\")\n\necho \"📑 Rolling back to sha: ${sha}\"\n\ngocd-emergency-deploy \\\n --material-name=\"${ROLLBACK_MATERIAL_NAME}\" \\\n --commit-sha=\"${sha}\" \\\n --deploy-stage=\"${ROLLBACK_STAGE}\" \\\n --pause-message=\"$pause_message\" \\\n \"$@\"\n" } ] } diff --git a/test/testdata/goldens/pipedream/rollback.jsonnet_output-files.golden b/test/testdata/goldens/pipedream/rollback.jsonnet_output-files.golden index aa50934..a2acca4 100644 --- a/test/testdata/goldens/pipedream/rollback.jsonnet_output-files.golden +++ b/test/testdata/goldens/pipedream/rollback.jsonnet_output-files.golden @@ -300,6 +300,7 @@ "rollback-example": { "display_order": 1, "environment_variables": { + "ALLOW_MISSING_FLAGS": "", "ALL_PIPELINE_FLAGS": "--pipeline=deploy-example-s4s2 --pipeline=deploy-example-de --pipeline=deploy-example-us --pipeline=deploy-example-us2 --pipeline=deploy-example-st", "GOCD_ACCESS_TOKEN": "{{SECRET:[devinfra][gocd_access_token]}}", "REGION_PIPELINE_FLAGS": "--pipeline=deploy-example-s4s2 --pipeline=deploy-example-de --pipeline=deploy-example-us --pipeline=deploy-example-us2 --pipeline=deploy-example-st", @@ -340,7 +341,7 @@ "elastic_profile_id": "example", "tasks": [ { - "script": "##!/bin/bash\n\n## Note: $REGION_PIPELINE_FLAGS has no quoting, for word expansion\n## shellcheck disable=SC2086\nif [[ \"${REGION_PIPELINE_FLAGS:-}\" ]]; then\n set -- $REGION_PIPELINE_FLAGS\nfi\n\n## The user that triggered the rollback\nTRIGGERED_BY=\"${TRIGGERED_BY:-}\"\n\npause_message='This pipeline was rolled back, please check with team before un-pausing.'\n\n## Include triggered by in the pause message if it is not empty\nif [ -n \"$TRIGGERED_BY\" ]; then\n pause_message=\"$pause_message Triggered by: $TRIGGERED_BY\"\nfi\n\n## Get sha from the given pipeline run to deploy to all pipedream pipelines.\nsha=$(gocd-sha-for-pipeline --material-name=\"${ROLLBACK_MATERIAL_NAME}\")\n\necho \"📑 Rolling back to sha: ${sha}\"\n\ngocd-emergency-deploy \\\n --material-name=\"${ROLLBACK_MATERIAL_NAME}\" \\\n --commit-sha=\"${sha}\" \\\n --deploy-stage=\"${ROLLBACK_STAGE}\" \\\n --pause-message=\"$pause_message\" \\\n \"$@\"\n" + "script": "##!/bin/bash\n\n## Note: $ALLOW_MISSING_FLAGS and $REGION_PIPELINE_FLAGS have no quoting, for word expansion\n## shellcheck disable=SC2086\nif [[ \"${REGION_PIPELINE_FLAGS:-}\" || \"${ALLOW_MISSING_FLAGS:-}\" ]]; then\n set -- $ALLOW_MISSING_FLAGS $REGION_PIPELINE_FLAGS\nfi\n\n## The user that triggered the rollback\nTRIGGERED_BY=\"${TRIGGERED_BY:-}\"\n\npause_message='This pipeline was rolled back, please check with team before un-pausing.'\n\n## Include triggered by in the pause message if it is not empty\nif [ -n \"$TRIGGERED_BY\" ]; then\n pause_message=\"$pause_message Triggered by: $TRIGGERED_BY\"\nfi\n\n## Get sha from the given pipeline run to deploy to all pipedream pipelines.\nsha=$(gocd-sha-for-pipeline --material-name=\"${ROLLBACK_MATERIAL_NAME}\")\n\necho \"📑 Rolling back to sha: ${sha}\"\n\ngocd-emergency-deploy \\\n --material-name=\"${ROLLBACK_MATERIAL_NAME}\" \\\n --commit-sha=\"${sha}\" \\\n --deploy-stage=\"${ROLLBACK_STAGE}\" \\\n --pause-message=\"$pause_message\" \\\n \"$@\"\n" } ] } diff --git a/test/testdata/goldens/pipedream/rollback.jsonnet_single-file.golden b/test/testdata/goldens/pipedream/rollback.jsonnet_single-file.golden index a57a8e5..abae176 100644 --- a/test/testdata/goldens/pipedream/rollback.jsonnet_single-file.golden +++ b/test/testdata/goldens/pipedream/rollback.jsonnet_single-file.golden @@ -274,6 +274,7 @@ "rollback-example": { "display_order": 1, "environment_variables": { + "ALLOW_MISSING_FLAGS": "", "ALL_PIPELINE_FLAGS": "--pipeline=deploy-example-s4s2 --pipeline=deploy-example-de --pipeline=deploy-example-us --pipeline=deploy-example-us2 --pipeline=deploy-example-st", "GOCD_ACCESS_TOKEN": "{{SECRET:[devinfra][gocd_access_token]}}", "REGION_PIPELINE_FLAGS": "--pipeline=deploy-example-s4s2 --pipeline=deploy-example-de --pipeline=deploy-example-us --pipeline=deploy-example-us2 --pipeline=deploy-example-st", @@ -314,7 +315,7 @@ "elastic_profile_id": "example", "tasks": [ { - "script": "##!/bin/bash\n\n## Note: $REGION_PIPELINE_FLAGS has no quoting, for word expansion\n## shellcheck disable=SC2086\nif [[ \"${REGION_PIPELINE_FLAGS:-}\" ]]; then\n set -- $REGION_PIPELINE_FLAGS\nfi\n\n## The user that triggered the rollback\nTRIGGERED_BY=\"${TRIGGERED_BY:-}\"\n\npause_message='This pipeline was rolled back, please check with team before un-pausing.'\n\n## Include triggered by in the pause message if it is not empty\nif [ -n \"$TRIGGERED_BY\" ]; then\n pause_message=\"$pause_message Triggered by: $TRIGGERED_BY\"\nfi\n\n## Get sha from the given pipeline run to deploy to all pipedream pipelines.\nsha=$(gocd-sha-for-pipeline --material-name=\"${ROLLBACK_MATERIAL_NAME}\")\n\necho \"📑 Rolling back to sha: ${sha}\"\n\ngocd-emergency-deploy \\\n --material-name=\"${ROLLBACK_MATERIAL_NAME}\" \\\n --commit-sha=\"${sha}\" \\\n --deploy-stage=\"${ROLLBACK_STAGE}\" \\\n --pause-message=\"$pause_message\" \\\n \"$@\"\n" + "script": "##!/bin/bash\n\n## Note: $ALLOW_MISSING_FLAGS and $REGION_PIPELINE_FLAGS have no quoting, for word expansion\n## shellcheck disable=SC2086\nif [[ \"${REGION_PIPELINE_FLAGS:-}\" || \"${ALLOW_MISSING_FLAGS:-}\" ]]; then\n set -- $ALLOW_MISSING_FLAGS $REGION_PIPELINE_FLAGS\nfi\n\n## The user that triggered the rollback\nTRIGGERED_BY=\"${TRIGGERED_BY:-}\"\n\npause_message='This pipeline was rolled back, please check with team before un-pausing.'\n\n## Include triggered by in the pause message if it is not empty\nif [ -n \"$TRIGGERED_BY\" ]; then\n pause_message=\"$pause_message Triggered by: $TRIGGERED_BY\"\nfi\n\n## Get sha from the given pipeline run to deploy to all pipedream pipelines.\nsha=$(gocd-sha-for-pipeline --material-name=\"${ROLLBACK_MATERIAL_NAME}\")\n\necho \"📑 Rolling back to sha: ${sha}\"\n\ngocd-emergency-deploy \\\n --material-name=\"${ROLLBACK_MATERIAL_NAME}\" \\\n --commit-sha=\"${sha}\" \\\n --deploy-stage=\"${ROLLBACK_STAGE}\" \\\n --pause-message=\"$pause_message\" \\\n \"$@\"\n" } ] }