Run a Generic Shell Script with Spinnaker


Introduction

Spinnaker is a tool specialized in completing Deployments natively, without having to write shell scripts. That said, sometimes when end users may need to run custom logic related to deployments.
Spinnaker has a stage called Scriptto help deal with this logic.  However, this execution relies on a Jenkins job under the hood and has been deprecated in favor of the Jenkins stage.

If end users want to run a script, then Kubernetes jobs can be used to execute, but then the user will need to develop a Docker container that contains the script. A solution is to use a generic docker container and mount the script in a Kubernetes ConfigMap.

In this way, end users can define the script contents as part of the deployment yaml for the ConfigMap.

Prerequisites

N/A

Instructions

Basic Method

Stage: Deploy (Manifest)

apiVersion: v1
data:
  script.sh: |-
    echo "Hello world!"
    kubectl get pods
kind: ConfigMap
metadata:
  name: script-configmap
  namespace: german
---
apiVersion: batch/v1
kind: Job
metadata:
  labels:
    app: script-job
  name: script-job
  namespace: german
spec:
  backoffLimit: 2
  template:
    spec:
      containers:
        - command:
            - sh
            - /opt/script/script.sh
          image: 'bitnami/kubectl:1.12'
          name: script
          volumeMounts:
            - mountPath: /opt/script
              name: script-configmap
              readOnly: false
      restartPolicy: Never
      serviceAccountName: spinnaker-service-account
      volumes:
        - configMap:
            name: script-configmap
          name: script-configmap

Here we define a generic Deploy (Manifest) stage, supplying the above text contents.

End users define the ConfigMap as well as the Job. The script is part of the ConfigMap, and the Job runs a generic Docker container from the Docker hub with the tool kubectl. The job will execute a command, which is the script mounted in the ConfigMap.

In the situation there is a need to edit the script, this can be done without generating another Docker image.  Another consideration is that jobs can be run only once in Kubernetes and are immutable, so the end user will also need to add a Delete (Manifest) stage for cleaning up/deleting old jobs before another Deploy (manifest) stage is executed.

 

Retrieving the Shell Script from Version Control

The basic approach is fine for small scripts, but writing something more complex inside a ConfigMap definition is no fun.
Fortunately, a trick in Spinnaker allows users to read file contents from an artifact, which can be a GitHub repo file, Bitbucket repo file, AWS S3 file, and any supported Spinnaker artifact that refers to text files.
See the complete list of supported artifact sources: https://spinnaker.io/docs/setup/other_config/artifacts/

In the following example, a GitHub repository is used as an artifact source for the script file.

End users will a Webhook stage to make a request to Spinnaker’s Clouddriver service.  The service already has all the credentials needed for downloading artifacts.  However, administrators may need to enable and supply those artifact credentials through Spinnaker configuration, if they don't already exist.
Clouddriver will use the same logic it already uses in other parts of Spinnaker to download the file and return it to the Webhook stage.
The output of this stage (and hence the file) is available in the pipeline execution context, so user can then refer to it using Pipeline Expressions in the ConfigMap definition:

Example of the Webhook stage configuration

Webhook URL:  http://spin-clouddriver:7002/artifacts/fetch
Method:       PUT
Payload:      {
                "artifactAccount": "<ACCOUNT NAME CONFIGURED IN SPINNAKER CONFIG>",
                "reference": "https://api.github.com/repos/<ORG>/<REPO>/contents/<PATH TO FILE>",
                "type": "github/file",
                "version": "master"
              }

 

Example of the Deploy (Manifest) - ConfigMap

apiVersion: v1
data:
  script.sh: '${#stage("Webhook")["context"]["webhook"]["body"]}'
kind: ConfigMap
metadata:
  name: script-configmap
  namespace: german

 

Then the Run job stage can contain only the yaml description of the kubernetes job.

 

As Tested On Version

2.17