opa.pipelines

Package that supports pipeline analysis when pipelines are saved.

Example Payload

Click to expand
{
  "input": {
    "pipeline": {
      "application": "hostname",
      "expectedArtifacts": [
        {
          "defaultArtifact": {
            "artifactAccount": "myUsername",
            "id": "4aa85178-0618-46c4-b530-6883d393656d",
            "name": "manifests/deploy-spinnaker.yaml",
            "reference": "Https://api.github.com/repos/myUsername/hostname/contents/manifests/deploy-spinnaker.yaml",
            "type": "github/file",
            "version": "master"
          },
          "displayName": "hostname-deploy",
          "id": "0cf98032-1b0f-48db-9314-09c69293b3a6",
          "matchArtifact": {
            "artifactAccount": "myUsername",
            "customKind": true,
            "id": "3f72ed8e-cb95-454f-9119-2323682121ff",
            "name": "manifests/deploy-spinnaker.yaml",
            "type": "github/file"
          },
          "useDefaultArtifact": true,
          "usePriorArtifact": false
        },
        {
          "defaultArtifact": {
            "artifactAccount": "myUsername",
            "id": "e79162ab-69cb-4ff7-acf4-a8f2875ef8ef",
            "name": "manifests/service-spinnaker.yaml",
            "reference": "Https://api.github.com/repos/myUsername/hostname/contents/manifests/service-spinnaker.yaml",
            "type": "github/file"
          },
          "displayName": "service-hostname",
          "id": "425d20a8-2942-4902-8d2b-277769a1492c",
          "matchArtifact": {
            "artifactAccount": "myUsername",
            "customKind": true,
            "id": "d7ac7eca-0131-4d54-ab8f-880ff0041e4f",
            "name": "manifests/service-spinnaker",
            "type": "github/file"
          },
          "useDefaultArtifact": true,
          "usePriorArtifact": false
        }
      ],
      "id": "7db1e350-dedb-4dc1-9976-e71f97b5f132",
      "index": 0,
      "keepWaitingPipelines": false,
      "lastModifiedBy": "myUsername",
      "limitConcurrent": true,
      "name": "scale deployments",
      "parameterConfig": [
        {
          "default": "",
          "description": "",
          "hasOptions": false,
          "label": "",
          "name": "replicas",
          "options": [
            {
              "value": ""
            }
          ],
          "pinned": false,
          "required": false
        },
        {
          "default": "staging",
          "description": "",
          "hasOptions": false,
          "label": "",
          "name": "namespace",
          "options": [
            {
              "value": ""
            }
          ],
          "pinned": false,
          "required": true
        }
      ],
      "spelEvaluator": "v4",
      "stages": [
        {
          "account": "spinnaker",
          "cloudProvider": "kubernetes",
          "manifestArtifactId": "0cf98032-1b0f-48db-9314-09c69293b3a6",
          "moniker": {
            "app": "hostname"
          },
          "name": "Deploy (Manifest) g",
          "refId": "2",
          "requisiteStageRefIds": [],
          "skipExpressionEvaluation": false,
          "source": "artifact",
          "trafficManagement": {
            "enabled": false,
            "options": {
              "enableTraffic": false,
              "services": []
            }
          },
          "type": "deployManifest"
        },
        {
          "account": "spinnaker",
          "cloudProvider": "kubernetes",
          "manifestArtifactId": "425d20a8-2942-4902-8d2b-277769a1492c",
          "moniker": {
            "app": "hostname"
          },
          "name": "Deploy service (Manifest)",
          "refId": "3",
          "requisiteStageRefIds": [],
          "skipExpressionEvaluation": false,
          "source": "artifact",
          "trafficManagement": {
            "enabled": false,
            "options": {
              "enableTraffic": false,
              "services": []
            }
          },
          "type": "deployManifest"
        },
        {
          "completeOtherBranchesThenFail": false,
          "continuePipeline": true,
          "failPipeline": false,
          "instructions": "is the new service working?",
          "judgmentInputs": [],
          "name": "Manual Judgment",
          "notifications": [],
          "refId": "4",
          "requisiteStageRefIds": [
            "2",
            "3"
          ],
          "stageTimeoutMs": 60000,
          "type": "manualJudgment"
        },
        {
          "account": "spinnaker",
          "app": "hostname",
          "cloudProvider": "kubernetes",
          "location": "staging",
          "manifestName": "deployment hostname",
          "mode": "static",
          "name": "Scale (Manifest)",
          "refId": "5",
          "replicas": "10",
          "requisiteStageRefIds": [
            "4"
          ],
          "type": "scaleManifest"
        }
      ],
      "triggers": [
        {
          "branch": "master",
          "enabled": true,
          "expectedArtifactIds": [
            "0cf98032-1b0f-48db-9314-09c69293b3a6",
            "425d20a8-2942-4902-8d2b-277769a1492c"
          ],
          "project": "myUsername",
          "secret": "spinnaker",
          "slug": "hostname",
          "source": "github",
          "type": "git"
        }
      ],
      "updateTs": "1620677311000"
    }
  }
}

