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.