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