Manual approval by role

Requires a manual approval by the qa role, and a manual approval by the infosec role happen earlier in a pipeline than any deployment to a production account. Production accounts must have been loaded into the OPA data document in an array named production_accounts:

package opa.pipelines

deny["production deploy stage must follow approval by 'qa' and 'infosec'"] {
  some j
  stage :=input.pipeline.stages[j]
  stage.type=="deployManifest"
  stage.account==data.production_accounts[_] 
  lacksEarlierApprovalBy(["qa","infosec"][_],j)  
}

stage_graph[idx]  = edges { #converts stage graph into the structure rego needs
  input.pipeline.stages[idx]
  edges := {neighbor | input.pipeline.stages[neighbor].refId ==   
                  input.pipeline.stages[idx].requisiteStageRefIds[_]}
}

hasEarlierApprovalBy(role, idx){
    stage := input.pipeline.stages[i]
    stage.type=="manualJudgment"
    stage.selectedStageRoles[0]==role; count(stage.selectedStageRoles)==1
    reachable := graph.reachable(stage_graph, {idx})[_]
    reachable ==i
}
lacksEarlierApprovalBy(role,idx) {
    not hasEarlierApprovalBy(role,idx) 
}

Allow list for target namespaces

Only allows applications to deploy to namespaces that are on an allow list.

package opa.pipelines

allowedNamespaces:=[{"app":"app1","ns": ["ns1","ns2"]},
                                    {"app":"app2", "ns":["ns3"]}]

deny["Stage deploys to a namespace to which this application lacks access"]{
    ns :=object.get(input.stage.context.manifests[_].metadata,"namespace","default")
    application := input.pipeline.application
    not canDeploy(ns, application)
}

canDeploy(namespace, application){
    some i
    allowedNamespaces[i].app==application
    allowedNamespaces[i].ns[_]==namespace
}

Deployment window

The policy prevents a user from saving a pipeline that deploys to production accounts unless the first stage of the pipeline specifies a schedule that prevents it from starting executions between 2pm and 7pm Pacific Standard Time (PST).

package opa.pipelines

productionAccounts:=["spinnaker"]

deny ["Your first stage must configure a blackout window that prevents an execution from starting between 2pm and 7pm PST."] {
# Restrict to just one app in my demo environment
some i
# Check whether or not this stage is at the beginning of the pipeline by verifying if it it depends on a  stage
    count(input.pipeline.stages[i].requisiteStageRefIds)==0
    input.pipeline.stages[_].account==productionAccounts[_]
    
    executionWindow := object.get(input.pipeline.stages[i],"restrictedExecutionWindow",null)
    
    # If no execution windoe is defined, or if a prohibited one is defined, then prevent execution.
    any([executionWindow ==null,
         isExecutionProhibitedDuringWindow(executionWindow)])
}

# Prevent the stage from executing between 2PM/14:00 and 7PM/19:00 PST by defining a window of time when deployments are allowed
    isExecutionProhibitedDuringWindow(window){
      some i
      # Window overlaps the start of the blackout window.
      window.whitelist[i].startHour<13
      window.whitelist[i].endHour>13
    }{
      some i
      # Window overlaps the end of the blackout window.
      window.whitelist[i].startHour<19
      window.whitelist[i].endHour>19
    }{ # Window overlaps the start of the blackout window starting on a prior day.
      window.whitelist[i].endHour<window.whitelist[i].startHour
      window.whitelist[i].endHour>13
    }{ # Window overlaps the start of the blackout window starting on a prior day.
      window.whitelist[i].endHour<window.whitelist[i].startHour
      window.whitelist[i].startHour<19
    }
    {
      count(window.whitelist)==0
    }

Keys

