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.