github.com/ouraigua/jenkins-library@v0.0.0-20231028010029-fbeaf2f3aa9b/DEVELOPMENT.md (about)

     1  # Development
     2  
     3  **Table of contents:**
     4  
     5  1. [Getting started](#getting-started)
     6  1. [Build the project](#build-the-project)
     7  1. [Generating step framework](#generating-step-framework)
     8  1. [Best practices for writing piper-go steps](#best-practices-for-writing-piper-go-steps)
     9  1. [Testing](#testing)
    10  1. [Debugging](#debugging)
    11  1. [Release](#release)
    12  1. [Pipeline Configuration](#pipeline-configuration)
    13  1. [Security Setup](#security-setup)
    14  
    15  ## Getting started
    16  
    17  1. [Ramp up your development environment](#ramp-up)
    18  1. [Get familiar with Go language](#go-basics)
    19  1. Create [a GitHub account](https://github.com/join)
    20  1. Setup [GitHub access via SSH](https://help.github.com/articles/connecting-to-github-with-ssh/)
    21  1. [Create and checkout a repo fork](#checkout-your-fork)
    22  1. Optional: [Get Jenkins related environment](#jenkins-environment)
    23  1. Optional: [Get familiar with Jenkins Pipelines as Code](#jenkins-pipelines)
    24  
    25  ### Ramp up
    26  
    27  First you need to set up an appropriate development environment:
    28  
    29  1. Install Go, see [GO Getting Started](https://golang.org/doc/install)
    30  1. Install an IDE with Go plugins, see for example [Go in Visual Studio Code](https://code.visualstudio.com/docs/languages/go)
    31  
    32  **Note:** The Go version to be used is the one specified in the "go.mod" file.
    33  
    34  ### Go basics
    35  
    36  In order to get yourself started, there is a lot of useful information out there.
    37  
    38  As a first step to take we highly recommend the [Golang documentation](https://golang.org/doc/), especially [A Tour of Go](https://tour.golang.org/welcome/1).
    39  
    40  We have a strong focus on high quality software and contributions without adequate tests will not be accepted.
    41  There is an excellent resource which teaches Go using a test-driven approach: [Learn Go with Tests](https://github.com/quii/learn-go-with-tests)
    42  
    43  ### Checkout your fork
    44  
    45  The project uses [Go modules](https://blog.golang.org/using-go-modules). Thus please make sure to **NOT** checkout the project into your [`GOPATH`](https://github.com/golang/go/wiki/SettingGOPATH).
    46  
    47  To check out this repository:
    48  
    49  1. Create your own
    50      [fork of this repo](https://help.github.com/articles/fork-a-repo/)
    51  1. Clone it to your machine, for example like:
    52  
    53  ```shell
    54  mkdir -p ${HOME}/projects/jenkins-library
    55  cd ${HOME}/projects
    56  git clone git@github.com:${YOUR_GITHUB_USERNAME}/jenkins-library.git
    57  cd jenkins-library
    58  git remote add upstream git@github.com:sap/jenkins-library.git
    59  git remote set-url --push upstream no_push
    60  ```
    61  
    62  ### Jenkins environment
    63  
    64  If you want to contribute also to the Jenkins-specific parts like
    65  
    66  * Jenkins library step
    67  * Jenkins pipeline integration
    68  
    69  you need to do the following in addition:
    70  
    71  * [Install Groovy](https://groovy-lang.org/install.html)
    72  * [Install Maven](https://maven.apache.org/install.html)
    73  * Get a local Jenkins installed: Use for example [cx-server](https://github.com/SAP/devops-docker-cx-server)
    74  
    75  ### Jenkins pipelines
    76  
    77  The Jenkins related parts depend on
    78  
    79  * [Jenkins Pipelines as Code](https://jenkins.io/doc/book/pipeline-as-code/)
    80  * [Jenkins Shared Libraries](https://jenkins.io/doc/book/pipeline/shared-libraries/)
    81  
    82  You should get familiar with these concepts for contributing to the Jenkins-specific parts.
    83  
    84  ## Build the project
    85  
    86  ### Build the executable suitable for the CI/CD Linux target environments
    87  
    88  Use Docker:
    89  
    90  `docker build -t piper:latest .`
    91  
    92  You can extract the binary using Docker means to your local filesystem:
    93  
    94  ```sh
    95  docker create --name piper piper:latest
    96  docker cp piper:/build/piper .
    97  docker rm piper
    98  ```
    99  
   100  ## Generating step framework
   101  
   102  The steps are generated based on the yaml files in `resources/metadata/` with the following command from the root of the project:
   103  
   104  ```bash
   105  go generate
   106  ```
   107  
   108  The yaml format is kept pretty close to Tekton's [task format](https://github.com/tektoncd/pipeline/blob/master/docs/tasks.md).
   109  Where the Tekton format was not sufficient some extenstions have been made.
   110  
   111  Examples are:
   112  
   113  * matadata - longDescription
   114  * spec - inputs - secrets
   115  * spec - containers
   116  * spec - sidecars
   117  
   118  There are certain extensions:
   119  
   120  * **aliases** allow alternative parameter names also supporting deeper configuration structures. [Example](https://github.com/SAP/jenkins-library/blob/master/resources/metadata/kubernetesDeploy.yaml)
   121  * **resources** allow to read for example from a shared `commonPipelineEnvironment` which contains information which has been provided by a previous step in the pipeline via an output. [Example](https://github.com/SAP/jenkins-library/blob/master/resources/metadata/githubPublishRelease.yaml)
   122  * **secrets** allow to specify references to Jenkins credentials which can be used in the `groovy` library. [Example](https://github.com/SAP/jenkins-library/blob/master/resources/metadata/kubernetesDeploy.yaml)
   123  * **outputs** allow to write to dedicated outputs like
   124  
   125    * Influx metrics. [Example](https://github.com/SAP/jenkins-library/blob/master/resources/metadata/checkmarxExecuteScan.yaml)
   126    * Sharing data via `commonPipelineEnvironment` which can be used by another step as input
   127  
   128  * **conditions** allow for example to specify in which case a certain container is used (depending on a configuration parameter). [Example](https://github.com/SAP/jenkins-library/blob/master/resources/metadata/kubernetesDeploy.yaml)
   129  
   130  ## Best practices for writing piper-go steps
   131  
   132  1. [Logging](#logging)
   133  1. [Error handling](#error-handling)
   134  1. [HTTP calls](#http-calls)
   135  
   136  Implementing a new step starts by adding a new yaml file in `resources/metadata/` and running
   137  the [step generator](#generating-step-framework). This creates most of the boiler-plate code for the
   138  step's implementation in `cmd/`. There are four files per step based on the name given within the yaml:
   139  
   140  1. `cmd/<step>.go` - contains the skeleton of your step implementation.
   141  1. `cmd/<step>_test.go` - write your unit tests here.
   142  1. `cmd/<step>_generated.go` - contains the generated boiler plate code, and a dedicated type definition for your step's options.
   143  1. `cmd/<step>_generated_test.go` - contains a simple unit test for the generated part.
   144  
   145  You never edit in the generated parts. If you need to make changes, you make them in the yaml and re-run the step
   146  generator (which will of course not overwrite your implementation).
   147  
   148  The file `cmd/<step>.go` initially contains two functions:
   149  
   150  ```golang
   151  func step(options stepOptions, telemetryData *telemetry.CustomData) {
   152      err := runStep(&options, telemetryData)
   153      if err != nil {
   154          log.Entry().WithError(err).Fatal("step execution failed")
   155      }
   156  }
   157  
   158  func runStep(options *stepOptions, telemetryData *telemetry.CustomData) error {
   159  }
   160  ```
   161  
   162  The separation into these two functions facilitates unit tests and mocking. From your tests, you could call
   163  `runStep()` with mocking instances of needed objects, while inside `step()`, you create runtime instances of these
   164  objects.
   165  
   166  ### Logging
   167  
   168  Logging is done via the [sirupsen/logrus](https://github.com/sirupsen/logrus) framework.
   169  It can conveniently be accessed through:
   170  
   171  ```golang
   172  import (
   173      "github.com/SAP/jenkins-library/pkg/log"
   174  )
   175  
   176  func myStep ...
   177      ...
   178      log.Entry().Info("This is my info.")
   179      ...
   180  }
   181  ```
   182  
   183  If a fatal error occurs your code should act similar to:
   184  
   185  ```golang
   186      ...
   187      if err != nil {
   188          log.Entry().
   189              WithError(err).
   190              Fatal("failed to execute step ...")
   191      }
   192  ```
   193  
   194  Calling `Fatal` results in an `os.Exit(0)` and before exiting some cleanup actions (e.g. writing output data,
   195  writing telemetry data if not deactivated by the user, ...) are performed.
   196  
   197  ### Error handling
   198  
   199  In order to better understand the root cause of errors that occur, we wrap errors like
   200  
   201  ```golang
   202      f, err := os.Open(path)
   203      if err != nil {
   204          return errors.Wrapf(err, "open failed for %v", path)
   205      }
   206      defer f.Close()
   207  ```
   208  
   209  We use [github.com/pkg/errors](https://github.com/pkg/errors) for that.
   210  
   211  It has proven a good practice to bubble up errors until the runtime entry function  and only
   212  there exit via the logging framework (see also [logging](#logging)).
   213  
   214  ### Error categories
   215  
   216  For errors, we have a convenience function to set a pre-defined category once an error occurs:
   217  
   218  ```golang
   219  log.SetErrorCategory(log.ErrorCompliance)
   220  ```
   221  
   222  Error categories are defined in [`pkg/log/ErrorCategory`](pkg/log/errors.go).
   223  
   224  With writing a fatal error
   225  
   226  ```golang
   227  log.Entry().WithError(err).Fatal("the error message")
   228  ```
   229  
   230  the category will be written into the file `errorDetails.json` and can be used from there in the further pipeline flow.
   231  Writing the file is handled by [`pkg/log/FatalHook`](pkg/log/fatalHook.go).
   232  
   233  ### HTTP calls
   234  
   235  All HTTP(S) interactions with other systems should be leveraging the [`pkg/http`](pkg/http) to enable capabilities provided
   236  centrally like automatic retries in case of intermittend HTTP errors or individual and optimized timout or logging capabilities.
   237  The HTTP package provides a thin wrapper around the standard golang `net/http` package adding just the right bit of sugar on top to
   238  have more control on common behaviors.
   239  
   240  ### Automatic retries
   241  
   242  Automatic retries have been implemented based on [hashicorp's retryable HTTP client for golang](https://github.com/hashicorp/go-retryablehttp)
   243  with some extensions and customizations to the HTTP status codes being retried as well as to improve some service specific error situations.
   244  The client by default retries 15 times until it gives up and regards a specific communication event as being not recoverable. If you know by heart that
   245  your service is much more stable and cloud live without retry handling or a specifically lower amout of retries, you can easily customize behavior via the
   246  `ClientOptions` as shown in the sample below:
   247  
   248  ```golang
   249  clientOptions := piperhttp.ClientOptions{}
   250  clientOptions.MaxRetries = -1
   251  httpClient.SetOptions(clientOptions)
   252  ```
   253  
   254  ## Testing
   255  
   256  1. [Mocking](#mocking)
   257  1. [Mockable Interface](#mockable-interface)
   258  1. [Global function pointers](#global-function-pointers)
   259  1. [Test Parallelization](#test-parallelization)
   260  
   261  Unit tests are done using basic `golang` means.
   262  
   263  Additionally, we encourage you to use [github.com/stretchr/testify/assert](https://github.com/stretchr/testify/assert)
   264  in order to have slimmer assertions if you like. A good pattern to follow is this:
   265  
   266  ```golang
   267  func TestNameOfFunctionUnderTest(t *testing.T) {
   268      t.Run("A description of the test case", func(t *testing.T) {
   269          // init
   270          // test
   271          // assert
   272      })
   273      t.Run("Another test case", func(t *testing.T) {
   274          // init
   275          // test
   276          // assert
   277      })
   278  }
   279  ```
   280  
   281  This will also structure the test output for better readability.
   282  
   283  ### Mocking
   284  
   285  Tests should be written only for the code of your step implementation, while any
   286  external functionality should be mocked, in order to test all code paths including
   287  the error cases.
   288  
   289  There are (at least) two approaches for this:
   290  
   291  #### Mockable Interface
   292  
   293  In this approach you declare an interface that contains every external function
   294  used within your step that you need to be able to mock. In addition, you declare a struct
   295  which holds the data you need during runtime, and implement the interface with the "real"
   296  functions. Here is an example to illustrate:
   297  
   298  ```golang
   299  import (
   300      "github.com/SAP/jenkins-library/pkg/piperutils"
   301  )
   302  
   303  type myStepUtils interface {
   304      fileExists(path string) (bool, error)
   305      fileRead(path string) ([]byte, error)
   306  }
   307  
   308  type myUtilsData struct {
   309      fileUtils piperutils.Files
   310  }
   311  
   312  func (u *myUtilsData) fileExists(path string) (bool, error) {
   313      return u.fileUtils.FileExists(path)
   314  }
   315  
   316  func (u *myUtilsData) fileRead(path string) ([]byte, error) {
   317      return u.fileUtils.FileRead(path)
   318  }
   319  ```
   320  
   321  Then you create the runtime version of the utils data in your top-level entry function and
   322  pass it to your `run*()` function:
   323  
   324  ```golang
   325  func step(options stepOptions, _ *telemetry.CustomData) {
   326      utils := myUtilsData{
   327          fileUtils: piperutils.Files{},
   328      }
   329      err := runStep(&options, &utils)
   330      ...
   331  }
   332  
   333  func runStep(options *stepOptions, utils myStepUtils) error {
   334      ...
   335      exists, err := utils.fileExists(path)
   336      ...
   337  }
   338  ```
   339  
   340  In your tests, you would provide a mocking implementation of this interface and pass
   341  instances of that to the functions under test. To better illustrate this, here is an example
   342  for the interface above implemented in the `<step>_test.go` file:
   343  
   344  ```golang
   345  type mockUtilsBundle struct {
   346      files map[string][]byte
   347  }
   348  
   349  func newMockUtilsBundle() mockUtilsBundle {
   350      utils := mockUtilsBundle{}
   351      utils.files = map[string][]byte{}
   352      return utils
   353  }
   354  
   355  func (m *mockUtilsBundle) fileExists(path string) (bool, error) {
   356      content := m.files[path]
   357      return content != nil, nil
   358  }
   359  
   360  func (m *mockUtilsBundle) fileRead(path string) ([]byte, error) {
   361      content := m.files[path]
   362      if content == nil {
   363          return nil, fmt.Errorf("could not read '%s': %w", path, os.ErrNotExist)
   364      }
   365      return content, nil
   366  }
   367  
   368  // This is how it would be used in tests:
   369  
   370  func TestSomeFunction() {
   371      t.Run("Happy path", func(t *testing.T) {
   372          // init
   373          utils := newMockUtilsBundle()
   374          utils.files["some/path/file.xml"] = []byte(´content of the file´)
   375          // test
   376          err := someFunction(&utils)
   377          // assert
   378          assert.NoError(t, err)
   379      })
   380      t.Run("Error path", func(t *testing.T) {
   381          // init
   382          utils := newMockUtilsBundle()
   383          // test
   384          err := someFunction(&utils)
   385          // assert
   386          assert.EqualError(t, err, "could not read 'some/path/file.xml'")
   387      })
   388  }
   389  ```
   390  
   391  #### Global Function Pointers
   392  
   393  An alternative approach are global function pointers:
   394  
   395  ```golang
   396  import (
   397      FileUtils "github.com/SAP/jenkins-library/pkg/piperutils"
   398  )
   399  
   400  var fileUtilsExists = FileUtils.FileExists
   401  
   402  func someFunction(options *stepOptions) error {
   403      ...
   404      exists, err := fileUtilsExists(path)
   405      ...
   406  }
   407  ```
   408  
   409  In your tests, you can then simply set the function pointer to a mocking implementation:
   410  
   411  ```golang
   412  func TestSomeFunction() {
   413      t.Run("Happy path", func(t *testing.T) {
   414          // init
   415          originalFileExists := fileUtilsExists
   416          fileUtilsExists = func(filename string) (bool, error) {
   417              return true, nil
   418          }
   419          defer fileUtilsExists = originalFileExists
   420          // test
   421          err := someFunction(...)
   422          // assert
   423          assert.NoError(t, err)
   424      })
   425      t.Run("Error path", func(t *testing.T) {
   426          // init
   427          originalFileExists := fileUtilsExists
   428          fileUtilsExists = func(filename string) (bool, error) {
   429              return false, errors.New("something happened")
   430          }
   431          defer fileUtilsExists = originalFileExists
   432          // test
   433          err := someFunction(...)
   434          // assert
   435          assert.EqualError(t, err, "something happened")
   436      })
   437  }
   438  ```
   439  
   440  Both approaches have their own benefits. Global function pointers require less preparation
   441  in the actual implementation and give great flexibility in the tests, while mocking interfaces
   442  tend to result in more code re-use and slim down the tests. The mocking implementation of a
   443  utils interface can facilitate implementations of related functions to be based on shared data.
   444  
   445  ### Test Parallelization
   446  
   447  Tests that can be executed in parallel should be marked as such.
   448  With the command `t.Parallel()` the test framework can be notified that this test can run in parallel, and it can start running the next test.
   449  ([Example in Stackoverflow](https://stackoverflow.com/questions/44325232/are-tests-executed-in-parallel-in-go-or-one-by-one))
   450  Therefore, this command shall be called at the beginning of a test method **and also** in each `t.Run()` sub tests.
   451  See also the [documentation](https://golang.org/pkg/testing/#T.Parallel) for `t.Parallel()` and `t.Run()`.
   452  
   453  ```go
   454  func TestMethod(t *testing.T) {
   455      t.Parallel() // indicates that this method can run parallel to other methods
   456  
   457      t.Run("sub test 1", func(t *testing.T){
   458          t.Parallel() // indicates that this sub test can run parallel to other sub tests
   459          // execute test
   460      })
   461  
   462      t.Run("sub test 2", func(t *testing.T){
   463          t.Parallel() // indicates that this sub test can run parallel to other sub tests
   464          // execute test
   465      })
   466  }
   467  ```
   468  
   469  Go will first execute the non-parallelized tests in sequence and afterwards execute all the parallel tests in parallel, limited by the default number of parallel executions.
   470  
   471  It is important that tests executed in parallel use the variable values actually meant to be visible to them.
   472  Especially in table tests, it can happen easily that a variable injected into the `t.Run()`-closure via the outer scope is changed before or while the closure executes.
   473  To prevent this, it is possible to create shadowing instances of variables in the body of the test loop.
   474  (See [blog about it](https://eleni.blog/2019/05/11/parallel-test-execution-in-go/).)
   475  At the minimum, you need to capture the test case value from the loop iteration variable, by shadowing this variable in the loop body.
   476  Inside the `t.Run()` closure, this shadow copy is visible, and cannot be overwritten by later loop iterations.
   477  If you do not make this shadowing copy, what is visible in the closure is the variable which gets re-assigned with a new value in each loop iteration.
   478  The value of this variable is then not fixed for the test run.
   479  
   480  ```go
   481  func TestMethod(t *testing.T) {
   482      t.Parallel() // indicates that this method can parallel to other methods
   483      testCases := []struct {
   484          Name string
   485      }{
   486          {
   487              Name: "Name1"
   488          },
   489          {
   490              Name: "Name2"
   491          },
   492      }
   493  
   494      for _, testCase := range testCases { // testCase defined here is re-assigned in each iteration
   495          testCase := testCase // define new variable within loop to detach from overwriting of the outer testCase variable by next loop iteration
   496          // The same variable name "testCase" is used for convenience.
   497          t.Run(testCase.Name, func(t *testing.T) {
   498              t.Parallel() // indicates that this sub test can run parallel to other sub tests
   499              // execute test
   500          })
   501      }
   502  }
   503  ```
   504  
   505  ### Test pipeline for your fork (Jenkins)
   506  
   507  Piper is ececuting the steps of each stage within a container. If you want to test your developments you have to ensure they are part of the image which is used in your test pipeline.
   508  
   509  #### Testing Pipeline or Stage Definition changes (Jenkins)
   510  
   511  As the pipeline and stage definitions (e.g. \*Pipeline\*Stage\*.groovy files in the vars folder) are directly executed you can easily test them just by referencing to your repo/branch/tag in the jenkinsfile.
   512  
   513  ```groovy
   514  @Library('my-piper-lib-os-fork@MyTest') _
   515  
   516  abapEnvironmentPipeline script: this
   517  ```
   518  
   519  #### Testing changes on Step Level (Jenkins)
   520  
   521  To trigger the creation of a "custom" container with your changes you can reuse a feature in piper which is originally meant for executing the integration tests. If the environment variables 'REPOSITORY_UNDER_TEST' (pointing to your forked repo) and 'LIBRARY_VERSION_UNDER_TEST' (pointing to a tag in your forked repo) are set a corresponding container gets created on the fly upon first usage in the pipeline. The drawback is that this takes extra time (1-2 minutes) you have to spend for every execution of the pipeline.
   522  
   523  ```groovy
   524  @Library('piper-lib-os') _
   525  
   526  env.REPOSITORY_UNDER_TEST       = 'myfork' // e.g. 'myUser/jenkins-library'
   527  env.LIBRARY_VERSION_UNDER_TEST  = 'MyTag'
   528  
   529  abapEnvironmentPipeline script: this
   530  ```
   531  
   532  #### Using Parameterized Pipelines (Jenkins)
   533  
   534  For test purpose it can be useful to utilize a parameterized pipeline. E.g. to toggle creation of the custom container:
   535  
   536  ```groovy
   537  @Library('my-piper-lib-os-fork@MyTest') _
   538  
   539  properties([
   540      parameters([
   541          booleanParam(name: 'toggleSomething', defaultValue: false, description: 'dito'),
   542          booleanParam(name: 'testPiperFork', defaultValue: false, description: 'dito'),
   543          string(name: 'repoUnderTest', defaultValue: '<MyUser>/jenkins-library', description: 'dito'),
   544          string(name: 'tag', defaultValue: 'MyTest', description: 'dito')
   545      ])
   546  ])
   547  
   548  if (params.testPiperFork == true) {
   549      env.REPOSITORY_UNDER_TEST       = params.repoUnderTest
   550      env.LIBRARY_VERSION_UNDER_TEST  = params.tag
   551  }
   552  
   553  abapEnvironmentPipeline script: this
   554  ```
   555  
   556  or skipping steps/stages with the help of extensions:
   557  
   558  ```groovy
   559  void call(Map piperParams) {
   560    echo "Start - Extension for stage: ${piperParams.stageName}"
   561  
   562    if (params.toggleSomething == true) {
   563      // do something
   564      echo "now execute original stage as defined in the template"
   565      piperParams.originalStage()
   566    } else {
   567      // do something else
   568      // e.g. only this singele step of the stage
   569      somePiperStep( script: piperParams.script, someConfigParameter: '<...>' )
   570    }
   571    
   572    echo "End - Extension for stage: ${piperParams.stageName}"
   573  }
   574  return this
   575  ```
   576  
   577  ## Debugging
   578  
   579  Debugging can be initiated with VS code fairly easily. Compile the binary with specific compiler flags to turn off optimizations `go build -gcflags "all=-N -l" -o piper.exe`.
   580  
   581  Modify the `launch.json` located in folder `.vscode` of your project root to point with `program` exactly to the binary that you just built with above command - must be an absolute path. Add any arguments required for the execution of the Piper step to `args`. What is separated with a blank on the command line must go into a separate string.
   582  
   583  ```javascript
   584  {
   585      // Use IntelliSense to learn about possible attributes.
   586      // Hover to view descriptions of existing attributes.
   587      // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
   588      "version": "0.2.0",
   589      "configurations": [
   590          {
   591              "name": "Launch",
   592              "type": "go",
   593              "request": "launch",
   594              "mode": "exec",
   595              "program": "C:/CF@HCP/git/jenkins-library-public/piper.exe",
   596              "env": {},
   597              "args": ["checkmarxExecuteScan", "--password", "abcd", "--username", "1234", "--projectName", "testProject4711", "--serverUrl", "https://cx.server.com/"]
   598          }
   599      ]
   600  }
   601  ```
   602  
   603  Finally, set your breakpoints and use the `Launch` button in the VS code UI to start debugging.
   604  
   605  ## Release
   606  
   607  Releases are performed using GitHub workflows and [Project "Piper" Action](https://github.com/SAP/project-piper-action).
   608  
   609  There are two different workflows:
   610  
   611  - [weekly release workflow](.github/workflows/release-go.yml) running every Monday at 09:00 UTC.
   612  - [commit-based workflow](.github/workflows/upload-go-master.yml) which releases the binary as `piper_master` on the [latest release](https://github.com/SAP/jenkins-library/releases/latest).
   613  
   614  It is also possible to release on demand using the `contrib/perform-release.sh` script with a personal access token (`repo` scope).
   615  
   616  ```
   617  PIPER_RELEASE_TOKEN=<token> contrib/perform-release.sh
   618  ```
   619  
   620  ## Pipeline Configuration
   621  
   622  The pipeline configuration is organized in a hierarchical manner and configuration parameters are incorporated from multiple sources.
   623  In general, there are four sources for configurations:
   624  
   625  1. Directly passed step parameters
   626  1. Project specific configuration placed in `.pipeline/config.yml`
   627  1. Custom default configuration provided in `customDefaults` parameter of the project config or passed as parameter to the step `setupCommonPipelineEnvironment`
   628  1. Default configuration from Piper library
   629  
   630  For more information and examples on how to configure a project, please refer to the [configuration documentation](https://sap.github.io/jenkins-library/configuration/).
   631  
   632  ### Groovy vs. Go step configuration
   633  
   634  The configuration of a project is, as of now, resolved separately for Groovy and Go steps.
   635  There are, however, dependencies between the steps responsible for resolving the configuration.
   636  The following provides an overview of the central components and their dependencies.
   637  
   638  #### setupCommonPipelineEnvironment (Groovy)
   639  
   640  The step `setupCommonPipelineEnvironment` initializes the `commonPipelineEnvironment` and `DefaultValueCache`.
   641  Custom default configurations can be provided as parameters to `setupCommonPipelineEnvironment` or via the `customDefaults` parameter in project configuration.
   642  
   643  #### DefaultValueCache (Groovy)
   644  
   645  The `DefaultValueCache` caches the resolved (custom) default pipeline configuration and the list of configurations that contributed to the result.
   646  On initialization, it merges the provided custom default configurations with the default configuration from Piper library, as per the hierarchical order.
   647  
   648  Note, the list of configurations cached by `DefaultValueCache` is used to pass path to the (custom) default configurations of each Go step.
   649  It only contains the paths of configurations which are **not** provided via `customDefaults` parameter of the project configuration, since the Go layer already resolves configurations provided via `customDefaults` parameter independently.
   650  
   651  ## Additional Developer Hints
   652  
   653  You can find additional hints at [documentation/developer-hints](./documentation/developer_hints)
   654  
   655  ## Security Setup
   656  
   657  Here some hints and tricks are described to enhance the security within the development process.
   658  
   659  1. [Signing Commits](#signing-commits)
   660  
   661  ### Signing Commits
   662  
   663  In git, commits can be [signed](https://git-scm.com/book/en/v2/Git-Tools-Signing-Your-Work) to guarantee that that changes were made by the person named in the commit.
   664  The name and email used for commits can be easily modified in the local git setup and afterwards it cannot be distinguished anymore if the commit was done by the real person or by some potential attacker.
   665  
   666  In Windows, this can be done via [GnuPG](https://www.gnupg.org/(en)/download/index.html).
   667  Download and install the tool.
   668  Via the manager tool *Kleopatra* a new key pair can be easily created with a little wizard.
   669  Make sure that the name and email are the ones used in your git.
   670  
   671  The public key must then be added to the github's GPG section.
   672  The private key should be kept in a backup as this signature is bound to you and not your machine.
   673  
   674  The only thing left are some changes in the *.gitconfig* file.
   675  The file shall be located in your user directory.
   676  It might look something like the following.
   677  All parts that are not relevant for signing were removed.
   678  
   679  ```
   680  [user]
   681    name = My Name
   682    email = my.name@sap.com
   683    # Hash or email of you GPG key
   684    signingkey = D3CF72CC4006DE245C049566242831AEEE9DA2DD
   685  [commit]
   686    # enable signing for commits
   687    gpgsign = true
   688  [tag]
   689    # enable signing for tags (note the capital S)
   690    gpgSign = true
   691  [gpg]
   692    # Windows was not able to find the private key. Setting the gpg command to use solved this.
   693    program = C:\\Program Files (x86)\\GnuPG\\bin\\gpg.exe
   694  ```
   695  
   696  Add the three to four lines to you git config and this will do the necessary such that all your commits will be signed.