KeyTypeDescription
input.pipeline.applicationstringThe name of the Spinnaker application to which this pipeline belongs.
input.pipeline.expectedArtifacts[][array]See artifacts for more information.
input.pipeline.idstringThe unique ID of the pipeline
input.pipeline.indexnumber
input.pipeline.keepWaitingPipelinesbooleanIf false and concurrent pipeline execution is disabled, then the pipelines in the waiting queue gets canceled when the next execution starts.
input.pipeline.lastModifiedBystringThe ID of the user that last modified the pipeline.
input.pipeline.limitConcurrentbooleanTrue if only 1 concurrent execution of this pipeline is allowed.
input.pipeline.namestringThe name of this pipeline.
input.pipeline.parameterConfig[].defaultstringThe default value associated with this parameter.
input.pipeline.parameterConfig[].descriptionstring(Optional): If supplied, is displayed to users as a tooltip when triggering the pipeline manually. You can include HTML in this field.
input.pipeline.parameterConfig[].hasOptionsbooleanTrue if the Show Options checkbox in the parameter is checked.
input.pipeline.parameterConfig[].labelstringThe display name of the parameter.
input.pipeline.parameterConfig[].namestringThe parameter name that can be used in SpEL.
input.pipeline.parameterConfig[].options[].valuestringThe value for this option in a multi-option parameter.
input.pipeline.parameterConfig[].pinnedboolean(Optional): If checked, this parameter is always shown in a pipeline execution view, otherwise it’ll be collapsed by default.
input.pipeline.parameterConfig[].requiredbooleanTrue if this is this a required parameter.
input.pipeline.spelEvaluatorstringWhich version of the Spring Expression Language (SpEL) is being used to evaluate the SpEL expression.
input.pipeline.stages[].accountstringThe account the stage deploys to. Applies to the following stage types: deployManifest, scaleManifest, deploy.
input.pipeline.stages[].appstringThe name of the application being deployed. Use input.body.application instead. Applies to the following stage types: deployManifest, scaleManifest.
input.pipeline.stages[].cloudProviderstringWhich specific cloud provider is being used. Applies to the following stage types: deployManifest, scaleManifest, and deploy.
input.pipeline.stages[].completeOtherBranchesThenFailbooleanPrevents any stages that depend on this stage from running, but allows other branches of the pipeline to run. The pipeline is marked as failed once complete. Available for all stages.
input.pipeline.stages[].continuePipelinebooleanContinues execution of downstream stages, marking this stage as failed/continuing. Available for all stages.
input.pipeline.stages[].failPipelinebooleanImmediately halts execution of all running stages and fails the entire execution if this stage fails. Available for all stages.
input.pipeline.stages[].instructionsstringOnly available on the manual Judgement stage.
Instructions are shown to the user when making a manual judgment.
input.pipeline.stages[].locationstringOnly available on the scale manifest stage.
The namespace to scale the manifest in.
input.pipeline.stages[].manifestArtifactIdstringOnly available on the deploy manifest stage.
The artifact ID to deploy.
input.pipeline.stages[].manifestNamestringOnly available on the scale manifest stage.
The name of the manifest to scale.
input.pipeline.stages[].modestringOnly available on the scale manifest stage.
Determines whether the stage uses a static or a dynamic selector.
input.pipeline.stages[].moniker.appstringThe application being deployed.
input.pipeline.stages[].namestringThe name of the stage.
input.pipeline.stages[].refIdstringThe unique ID for the stage in the stage graph.
input.pipeline.stages[].replicasstringOnly applicable to the scale manifest stage.
How many pods should be running after the scaling action.
input.pipeline.stages[].requisiteStageRefIds.[]stringThe unique IDs of other stages that must complete before this stage.
input.pipeline.stages[].skipExpressionEvaluationbooleanIf true then SpEL is not evaluated in artifacts referenced by the stage.
input.pipeline.stages[].sourcestringOnly applicable to the deploy manifest stage.
Specifies whether the manifest should be read from an artifact, or provided as text in the pipeline definition.
input.pipeline.stages[].stageTimeoutMsnumberOnly applicable to the manual judgement stage.
Specifies how long the user has to provide a judgement.
input.pipeline.stages[].trafficManagement.enabledbooleanOnly applicable to the deploy manifest stage.
Allow Spinnaker to associate each ReplicaSet deployed in this stage with one or more Services, and manage traffic based on your selected rollout strategy options.
input.pipeline.stages[].trafficManagement.options.enableTrafficbooleanOnly applicable to the deploy manifest stage.
Sends client requests to new pods when traffic management is enabled.
input.pipeline.stages[].typestringThe type of the stage.
input.pipeline.updateTsstringThe timestamp of the pipeline’s last modification.

input.pipeline.trigger

See input.pipeline.trigger for more information.


Last modified August 18, 2023: (02b163b7)