├── .github ├── pull_request_template.md ├── release-drafter.yml └── workflows │ ├── CI.yml │ └── docs.yaml ├── .gitignore ├── .groovylintrc.json ├── .markdownlint-cli2.yaml ├── .vale.ini ├── Copyright and License ├── Justfile ├── LICENSE.md ├── README.md ├── build.gradle ├── catalog-info.yaml ├── docs ├── .pages ├── assets │ ├── attachments │ │ ├── google_lighthouse │ │ │ └── google_lighthouse.pdf │ │ ├── pytest │ │ │ └── pytest.pdf │ │ ├── sysdig_secure │ │ │ └── sysdig_secure_report.pdf │ │ └── webhint │ │ │ └── webhint_mockaroo.pdf │ └── images │ │ ├── a11y │ │ ├── index.png │ │ └── report.png │ │ ├── jenkins │ │ └── pinning-sdp-libraries-to-a-specific-release.png │ │ ├── jte.svg │ │ ├── openshift │ │ └── Openshift_deploy_to_diagram.png │ │ └── owasp_zap │ │ └── report.png ├── concepts │ ├── overview.md │ └── unit-testing │ │ ├── executing-tests.md │ │ ├── faq.md │ │ ├── index.md │ │ ├── jenkins-spock.md │ │ └── writing-tests.md ├── contributing │ ├── create-new-library.md │ └── index.md ├── css │ └── extra.css ├── glossary.md ├── how-to │ ├── .pages │ ├── overview.md │ └── pin-a-library-source-to-a-specific-release.md ├── index.md ├── styles │ ├── Microsoft │ │ ├── AMPM.yml │ │ ├── Accessibility.yml │ │ ├── Acronyms.yml │ │ ├── Adverbs.yml │ │ ├── Auto.yml │ │ ├── Avoid.yml │ │ ├── ComplexWords.yml │ │ ├── Contractions.yml │ │ ├── Dashes.yml │ │ ├── DateFormat.yml │ │ ├── DateNumbers.yml │ │ ├── DateOrder.yml │ │ ├── Ellipses.yml │ │ ├── FirstPerson.yml │ │ ├── Foreign.yml │ │ ├── Gender.yml │ │ ├── GenderBias.yml │ │ ├── GeneralURL.yml │ │ ├── HeadingColons.yml │ │ ├── HeadingPunctuation.yml │ │ ├── Headings.yml │ │ ├── Hyphens.yml │ │ ├── Negative.yml │ │ ├── Ordinal.yml │ │ ├── OxfordComma.yml │ │ ├── Passive.yml │ │ ├── Percentages.yml │ │ ├── Quotes.yml │ │ ├── RangeFormat.yml │ │ ├── RangeTime.yml │ │ ├── Ranges.yml │ │ ├── Semicolon.yml │ │ ├── SentenceLength.yml │ │ ├── Spacing.yml │ │ ├── Suspended.yml │ │ ├── Terms.yml │ │ ├── URLFormat.yml │ │ ├── Units.yml │ │ ├── Vocab.yml │ │ ├── We.yml │ │ ├── Wordiness.yml │ │ └── meta.json │ └── Vocab │ │ └── SDP │ │ └── accept.txt └── tutorials │ └── overview.md ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── libraries ├── README.md ├── a11y │ ├── README.md │ ├── steps │ │ └── accessibility_compliance_test.groovy │ └── test │ │ └── AccessibilityComplianceTestSpec.groovy ├── anchore │ ├── README.md │ ├── library_config.groovy │ └── steps │ │ ├── add_registry_creds.groovy │ │ └── scan_container_image.groovy ├── cypress │ ├── README.md │ ├── library_config.groovy │ ├── steps │ │ └── end_to_end_test.groovy │ └── test │ │ └── EndToEndTestSpec.groovy ├── docker │ ├── README.md │ ├── steps │ │ ├── build.groovy │ │ ├── buildx.groovy │ │ ├── get_images_to_build.groovy │ │ ├── get_registry_info.groovy │ │ ├── login_to_registry.groovy │ │ └── retag.groovy │ └── test │ │ ├── BuildSpec.groovy │ │ ├── BuildxSpec.groovy │ │ ├── GetImagesToBuildSpec.groovy │ │ ├── GetRegistryInfoSpec.groovy │ │ ├── LoginToRegistrySpec.groovy │ │ └── RetagSpec.groovy ├── docker_compose │ ├── README.md │ ├── steps │ │ └── compose.groovy │ └── test │ │ └── DockerComposeSpec.groovy ├── dotnet │ ├── README.md │ ├── steps │ │ └── dotnet_invoke.groovy │ └── test │ │ └── DotNetInvokeSpec.groovy ├── git │ ├── README.md │ ├── library_config.groovy │ ├── steps │ │ ├── git.groovy │ │ ├── git_distributions.groovy │ │ ├── github.groovy │ │ ├── github_enterprise.groovy │ │ ├── gitlab.groovy │ │ ├── gitlab_status.groovy │ │ ├── on_change.groovy │ │ ├── on_commit.groovy │ │ ├── on_merge.groovy │ │ ├── on_pull_request.groovy │ │ └── withGit.groovy │ └── test │ │ ├── GitDistributionsSpec.groovy │ │ ├── GitSpec.groovy │ │ └── GitlabStatusSpec.groovy ├── google_lighthouse │ ├── README.md │ ├── library_config.groovy │ └── steps │ │ └── accessibility_compliance_scan.groovy ├── grype │ ├── README.md │ ├── library_config.groovy │ ├── resources │ │ └── transform-grype-scan-results.sh │ ├── steps │ │ └── container_image_scan.groovy │ └── test │ │ └── ContainerImageScanSpec.groovy ├── kubernetes │ ├── README.md │ └── steps │ │ ├── deploy_to.groovy │ │ └── ephemeral.groovy ├── maven │ ├── README.md │ ├── steps │ │ └── maven_invoke.groovy │ └── test │ │ └── MavenInvokeSpec.groovy ├── npm │ ├── README.md │ ├── steps │ │ └── npm_invoke.groovy │ └── test │ │ └── NpmInvokeSpec.groovy ├── openshift │ ├── README.md │ ├── steps │ │ ├── deploy_to.groovy │ │ └── ephemeral.groovy │ └── test │ │ ├── DeployToSpec.groovy │ │ └── EphemeralSpec.groovy ├── owasp_dep_check │ ├── README.md │ ├── steps │ │ └── application_dependency_scan.groovy │ └── test │ │ └── ApplicationDependencySpec.groovy ├── owasp_zap │ ├── README.md │ ├── steps │ │ └── penetration_test.groovy │ └── test │ │ └── PenetrationTestSpec.groovy ├── protractor │ ├── README.md │ └── steps │ │ └── functional_test.groovy ├── pytest │ ├── README.md │ ├── library_config.groovy │ └── steps │ │ └── unit_test.groovy ├── sdp │ ├── README.md │ ├── steps │ │ ├── archive_pipeline_config.groovy │ │ ├── create_workspace_stash.groovy │ │ ├── inside_sdp_image.groovy │ │ └── jteVersion.groovy │ └── test │ │ └── InsideSdpImageSpec.groovy ├── slack │ ├── README.md │ ├── steps │ │ └── slack.groovy │ └── test │ │ └── SlackSpec.groovy ├── sonarqube │ ├── README.md │ ├── library_config.groovy │ └── steps │ │ └── static_code_analysis.groovy ├── syft │ ├── README.md │ ├── library_config.groovy │ ├── steps │ │ └── generate_sbom.groovy │ └── test │ │ └── GenerateSBOMSpec.groovy ├── sysdig_secure │ ├── README.md │ ├── library_config.groovy │ └── steps │ │ └── scan_container_image.groovy ├── terraform │ ├── README.md │ └── steps │ │ └── deploy_to.groovy ├── twistlock │ ├── README.md │ ├── steps │ │ └── scan_container_image.groovy │ └── test │ │ └── ScanContainerImageSpec.groovy ├── webhint │ ├── README.md │ ├── library_config.groovy │ └── steps │ │ └── accessibility_compliance_scan.groovy └── yarn │ ├── README.md │ ├── steps │ └── yarn_invoke.groovy │ └── test │ └── YarnInvokeSpec.groovy ├── mkdocs.yml ├── resources ├── docs │ ├── Dockerfile │ ├── README.template.md │ ├── add_glossary.py │ └── copy_docs.py └── test │ ├── JTEPipelineSpecification.groovy │ └── jacocoSpec.groovy └── sonar-project.properties /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | # PR Details 2 | 3 | 4 | 5 | ## Description 6 | 7 | 8 | 9 | ## How Has This Been Tested 10 | 11 | 12 | 13 | 14 | 15 | ## Types of Changes 16 | 17 | 18 | 19 | - [ ] Docs change / refactoring / dependency upgrade 20 | - [ ] Bug fix (non-breaking change which fixes an issue) 21 | - [ ] New feature (non-breaking change which adds functionality) 22 | - [ ] Breaking change (fix or feature that would cause existing functionality to change) 23 | 24 | ## Checklist 25 | 26 | 27 | 28 | 29 | - [ ] I am submitting this pull request to the appropriate branch 30 | - [ ] I have labeled this pull request appropriately 31 | - [ ] I have updated the documentation accordingly. 32 | - [ ] All new and existing tests passed. 33 | -------------------------------------------------------------------------------- /.github/release-drafter.yml: -------------------------------------------------------------------------------- 1 | --- 2 | categories: 3 | - title: 🚀 Features 4 | label: enhancement 5 | - title: 🐛 Bug Fixes 6 | label: bug 7 | - title: 📖 Documentation 8 | label: documentation 9 | exclude-labels: 10 | - 'skip-changelog' 11 | change-template: '- $TITLE @$AUTHOR (#$NUMBER)' 12 | template: | 13 | < insert a general overview of the release > 14 | ## What's Changed: 15 | $CHANGES 16 | -------------------------------------------------------------------------------- /.github/workflows/CI.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | pull_request: 4 | branches: 5 | - main 6 | jobs: 7 | MarkdownLint: 8 | runs-on: ubuntu-latest 9 | if: github.repository == 'boozallen/sdp-libraries' 10 | container: 11 | image: davidanson/markdownlint-cli2:0.4.0 12 | options: --user root 13 | steps: 14 | - uses: actions/checkout@v2 15 | - name: markdownlint-cli2 16 | run: markdownlint-cli2 17 | Vale: 18 | runs-on: ubuntu-latest 19 | if: github.repository == 'boozallen/sdp-libraries' 20 | container: 21 | image: jdkato/vale:v2.18.0 22 | options: --user root 23 | steps: 24 | - uses: actions/checkout@v2 25 | - name: vale 26 | run: vale docs libraries 27 | Unit_Test: 28 | runs-on: ubuntu-latest 29 | if: github.repository == 'boozallen/sdp-libraries' 30 | steps: 31 | - uses: actions/checkout@v1 32 | - uses: actions/setup-java@v1 33 | with: 34 | java-version: 8 35 | - uses: eskatos/gradle-command-action@v1 36 | with: 37 | arguments: --no-daemon test 38 | - uses: actions/upload-artifact@v1 39 | with: 40 | name: test-results 41 | path: target/reports 42 | -------------------------------------------------------------------------------- /.github/workflows/docs.yaml: -------------------------------------------------------------------------------- 1 | name: docs 2 | on: 3 | push: 4 | branches: 5 | - main 6 | jobs: 7 | DeployDocs: 8 | runs-on: ubuntu-latest 9 | if: github.repository == 'boozallen/sdp-libraries' 10 | steps: 11 | - uses: actions/checkout@v2 12 | with: 13 | fetch-depth: 0 14 | - uses: extractions/setup-just@v1 15 | - name: "Deploy the Docs" 16 | run: just update-docs 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore Gradle project-specific cache directory 2 | .gradle 3 | 4 | .classpath 5 | .project 6 | .settings 7 | bin 8 | 9 | # Ignore Gradle build output directory 10 | site/ 11 | target/ 12 | 13 | # Local Netlify folder 14 | .netlify 15 | 16 | # .DS_Store 17 | .DS_Store 18 | 19 | # .Jenv file 20 | .java-version 21 | -------------------------------------------------------------------------------- /.groovylintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "recommended-jenkinsfile", 3 | "rules": { 4 | "CouldBeElvis": "off", 5 | "CouldBeSwitchStatement": "off", 6 | "VariableName": { 7 | "severity": "info" 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /.markdownlint-cli2.yaml: -------------------------------------------------------------------------------- 1 | config: 2 | # prefer semantic line breaks over max setnence length 3 | line-length: false 4 | # tabbed code examples violate this rule 5 | no-space-in-code: false 6 | # sometimes you gotta be hacky 7 | no-inline-html: false 8 | 9 | fix: true 10 | 11 | globs: 12 | - "**.md" 13 | ignores: 14 | - docs/styles 15 | - LICENSE.md -------------------------------------------------------------------------------- /.vale.ini: -------------------------------------------------------------------------------- 1 | StylesPath = docs/styles 2 | Vocab = SDP 3 | [*.md] 4 | BasedOnStyles = Microsoft, Vale 5 | # ignores tabbed content from pymdownx.tabbed 6 | BlockIgnores = (?s) *(=== *("|').*("|')( *|)\n( *|)\x60\x60\x60([^\x60]*|\n*)?\x60\x60\x60) 7 | # ignores faq code admonitions 8 | TokenIgnores = (\?\?\?\+?\sfaq) -------------------------------------------------------------------------------- /Copyright and License: -------------------------------------------------------------------------------- 1 | Copyright © 2018 Booz Allen Hamilton. All Rights Reserved. 2 | This software package is licensed under the Booz Allen Public License. The license can be found here: http://boozallen.github.io/licenses/bapl -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SDP Pipeline Libraries 2 | 3 | This repository contains [Booz Allen's](https://boozallen.com) pipeline libraries that integrate with the [Jenkins Templating Engine](https://plugins.jenkins.io/templating-engine/). 4 | 5 | If you want to learn more, the best place to get started is the [documentation](https://boozallen.github.io/sdp-docs/sdp-libraries/). 6 | 7 | ## Usage 8 | 9 | In order to use the different libraries in this repository, you can configure this repository as a library source, for a detailed example of how to do this you may refer to [this lab](https://boozallen.github.io/sdp-docs/learning-labs/1/jte-the-basics/3-first-libraries.html#_configure_the_library_source). 10 | 11 | It is recommended that rather than using the master branch you pin your library source to a particular github release such as: [like 2.0]. This helps to ensure that you have greater control in version management. 12 | 13 | Also ensure that in addition to whichever library you wish to use you include the `sdp` library. This helps to resolve a number of dependency errors you may otherwise face. 14 | 15 | ### Configuring the sdp library 16 | 17 | As a dependency for every other library, it is important that the sdp library not only be included but also configured properly. For instructions on how to configure this library, please reference the [sdp guide](https://boozallen.github.io/sdp-docs/sdp-libraries/libraries/sdp.html) 18 | 19 | ## Repository Structure 20 | 21 | A detailed description is available in the [contributing guide](./docs/contributing/index.md) 22 | 23 | ## Contributing 24 | 25 | We accept contributions via a fork-based development workflow. See the [contributing guide](./docs/contributing/index.md). 26 | -------------------------------------------------------------------------------- /catalog-info.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: backstage.io/v1alpha1 2 | kind: Component 3 | metadata: 4 | name: sdp-libraries 5 | title: Solutions Delivery Platform (SDP) Libraries 6 | description: 'The Solutions Delivery Platform Pipeline Libraries for the Jenkins Templating Engine (JTE)' 7 | annotations: 8 | github.com/project-slug: boozallen/sdp-libraries 9 | tags: 10 | - pipeline 11 | - sdp 12 | - solutions-delivery-platform 13 | - jenkins 14 | - sonarqube 15 | - devsecops 16 | - devops 17 | - supply-chain-security 18 | - reusable 19 | links: 20 | - url: https://boozallen.github.io/sdp-docs/sdp-libraries/ 21 | title: Documentation Website 22 | spec: 23 | type: docs 24 | lifecycle: production 25 | owner: uip/software-studio 26 | system: system:infrastructure-and-pipeline 27 | -------------------------------------------------------------------------------- /docs/.pages: -------------------------------------------------------------------------------- 1 | nav: 2 | - "Home": 'index.md' 3 | - concepts 4 | - tutorials 5 | - how-to 6 | - libraries 7 | - contributing -------------------------------------------------------------------------------- /docs/assets/attachments/google_lighthouse/google_lighthouse.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boozallen/sdp-libraries/a2186fa3e8ecf9af1f8f83fdef9a06a05545fdf1/docs/assets/attachments/google_lighthouse/google_lighthouse.pdf -------------------------------------------------------------------------------- /docs/assets/attachments/pytest/pytest.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boozallen/sdp-libraries/a2186fa3e8ecf9af1f8f83fdef9a06a05545fdf1/docs/assets/attachments/pytest/pytest.pdf -------------------------------------------------------------------------------- /docs/assets/attachments/sysdig_secure/sysdig_secure_report.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boozallen/sdp-libraries/a2186fa3e8ecf9af1f8f83fdef9a06a05545fdf1/docs/assets/attachments/sysdig_secure/sysdig_secure_report.pdf -------------------------------------------------------------------------------- /docs/assets/attachments/webhint/webhint_mockaroo.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boozallen/sdp-libraries/a2186fa3e8ecf9af1f8f83fdef9a06a05545fdf1/docs/assets/attachments/webhint/webhint_mockaroo.pdf -------------------------------------------------------------------------------- /docs/assets/images/a11y/index.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boozallen/sdp-libraries/a2186fa3e8ecf9af1f8f83fdef9a06a05545fdf1/docs/assets/images/a11y/index.png -------------------------------------------------------------------------------- /docs/assets/images/a11y/report.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boozallen/sdp-libraries/a2186fa3e8ecf9af1f8f83fdef9a06a05545fdf1/docs/assets/images/a11y/report.png -------------------------------------------------------------------------------- /docs/assets/images/jenkins/pinning-sdp-libraries-to-a-specific-release.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boozallen/sdp-libraries/a2186fa3e8ecf9af1f8f83fdef9a06a05545fdf1/docs/assets/images/jenkins/pinning-sdp-libraries-to-a-specific-release.png -------------------------------------------------------------------------------- /docs/assets/images/openshift/Openshift_deploy_to_diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boozallen/sdp-libraries/a2186fa3e8ecf9af1f8f83fdef9a06a05545fdf1/docs/assets/images/openshift/Openshift_deploy_to_diagram.png -------------------------------------------------------------------------------- /docs/assets/images/owasp_zap/report.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boozallen/sdp-libraries/a2186fa3e8ecf9af1f8f83fdef9a06a05545fdf1/docs/assets/images/owasp_zap/report.png -------------------------------------------------------------------------------- /docs/concepts/overview.md: -------------------------------------------------------------------------------- 1 | # Overview 2 | 3 | !!! abstract "Coming Soon!" 4 | Concepts is next up on the priority list after this initial release of the new docs site is over! 5 | -------------------------------------------------------------------------------- /docs/concepts/unit-testing/executing-tests.md: -------------------------------------------------------------------------------- 1 | # Test Execution 2 | 3 | In SDP Pipeline Libraries, tests are placed in a directory called `test` within the library directory. 4 | 5 | ## Executing tests with Gradle 6 | 7 | --- 8 | 9 | This repository has been set up to run Jenkins-Spock tests using Gradle. 10 | 11 | Currently, Gradle 6.3.0 running on JDK 8 is required. These can be downloaded on OSX via: 12 | 13 | ```bash 14 | # gradle via sdkman: https://sdkman.io/ 15 | curl -s "https://get.sdkman.io" | bash 16 | source "$(pwd)/.sdkman/bin/sdkman-init.sh" 17 | sdk install gradle 6.3 18 | 19 | # java8 20 | brew install adoptopenjdk8 21 | export JAVA_8_HOME=$(/usr/libexec/java_home -v1.8) 22 | alias java8='export JAVA_HOME=$JAVA_8_HOME' 23 | java8 24 | ``` 25 | 26 | See the [Contributing Guide](../../contributing/index.md) for instructions on running unit tests. 27 | -------------------------------------------------------------------------------- /docs/concepts/unit-testing/faq.md: -------------------------------------------------------------------------------- 1 | # Frequently Asked Questions 2 | 3 | *This section covers questions that aren't answered in the Spock or Jenkins-Spock documentation.* 4 | 5 | ???+ faq "What's the difference between explicitlyMockPipelineStep and explicitlyMockPipelineVariable?" 6 | 7 | **Practically speaking, the difference is you can omit ".call" for explicitlyMockPipelineStep() when you use getPipelineMock()** 8 | 9 | In the example on the [Writing Tests](./writing-tests.md) page, ``explicitlyMockPipelineStep()`` is used to mock `error`. 10 | In this case, `1 * getPipelineMock("error")` can be used to see if the `error` pipeline step is run. 11 | If `explicitlyMockPipelineVariable()` had been used instead, `1 * getPipelineMock("error.call")` could be used to check for the `error` pipeline step. 12 | 13 | There may be some additional differences as well, so try to use what makes the most sense. 14 | 15 | ???+ faq "What if the exact parameters are unknown?" 16 | 17 | **There are ways to match parameters to regex expressions, as well as test parameters individually** 18 | 19 | The standard format for interaction-based tests is: 20 | 21 | ```groovy 22 | * getPipelineMock()() 23 | ``` 24 | 25 | While you can put the exact parameter value in the second parentheses, you can also run arbitrary groovy code inside curly brackets. 26 | If it's a "match" depends on if that code returns `true` or `false`. 27 | A good example is in [PenetrationTestSpec.groovy](https://github.com/boozallen/sdp-libraries/blob/main/libraries/owasp_zap/test/PenetrationTestSpec.groovy#L33). Use `it` to get the value of the parameter: 28 | `1 * getPipelineMock("sh")({it =~ / (zap-cli open-url) Kirk (.+)/})`. 29 | 30 | ???+ faq "Is interaction-based testing required?" 31 | 32 | **No, but you can't get variables the same way as traditional Spock tests** 33 | 34 | This is because the script gets run within the `loadPipelineScriptForTest` object. 35 | You can only access the limited set of variables stored in the binding. 36 | It makes more sense to see how variables are being used in pipeline steps, 37 | and make sure those pipeline steps use the correct value for those variables. 38 | 39 | Similarly, if you need to control how a variable is set, 40 | you need to stub whatever method or pipeline step sets the initial value for that variable. 41 | 42 | As an example, in [PenetrationTestSpec.groovy](https://github.com/boozallen/sdp-libraries/blob/main/libraries/owasp_zap/test/PenetrationTestSpec.groovy), 43 | the `target` variable in [penetration_test.groovy](https://github.com/boozallen/sdp-libraries/blob/main/libraries/owasp_zap/steps/penetration_test.groovy) is tested by checking the parameters to an `sh` step. 44 | 45 | ???+ faq "What troubleshooting steps are required for 'can't run method foo() on null' errors?" 46 | 47 | **You need to find a way to stub the method that sets the value for the object that calls foo()** 48 | 49 | Check out an example in [GetImagesToBuildSpec.groovy](https://github.com/boozallen/sdp-libraries/blob/main/libraries/docker/test/GetImagesToBuildSpec.groovy). 50 | -------------------------------------------------------------------------------- /docs/concepts/unit-testing/index.md: -------------------------------------------------------------------------------- 1 | # Overview 2 | 3 | The purpose of writing unit tests for pipeline libraries is to confirm that the libraries functions as expected. 4 | It is also a way to confirm that any features that were added to a library didn't inadvertently break other features. 5 | -------------------------------------------------------------------------------- /docs/concepts/unit-testing/jenkins-spock.md: -------------------------------------------------------------------------------- 1 | # Jenkins Spock 2 | 3 | Pipeline libraries are tested using [Jenkins-Spock](https://github.com/homeaway/jenkins-spock), 4 | a variation of the [Spock](http://spockframework.org/spock/docs) testing framework that has been designed around testing Jenkins pipelines. 5 | 6 | ## Writing a Specification File 7 | 8 | --- 9 | 10 | A **specification** is a list of features derived from business requirements. 11 | A specification file contains that list of features as unit tests, and those tests validate that the features work as expected. 12 | There should be a separate file for each pipeline step in your library. 13 | 14 | Below is an outline of a specification file. 15 | It shows what you need to include to run tests, as well as some conventions for what to name methods and variables. 16 | Create a groovy file with the same name as the class (such as `MyPipelineStepSpec.groovy`) 17 | and use this outline to get started, making sure to swap names with ones for your library. 18 | 19 | ## Sample Specification 20 | 21 | --- 22 | 23 | 1. Import the framework 24 | 2. Create a class extending `JTEPipelineSpecification` 25 | 3. Create a field to house the loaded step 26 | 4. Define a setup method where you will load the step 27 | 5. Write a test 28 | 29 | ```groovy 30 | // Create a new class for the Spec 31 | // The naming convention is the pipeline step's name, followed by Spec, 32 | // all camel-cased starting w/ a capital. 33 | public class MyPipelineStepSpec extends JTEPipelineSpecification { 34 | 35 | // Define the variable that will store the step's groovy code. This variable 36 | // follows the same naming variable as the class name, with Spec omitted. 37 | def MyPipelineStep = null 38 | 39 | // setup() is a fixture method that gets run before every test. 40 | // http://spockframework.org/spock/docs/1.2/spock_primer.html#_fixture_methods 41 | def setup() { 42 | // It's required to load the pipeline script as part of setup() 43 | // With the library monorepo, pipeline step groovy files can be found in "sdp/libraries" 44 | MyPipelineStep = loadPipelineScriptForTest("sdp/libraries/my_library/my_pipeline_step.groovy") 45 | } 46 | 47 | // Write a test (i.e. Feature Method) for each feature you wish to validate 48 | // http://spockframework.org/spock/docs/1.2/spock_primer.html#_feature_methods 49 | def "Successful Build Sends Success Result" () { 50 | setup: 51 | // unlike in the pipeline, the config object is not loaded during 52 | // unit tests. Use this to set it manually 53 | MyPipelineStep.getBinding().setVariable("config", [ field: "String" ]) 54 | when: 55 | // This is the "stimulus". It does things to test what happens 56 | // Typically, you execute the step's groovy code like this 57 | MyPipelineStep() 58 | then: 59 | // Here's where you describe the expected response 60 | // If everything in here is valid, the test passes 61 | 1 * getPipelineMock("sh")("echo \"field = String\"") 62 | } 63 | 64 | } 65 | ``` 66 | -------------------------------------------------------------------------------- /docs/concepts/unit-testing/writing-tests.md: -------------------------------------------------------------------------------- 1 | # Writing Tests 2 | 3 | Now that you've laid the groundwork for your tests, it's time to write them. 4 | These are the "Feature Methods" because there should be one for each feature. 5 | Some things to write tests for are: 6 | 7 | * Things are built correctly (objects, string variables, maps, etc.) 8 | * Conditional Hierarchies function as expected 9 | * Variables get passed correctly 10 | * Things fail when they're supposed to 11 | 12 | Once you know the feature you're testing, like "Pipeline Fails When Config Is Undefined," write a feature method for it: 13 | 14 | ```groovy 15 | def "Pipeline Fails When Config Is Undefined" () { 16 | 17 | } 18 | ``` 19 | 20 | Now create a `setup:` block to define some do some pre-test preparation not covered by the `setup()` fixture method. 21 | In this example, the binding variable `config` is set to `null`, and a mock for the `error` pipeline step is created. 22 | 23 | ```groovy 24 | def "Pipeline Fails When Config Is Undefined" () { 25 | setup: 26 | explicitlyMockPipelineStep("error") 27 | MyPipelineStep.getBinding().setVariable("config", null) 28 | } 29 | ``` 30 | 31 | The next step is to execute the pipeline step and test the response. 32 | This happens in the `when:` and `then:` blocks, respectively. 33 | In this example, the pipeline step is called (with no parameters), 34 | and expects the `error` step to be called exactly once with the message `"ERROR: config is not defined"` 35 | 36 | ```groovy 37 | def "Pipeline Fails When Config Is Undefined" () { 38 | setup: 39 | explicitlyMockPipelineStep("error") 40 | MyPipelineStep.getBinding().setVariable("config", null) 41 | when: 42 | MyPipelineStep() // Run the pipeline step we loaded, with no parameters 43 | then: 44 | 1 * getPipelineMock("error")("ERROR: config is not defined") 45 | } 46 | ``` 47 | 48 | And that's the gist of it. 49 | You can add as many feature methods as necessary in the spec file, testing a variety of things. 50 | Be sure to check out the [Spock Documentation](http://spockframework.org/spock/docs), [Jenkins-Spock Documentation](./jenkins-spock.md), and already-created spec files in the [SDP Libraries repository](https://github.com/boozallen/sdp-libraries/tree/main/resources/test) for examples. 51 | -------------------------------------------------------------------------------- /docs/css/extra.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --gray-darker: #444444; 3 | --gray-dark: #696969; 4 | --gray: #999999; 5 | --gray-light: #cccccc; 6 | --gray-lighter: #ececec; 7 | --gray-lightest: #ededed; 8 | } 9 | 10 | *, 11 | *::before, 12 | *::after { 13 | box-sizing: border-box; 14 | } 15 | 16 | .cards { 17 | display: grid; 18 | grid-gap: 1rem; 19 | margin: 0 auto; 20 | margin-left: 20%; 21 | margin-right: 20%; 22 | max-width: 1000px; 23 | grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); 24 | } 25 | 26 | .card { 27 | border-radius: 0.5rem; 28 | min-height: 10rem; 29 | box-shadow: 0 10px 20px 0px rgba(0,0,0,0.25); 30 | transition: box-shadow 0.3s ease-in-out; 31 | display: flex; 32 | flex-direction: column; 33 | overflow: hidden; 34 | } 35 | 36 | .card:hover{ 37 | box-shadow: 0 20px 40px 0px rgba(0,0,0,0.25); 38 | } 39 | 40 | .card__content { 41 | display: flex; 42 | flex: 1 1 auto; 43 | flex-direction: column; 44 | padding: 1rem; 45 | color: black; 46 | } 47 | 48 | .card__title { 49 | color: var(--md-primary-fg-color); 50 | font-size: 1.25rem; 51 | font-weight: 300; 52 | letter-spacing: 2px; 53 | text-transform: uppercase; 54 | } 55 | 56 | .card__title svg { 57 | margin-right: 5px; 58 | } 59 | 60 | .card__text { 61 | flex: 1 1 auto; 62 | font-size: 0.875rem; 63 | line-height: 1.5; 64 | margin-bottom: 1.25rem; 65 | } 66 | 67 | /* to center an image */ 68 | .center{ 69 | display: block; 70 | margin: 0 auto; 71 | } 72 | 73 | /* table cell align vertical middle */ 74 | .md-typeset table:not([class]) td { 75 | vertical-align: middle; 76 | } -------------------------------------------------------------------------------- /docs/glossary.md: -------------------------------------------------------------------------------- 1 | 2 | *[SDP]: Solutions Delivery Platform 3 | *[BAH]: Booz Allen Hamilton 4 | *[DSL]: Domain Specific Language 5 | *[DSLs]: Domain Specific Languages 6 | *[JTE]: Jenkins Templating Engine 7 | *[SCM]: Source Code Management 8 | *[IDE]: Integrated Development Environment, like VS Code 9 | *[NPM]: Node Package Manager 10 | *[NVM]: Node Version Manager 11 | *[DRY]: Don't Repeat Yourself 12 | *[AWS]: Amazon Web Services 13 | *[JDK]: Java Development Kit 14 | *[PR]: Pull Request 15 | *[JSON]: JavaScript Object Notation 16 | *[CVE]: Common Vulnerabilities and Exposures 17 | *[CLI]: Command Line Interface 18 | *[SBOM]: Software Bill of Materials -------------------------------------------------------------------------------- /docs/how-to/.pages: -------------------------------------------------------------------------------- 1 | title: How To -------------------------------------------------------------------------------- /docs/how-to/overview.md: -------------------------------------------------------------------------------- 1 | # Overview 2 | 3 | How-To Guides are goal oriented step-by-step instructions for specific problems. 4 | 5 | | How-To Guide | Description | 6 | | ------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- | 7 | | [Pin a Library Source to a Specific Release](pin-a-library-source-to-a-specific-release.md) | Teaches you how to pin a Library Source, such as the SDP Libraries to a specific release. | 8 | -------------------------------------------------------------------------------- /docs/how-to/pin-a-library-source-to-a-specific-release.md: -------------------------------------------------------------------------------- 1 | # Pin a Library Source to a Specific Release 2 | 3 | Breaking changes will sometimes be merged into the default branch of the SDP Libraries repository. Because of this, you may want to pin the Library Source to a specific release so that your pipelines don't break when the library is updated. 4 | 5 | ## Steps 6 | 7 | 1. Navigate to your Jenkins dashboard. 8 | 2. Click on "Manage Jenkins" on the left side panel. 9 | 3. Click on "Configure System" under the "System Configuration" section. 10 | 4. Scroll down to the "Jenkins Templating Engine" section of this page. 11 | 5. Find the Library Source you want to pin to a specific release. 12 | 6. Update the "Branch Specifier" field using the following format: `refs/tags/{git tag}` (replacing `{git tag}` with the tag you want to pin the Library Source to). 13 | 7. Click "Save" to save the changes. 14 | 15 | ![Pinning the boozallen/sdp-libraries Library Source to version 3.2](../assets/images/jenkins/pinning-sdp-libraries-to-a-specific-release.png) 16 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | hide: 3 | - navigation 4 | - toc 5 | --- 6 | 7 | # Solutions Delivery Platform 8 | 9 | Welcome! :wave: 10 | 11 | 45 | 46 | !!! note "Work In Progress" 47 | This docs site is still a work in progress! Check out the [contributing](./contributing/index.md) section if you're interested in helping. 48 | -------------------------------------------------------------------------------- /docs/styles/Microsoft/AMPM.yml: -------------------------------------------------------------------------------- 1 | extends: existence 2 | message: Use 'AM' or 'PM' (preceded by a space). 3 | link: https://docs.microsoft.com/en-us/style-guide/a-z-word-list-term-collections/term-collections/date-time-terms 4 | level: error 5 | nonword: true 6 | tokens: 7 | - '\d{1,2}[AP]M' 8 | - '\d{1,2} ?[ap]m' 9 | - '\d{1,2} ?[aApP]\.[mM]\.' 10 | -------------------------------------------------------------------------------- /docs/styles/Microsoft/Accessibility.yml: -------------------------------------------------------------------------------- 1 | extends: existence 2 | message: "Don't use language (such as '%s') that defines people by their disability." 3 | link: https://docs.microsoft.com/en-us/style-guide/a-z-word-list-term-collections/term-collections/accessibility-terms 4 | level: suggestion 5 | ignorecase: true 6 | tokens: 7 | - a victim of 8 | - able-bodied 9 | - affected by 10 | - an epileptic 11 | - crippled 12 | - disabled 13 | - dumb 14 | - handicapped 15 | - handicaps 16 | - healthy 17 | - lame 18 | - maimed 19 | - missing a limb 20 | - mute 21 | - normal 22 | - sight-impaired 23 | - stricken with 24 | - suffers from 25 | - vision-impaired 26 | -------------------------------------------------------------------------------- /docs/styles/Microsoft/Acronyms.yml: -------------------------------------------------------------------------------- 1 | extends: conditional 2 | message: "'%s' has no definition." 3 | link: https://docs.microsoft.com/en-us/style-guide/acronyms 4 | level: suggestion 5 | ignorecase: false 6 | # Ensures that the existence of 'first' implies the existence of 'second'. 7 | first: '\b([A-Z]{3,5})\b' 8 | second: '(?:\b[A-Z][a-z]+ )+\(([A-Z]{3,5})\)' 9 | # ... with the exception of these: 10 | exceptions: 11 | - API 12 | - ASP 13 | - CLI 14 | - CPU 15 | - CSS 16 | - CSV 17 | - DEBUG 18 | - DOM 19 | - DPI 20 | - FAQ 21 | - GCC 22 | - GDB 23 | - GET 24 | - GPU 25 | - GTK 26 | - GUI 27 | - HTML 28 | - HTTP 29 | - HTTPS 30 | - IDE 31 | - JAR 32 | - JSON 33 | - JSX 34 | - LESS 35 | - LLDB 36 | - NET 37 | - NOTE 38 | - NVDA 39 | - OSS 40 | - PATH 41 | - PDF 42 | - PHP 43 | - POST 44 | - RAM 45 | - REPL 46 | - RSA 47 | - SCM 48 | - SCSS 49 | - SDK 50 | - SQL 51 | - SSH 52 | - SSL 53 | - SVG 54 | - TBD 55 | - TCP 56 | - TODO 57 | - URI 58 | - URL 59 | - USB 60 | - UTF 61 | - XML 62 | - XSS 63 | - YAML 64 | - ZIP 65 | - DSL 66 | -------------------------------------------------------------------------------- /docs/styles/Microsoft/Auto.yml: -------------------------------------------------------------------------------- 1 | extends: existence 2 | message: "In general, don't hyphenate '%s'." 3 | link: https://docs.microsoft.com/en-us/style-guide/a-z-word-list-term-collections/a/auto 4 | ignorecase: true 5 | level: error 6 | action: 7 | name: convert 8 | params: 9 | - simple 10 | tokens: 11 | - 'auto-\w+' 12 | -------------------------------------------------------------------------------- /docs/styles/Microsoft/Avoid.yml: -------------------------------------------------------------------------------- 1 | extends: existence 2 | message: "Don't use '%s'." 3 | # See the A-Z word list 4 | link: https://docs.microsoft.com/en-us/style-guide 5 | ignorecase: true 6 | level: error 7 | tokens: 8 | - abortion 9 | - and so on 10 | - app(?:lication)s? (?:developer|program) 11 | - app(?:lication)? file 12 | - backbone 13 | - backend 14 | - contiguous selection 15 | -------------------------------------------------------------------------------- /docs/styles/Microsoft/ComplexWords.yml: -------------------------------------------------------------------------------- 1 | extends: substitution 2 | message: "Consider using '%s' instead of '%s'." 3 | link: https://docs.microsoft.com/en-us/style-guide/word-choice/use-simple-words-concise-sentences 4 | ignorecase: true 5 | level: suggestion 6 | action: 7 | name: replace 8 | swap: 9 | "approximate(?:ly)?": about 10 | abundance: plenty 11 | accelerate: speed up 12 | accentuate: stress 13 | accompany: go with 14 | accomplish: carry out|do 15 | accorded: given 16 | accordingly: so 17 | accrue: add 18 | accurate: right|exact 19 | acquiesce: agree 20 | acquire: get|buy 21 | additional: more|extra 22 | address: discuss 23 | addressees: you 24 | adjacent to: next to 25 | adjustment: change 26 | admissible: allowed 27 | advantageous: helpful 28 | advise: tell 29 | aggregate: total 30 | aircraft: plane 31 | alleviate: ease 32 | allocate: assign|divide 33 | alternatively: or 34 | alternatives: choices|options 35 | ameliorate: improve 36 | amend: change 37 | anticipate: expect 38 | apparent: clear|plain 39 | ascertain: discover|find out 40 | assistance: help 41 | attain: meet 42 | attempt: try 43 | authorize: allow 44 | belated: late 45 | bestow: give 46 | cease: stop|end 47 | collaborate: work together 48 | commence: begin 49 | compensate: pay 50 | component: part 51 | comprise: form|include 52 | concept: idea 53 | concerning: about 54 | confer: give|award 55 | consequently: so 56 | consolidate: merge 57 | constitutes: forms 58 | contains: has 59 | convene: meet 60 | demonstrate: show|prove 61 | depart: leave 62 | designate: choose 63 | desire: want|wish 64 | determine: decide|find 65 | detrimental: bad|harmful 66 | disclose: share|tell 67 | discontinue: stop 68 | disseminate: send|give 69 | eliminate: end 70 | elucidate: explain 71 | employ: use 72 | enclosed: inside|included 73 | encounter: meet 74 | endeavor: try 75 | enumerate: count 76 | equitable: fair 77 | equivalent: equal 78 | exclusively: only 79 | expedite: hurry 80 | facilitate: ease 81 | females: women 82 | finalize: complete|finish 83 | frequently: often 84 | identical: same 85 | incorrect: wrong 86 | indication: sign 87 | initiate: start|begin 88 | itemized: listed 89 | jeopardize: risk 90 | liaise: work with|partner with 91 | maintain: keep|support 92 | methodology: method 93 | modify: change 94 | monitor: check|watch 95 | multiple: many 96 | necessitate: cause 97 | notify: tell 98 | numerous: many 99 | objective: aim|goal 100 | obligate: bind|compel 101 | optimum: best|most 102 | permit: let 103 | portion: part 104 | possess: own 105 | previous: earlier 106 | previously: before 107 | prioritize: rank 108 | procure: buy 109 | provide: give|offer 110 | purchase: buy 111 | relocate: move 112 | solicit: request 113 | state-of-the-art: latest 114 | subsequent: later|next 115 | substantial: large 116 | sufficient: enough 117 | terminate: end 118 | transmit: send 119 | utilization: use 120 | utilize: use 121 | -------------------------------------------------------------------------------- /docs/styles/Microsoft/Contractions.yml: -------------------------------------------------------------------------------- 1 | extends: substitution 2 | message: "Use '%s' instead of '%s'." 3 | link: https://docs.microsoft.com/en-us/style-guide/word-choice/use-contractions 4 | level: error 5 | ignorecase: true 6 | action: 7 | name: replace 8 | swap: 9 | are not: aren't 10 | cannot: can't 11 | could not: couldn't 12 | did not: didn't 13 | do not: don't 14 | does not: doesn't 15 | has not: hasn't 16 | have not: haven't 17 | how is: how's 18 | is not: isn't 19 | # it is: it's --> sometimes "it's" isn't grammatically correct 20 | should not: shouldn't 21 | that is: that's 22 | they are: they're 23 | was not: wasn't 24 | we are: we're 25 | we have: we've 26 | were not: weren't 27 | what is: what's 28 | when is: when's 29 | where is: where's 30 | will not: won't 31 | -------------------------------------------------------------------------------- /docs/styles/Microsoft/Dashes.yml: -------------------------------------------------------------------------------- 1 | extends: existence 2 | message: "Remove the spaces around '%s'." 3 | link: https://docs.microsoft.com/en-us/style-guide/punctuation/dashes-hyphens/emes 4 | ignorecase: true 5 | nonword: true 6 | level: error 7 | action: 8 | name: edit 9 | params: 10 | - remove 11 | - ' ' 12 | tokens: 13 | - '[—–]\s|\s[—–]' 14 | -------------------------------------------------------------------------------- /docs/styles/Microsoft/DateFormat.yml: -------------------------------------------------------------------------------- 1 | extends: existence 2 | message: Use 'July 31, 2016' format, not '%s'. 3 | link: https://docs.microsoft.com/en-us/style-guide/a-z-word-list-term-collections/term-collections/date-time-terms 4 | ignorecase: true 5 | level: error 6 | nonword: true 7 | tokens: 8 | - '\d{1,2} (?:Jan(?:uary)?|Feb(?:ruary)?|Mar(?:ch)?|Apr(?:il)|May|Jun(?:e)|Jul(?:y)|Aug(?:ust)|Sep(?:tember)?|Oct(?:ober)|Nov(?:ember)?|Dec(?:ember)?) \d{4}' 9 | -------------------------------------------------------------------------------- /docs/styles/Microsoft/DateNumbers.yml: -------------------------------------------------------------------------------- 1 | extends: existence 2 | message: "Don't use ordinal numbers for dates." 3 | link: https://docs.microsoft.com/en-us/style-guide/numbers#numbers-in-dates 4 | level: error 5 | nonword: true 6 | ignorecase: true 7 | raw: 8 | - \b(?:Jan(?:uary)?|Feb(?:ruary)?|Mar(?:ch)?|Apr(?:il)|May|Jun(?:e)|Jul(?:y)|Aug(?:ust)|Sep(?:tember)?|Oct(?:ober)|Nov(?:ember)?|Dec(?:ember)?)\b\s* 9 | tokens: 10 | - first 11 | - second 12 | - third 13 | - fourth 14 | - fifth 15 | - sixth 16 | - seventh 17 | - eighth 18 | - ninth 19 | - tenth 20 | - eleventh 21 | - twelfth 22 | - thirteenth 23 | - fourteenth 24 | - fifteenth 25 | - sixteenth 26 | - seventeenth 27 | - eighteenth 28 | - nineteenth 29 | - twentieth 30 | - twenty-first 31 | - twenty-second 32 | - twenty-third 33 | - twenty-fourth 34 | - twenty-fifth 35 | - twenty-sixth 36 | - twenty-seventh 37 | - twenty-eighth 38 | - twenty-ninth 39 | - thirtieth 40 | - thirty-first 41 | -------------------------------------------------------------------------------- /docs/styles/Microsoft/DateOrder.yml: -------------------------------------------------------------------------------- 1 | extends: existence 2 | message: "Always spell out the name of the month." 3 | link: https://docs.microsoft.com/en-us/style-guide/numbers#numbers-in-dates 4 | ignorecase: true 5 | level: error 6 | nonword: true 7 | tokens: 8 | - '\b\d{1,2}/\d{1,2}/(?:\d{4}|\d{2})\b' 9 | -------------------------------------------------------------------------------- /docs/styles/Microsoft/Ellipses.yml: -------------------------------------------------------------------------------- 1 | extends: existence 2 | message: "In general, don't use an ellipsis." 3 | link: https://docs.microsoft.com/en-us/style-guide/punctuation/ellipses 4 | nonword: true 5 | level: warning 6 | action: 7 | name: remove 8 | tokens: 9 | - '\.\.\.' 10 | -------------------------------------------------------------------------------- /docs/styles/Microsoft/FirstPerson.yml: -------------------------------------------------------------------------------- 1 | extends: existence 2 | message: "Use first person (such as '%s') sparingly." 3 | link: https://docs.microsoft.com/en-us/style-guide/grammar/person 4 | ignorecase: true 5 | level: warning 6 | nonword: true 7 | tokens: 8 | - (?:^|\s)I\s 9 | - (?:^|\s)I,\s 10 | - \bI'm\b 11 | - \bme\b 12 | - \bmy\b 13 | - \bmine\b 14 | -------------------------------------------------------------------------------- /docs/styles/Microsoft/Foreign.yml: -------------------------------------------------------------------------------- 1 | extends: substitution 2 | message: "Use '%s' instead of '%s'." 3 | link: https://docs.microsoft.com/en-us/style-guide/word-choice/use-us-spelling-avoid-non-english-words 4 | ignorecase: true 5 | level: error 6 | nonword: true 7 | action: 8 | name: replace 9 | swap: 10 | '\b(?:eg|e\.g\.)[\s,]': for example 11 | '\b(?:ie|i\.e\.)[\s,]': that is 12 | 13 | -------------------------------------------------------------------------------- /docs/styles/Microsoft/Gender.yml: -------------------------------------------------------------------------------- 1 | extends: existence 2 | message: "Don't use '%s'." 3 | link: https://github.com/MicrosoftDocs/microsoft-style-guide/blob/master/styleguide/grammar/nouns-pronouns.md#pronouns-and-gender 4 | level: error 5 | ignorecase: true 6 | tokens: 7 | - he/she 8 | - s/he 9 | -------------------------------------------------------------------------------- /docs/styles/Microsoft/GenderBias.yml: -------------------------------------------------------------------------------- 1 | extends: substitution 2 | message: "Consider using '%s' instead of '%s'." 3 | ignorecase: true 4 | level: error 5 | swap: 6 | (?:alumna|alumnus): graduate 7 | (?:alumnae|alumni): graduates 8 | air(?:m[ae]n|wom[ae]n): pilot(s) 9 | anchor(?:m[ae]n|wom[ae]n): anchor(s) 10 | authoress: author 11 | camera(?:m[ae]n|wom[ae]n): camera operator(s) 12 | chair(?:m[ae]n|wom[ae]n): chair(s) 13 | congress(?:m[ae]n|wom[ae]n): member(s) of congress 14 | door(?:m[ae]|wom[ae]n): concierge(s) 15 | draft(?:m[ae]n|wom[ae]n): drafter(s) 16 | fire(?:m[ae]n|wom[ae]n): firefighter(s) 17 | fisher(?:m[ae]n|wom[ae]n): fisher(s) 18 | fresh(?:m[ae]n|wom[ae]n): first-year student(s) 19 | garbage(?:m[ae]n|wom[ae]n): waste collector(s) 20 | lady lawyer: lawyer 21 | ladylike: courteous 22 | landlord: building manager 23 | mail(?:m[ae]n|wom[ae]n): mail carriers 24 | man and wife: husband and wife 25 | man enough: strong enough 26 | mankind: human kind 27 | manmade: manufactured 28 | manpower: personnel 29 | men and girls: men and women 30 | middle(?:m[ae]n|wom[ae]n): intermediary 31 | news(?:m[ae]n|wom[ae]n): journalist(s) 32 | ombuds(?:man|woman): ombuds 33 | oneupmanship: upstaging 34 | poetess: poet 35 | police(?:m[ae]n|wom[ae]n): police officer(s) 36 | repair(?:m[ae]n|wom[ae]n): technician(s) 37 | sales(?:m[ae]n|wom[ae]n): salesperson or sales people 38 | service(?:m[ae]n|wom[ae]n): soldier(s) 39 | steward(?:ess)?: flight attendant 40 | tribes(?:m[ae]n|wom[ae]n): tribe member(s) 41 | waitress: waiter 42 | woman doctor: doctor 43 | woman scientist[s]?: scientist(s) 44 | work(?:m[ae]n|wom[ae]n): worker(s) 45 | -------------------------------------------------------------------------------- /docs/styles/Microsoft/GeneralURL.yml: -------------------------------------------------------------------------------- 1 | extends: existence 2 | message: "For a general audience, use 'address' rather than 'URL'." 3 | link: https://docs.microsoft.com/en-us/style-guide/urls-web-addresses 4 | level: warning 5 | action: 6 | name: replace 7 | params: 8 | - URL 9 | - address 10 | tokens: 11 | - URL 12 | -------------------------------------------------------------------------------- /docs/styles/Microsoft/HeadingColons.yml: -------------------------------------------------------------------------------- 1 | extends: existence 2 | message: "Capitalize '%s'." 3 | link: https://docs.microsoft.com/en-us/style-guide/punctuation/colons 4 | nonword: true 5 | level: error 6 | scope: heading 7 | tokens: 8 | - ':\s[a-z]' 9 | -------------------------------------------------------------------------------- /docs/styles/Microsoft/HeadingPunctuation.yml: -------------------------------------------------------------------------------- 1 | extends: existence 2 | message: "Don't use end punctuation in headings." 3 | link: https://docs.microsoft.com/en-us/style-guide/punctuation/periods 4 | nonword: true 5 | level: warning 6 | scope: heading 7 | action: 8 | name: edit 9 | params: 10 | - remove 11 | - '.?!' 12 | tokens: 13 | - '[a-z][.?!](?:\s|$)' 14 | -------------------------------------------------------------------------------- /docs/styles/Microsoft/Headings.yml: -------------------------------------------------------------------------------- 1 | extends: capitalization 2 | message: "'%s' should use sentence-style capitalization." 3 | link: https://docs.microsoft.com/en-us/style-guide/capitalization 4 | level: suggestion 5 | scope: heading 6 | match: $sentence 7 | indicators: 8 | - ':' 9 | exceptions: 10 | - Azure 11 | - CLI 12 | - Code 13 | - Cosmos 14 | - Docker 15 | - Emmet 16 | - I 17 | - Kubernetes 18 | - Linux 19 | - macOS 20 | - Marketplace 21 | - MongoDB 22 | - REPL 23 | - Studio 24 | - TypeScript 25 | - URLs 26 | - Visual 27 | - VS 28 | - Windows 29 | -------------------------------------------------------------------------------- /docs/styles/Microsoft/Hyphens.yml: -------------------------------------------------------------------------------- 1 | extends: existence 2 | message: "'%s' doesn't need a hyphen." 3 | link: https://docs.microsoft.com/en-us/style-guide/punctuation/dashes-hyphens/hyphens 4 | level: warning 5 | ignorecase: false 6 | nonword: true 7 | action: 8 | name: edit 9 | params: 10 | - replace 11 | - '-' 12 | - ' ' 13 | tokens: 14 | - '\s[^\s-]+ly-' 15 | -------------------------------------------------------------------------------- /docs/styles/Microsoft/Negative.yml: -------------------------------------------------------------------------------- 1 | extends: existence 2 | message: "Form a negative number with an en dash, not a hyphen." 3 | link: https://docs.microsoft.com/en-us/style-guide/numbers 4 | nonword: true 5 | level: error 6 | action: 7 | name: edit 8 | params: 9 | - replace 10 | - '-' 11 | - '–' 12 | tokens: 13 | - '\s-\d+\s' 14 | -------------------------------------------------------------------------------- /docs/styles/Microsoft/Ordinal.yml: -------------------------------------------------------------------------------- 1 | extends: existence 2 | message: "Don't add -ly to an ordinal number." 3 | link: https://docs.microsoft.com/en-us/style-guide/numbers 4 | level: error 5 | action: 6 | name: edit 7 | params: 8 | - trim 9 | - ly 10 | tokens: 11 | - firstly 12 | - secondly 13 | - thirdly 14 | -------------------------------------------------------------------------------- /docs/styles/Microsoft/OxfordComma.yml: -------------------------------------------------------------------------------- 1 | extends: existence 2 | message: "Use the Oxford comma in '%s'." 3 | link: https://docs.microsoft.com/en-us/style-guide/punctuation/commas 4 | scope: sentence 5 | level: suggestion 6 | nonword: true 7 | tokens: 8 | - '(?:[^\s,]+,){1,} \w+ (?:and|or) \w+[.?!]' 9 | -------------------------------------------------------------------------------- /docs/styles/Microsoft/Passive.yml: -------------------------------------------------------------------------------- 1 | extends: existence 2 | message: "'%s' looks like passive voice." 3 | ignorecase: true 4 | level: suggestion 5 | raw: 6 | - \b(am|are|were|being|is|been|was|be)\b\s* 7 | tokens: 8 | - '[\w]+ed' 9 | - awoken 10 | - beat 11 | - become 12 | - been 13 | - begun 14 | - bent 15 | - beset 16 | - bet 17 | - bid 18 | - bidden 19 | - bitten 20 | - bled 21 | - blown 22 | - born 23 | - bought 24 | - bound 25 | - bred 26 | - broadcast 27 | - broken 28 | - brought 29 | - built 30 | - burnt 31 | - burst 32 | - cast 33 | - caught 34 | - chosen 35 | - clung 36 | - come 37 | - cost 38 | - crept 39 | - cut 40 | - dealt 41 | - dived 42 | - done 43 | - drawn 44 | - dreamt 45 | - driven 46 | - drunk 47 | - dug 48 | - eaten 49 | - fallen 50 | - fed 51 | - felt 52 | - fit 53 | - fled 54 | - flown 55 | - flung 56 | - forbidden 57 | - foregone 58 | - forgiven 59 | - forgotten 60 | - forsaken 61 | - fought 62 | - found 63 | - frozen 64 | - given 65 | - gone 66 | - gotten 67 | - ground 68 | - grown 69 | - heard 70 | - held 71 | - hidden 72 | - hit 73 | - hung 74 | - hurt 75 | - kept 76 | - knelt 77 | - knit 78 | - known 79 | - laid 80 | - lain 81 | - leapt 82 | - learnt 83 | - led 84 | - left 85 | - lent 86 | - let 87 | - lighted 88 | - lost 89 | - made 90 | - meant 91 | - met 92 | - misspelt 93 | - mistaken 94 | - mown 95 | - overcome 96 | - overdone 97 | - overtaken 98 | - overthrown 99 | - paid 100 | - pled 101 | - proven 102 | - put 103 | - quit 104 | - read 105 | - rid 106 | - ridden 107 | - risen 108 | - run 109 | - rung 110 | - said 111 | - sat 112 | - sawn 113 | - seen 114 | - sent 115 | - set 116 | - sewn 117 | - shaken 118 | - shaven 119 | - shed 120 | - shod 121 | - shone 122 | - shorn 123 | - shot 124 | - shown 125 | - shrunk 126 | - shut 127 | - slain 128 | - slept 129 | - slid 130 | - slit 131 | - slung 132 | - smitten 133 | - sold 134 | - sought 135 | - sown 136 | - sped 137 | - spent 138 | - spilt 139 | - spit 140 | - split 141 | - spoken 142 | - spread 143 | - sprung 144 | - spun 145 | - stolen 146 | - stood 147 | - stridden 148 | - striven 149 | - struck 150 | - strung 151 | - stuck 152 | - stung 153 | - stunk 154 | - sung 155 | - sunk 156 | - swept 157 | - swollen 158 | - sworn 159 | - swum 160 | - swung 161 | - taken 162 | - taught 163 | - thought 164 | - thrived 165 | - thrown 166 | - thrust 167 | - told 168 | - torn 169 | - trodden 170 | - understood 171 | - upheld 172 | - upset 173 | - wed 174 | - wept 175 | - withheld 176 | - withstood 177 | - woken 178 | - won 179 | - worn 180 | - wound 181 | - woven 182 | - written 183 | - wrung 184 | -------------------------------------------------------------------------------- /docs/styles/Microsoft/Percentages.yml: -------------------------------------------------------------------------------- 1 | extends: existence 2 | message: "Use a numeral plus the units." 3 | link: https://docs.microsoft.com/en-us/style-guide/numbers 4 | nonword: true 5 | level: error 6 | tokens: 7 | - '\b[a-zA-z]+\spercent\b' 8 | -------------------------------------------------------------------------------- /docs/styles/Microsoft/Quotes.yml: -------------------------------------------------------------------------------- 1 | extends: existence 2 | message: 'Punctuation should be inside the quotes.' 3 | link: https://docs.microsoft.com/en-us/style-guide/punctuation/quotation-marks 4 | level: error 5 | nonword: true 6 | tokens: 7 | - '["“][^"”“]+["”][.,]' 8 | -------------------------------------------------------------------------------- /docs/styles/Microsoft/RangeFormat.yml: -------------------------------------------------------------------------------- 1 | extends: existence 2 | message: "Use an en dash in a range of numbers." 3 | link: https://docs.microsoft.com/en-us/style-guide/numbers 4 | nonword: true 5 | level: error 6 | action: 7 | name: edit 8 | params: 9 | - replace 10 | - '-' 11 | - '–' 12 | tokens: 13 | - '\b\d+\s?[-]\s?\d+\b' 14 | -------------------------------------------------------------------------------- /docs/styles/Microsoft/RangeTime.yml: -------------------------------------------------------------------------------- 1 | extends: existence 2 | message: "Use 'to' instead of a dash in '%s'." 3 | link: https://docs.microsoft.com/en-us/style-guide/numbers 4 | nonword: true 5 | level: error 6 | action: 7 | name: edit 8 | params: 9 | - replace 10 | - '[-–]' 11 | - 'to' 12 | tokens: 13 | - '\b(?:AM|PM)\s?[-–]\s?.+(?:AM|PM)\b' 14 | -------------------------------------------------------------------------------- /docs/styles/Microsoft/Ranges.yml: -------------------------------------------------------------------------------- 1 | extends: existence 2 | message: "In most cases, use 'from' or 'through' to describe a range of numbers." 3 | link: 'https://docs.microsoft.com/en-us/style-guide/numbers' 4 | nonword: true 5 | level: warning 6 | tokens: 7 | - '\b\d+\s?[-–]\s?\d+\b' 8 | -------------------------------------------------------------------------------- /docs/styles/Microsoft/Semicolon.yml: -------------------------------------------------------------------------------- 1 | extends: existence 2 | message: "Try to simplify this sentence." 3 | link: https://docs.microsoft.com/en-us/style-guide/punctuation/semicolons 4 | nonword: true 5 | scope: sentence 6 | level: suggestion 7 | tokens: 8 | - ';' 9 | -------------------------------------------------------------------------------- /docs/styles/Microsoft/SentenceLength.yml: -------------------------------------------------------------------------------- 1 | extends: occurrence 2 | message: "Try to keep sentences short (< 30 words)." 3 | scope: sentence 4 | level: suggestion 5 | max: 30 6 | token: \b(\w+)\b 7 | 8 | -------------------------------------------------------------------------------- /docs/styles/Microsoft/Spacing.yml: -------------------------------------------------------------------------------- 1 | extends: existence 2 | message: "'%s' should have one space." 3 | link: https://docs.microsoft.com/en-us/style-guide/punctuation/periods 4 | level: error 5 | nonword: true 6 | tokens: 7 | - '[a-z][.?!] {2,}[A-Z]' 8 | - '[a-z][.?!][A-Z]' 9 | -------------------------------------------------------------------------------- /docs/styles/Microsoft/Suspended.yml: -------------------------------------------------------------------------------- 1 | extends: existence 2 | message: "Don't use '%s' unless space is limited." 3 | link: https://docs.microsoft.com/en-us/style-guide/punctuation/dashes-hyphens/hyphens 4 | ignorecase: true 5 | level: warning 6 | tokens: 7 | - '\w+- and \w+-' 8 | -------------------------------------------------------------------------------- /docs/styles/Microsoft/Terms.yml: -------------------------------------------------------------------------------- 1 | extends: substitution 2 | message: "Prefer '%s' over '%s'." 3 | level: warning 4 | ignorecase: true 5 | action: 6 | name: replace 7 | swap: 8 | '(?:virtual assistant|intelligent personal assistant)': personal digital assistant 9 | '(?:drive C:|drive C>|C: drive)': drive C 10 | '(?:internet bot|web robot)s?': bot(s) 11 | '(?:microsoft cloud|the cloud)': cloud 12 | '(?:mobile|smart) ?phone': phone 13 | '24/7': every day 14 | 'audio(?:-| )book': audiobook 15 | 'back(?:-| )light': backlight 16 | 'chat ?bots?': chatbot(s) 17 | adaptor: adapter 18 | administrate: administer 19 | afterwards: afterward 20 | alphabetic: alphabetical 21 | alphanumerical: alphanumeric 22 | anti-aliasing: antialiasing 23 | anti-malware: antimalware 24 | anti-spyware: antispyware 25 | anti-virus: antivirus 26 | appendixes: appendices 27 | artificial intelligence: artificial intelligence 28 | assembler: assembly language 29 | bpp: bpp 30 | bps: bps 31 | caap: CaaP 32 | conversation-as-a-platform: conversation as a platform 33 | eb: EB 34 | gb: GB 35 | gbps: Gbps 36 | kb: KB 37 | keypress: keystroke 38 | mb: MB 39 | pb: PB 40 | tb: TB 41 | zb: ZB 42 | viz: namely 43 | ergo: therefore 44 | -------------------------------------------------------------------------------- /docs/styles/Microsoft/URLFormat.yml: -------------------------------------------------------------------------------- 1 | extends: substitution 2 | message: "Use '%s' instead of '%s'." 3 | ignorecase: true 4 | level: error 5 | action: 6 | name: replace 7 | swap: 8 | URL for: URL of 9 | an URL: a URL 10 | 11 | -------------------------------------------------------------------------------- /docs/styles/Microsoft/Units.yml: -------------------------------------------------------------------------------- 1 | extends: existence 2 | message: "Don't spell out the number in '%s'." 3 | link: https://docs.microsoft.com/en-us/style-guide/a-z-word-list-term-collections/term-collections/units-of-measure-terms 4 | level: error 5 | raw: 6 | - '[a-zA-Z]+\s' 7 | tokens: 8 | - '(?:centi|milli)?meters' 9 | - '(?:kilo)?grams' 10 | - '(?:kilo)?meters' 11 | - '(?:mega)?pixels' 12 | - cm 13 | - inches 14 | - lb 15 | - miles 16 | - pounds 17 | -------------------------------------------------------------------------------- /docs/styles/Microsoft/Vocab.yml: -------------------------------------------------------------------------------- 1 | extends: existence 2 | message: "Verify your use of '%s' with the A-Z word list." 3 | link: 'https://docs.microsoft.com/en-us/style-guide' 4 | level: suggestion 5 | ignorecase: true 6 | tokens: 7 | - above 8 | - accessible 9 | - actionable 10 | - against 11 | - alarm 12 | - alert 13 | - alias 14 | - allows? 15 | - and/or 16 | - as well as 17 | - assure 18 | - author 19 | - avg 20 | - beta 21 | - ensure 22 | - he 23 | - insure 24 | - sample 25 | - she 26 | -------------------------------------------------------------------------------- /docs/styles/Microsoft/We.yml: -------------------------------------------------------------------------------- 1 | extends: existence 2 | message: "Try to avoid using first-person plural like '%s'." 3 | link: https://docs.microsoft.com/en-us/style-guide/grammar/person#avoid-first-person-plural 4 | level: warning 5 | ignorecase: true 6 | tokens: 7 | - we 8 | - we'(?:ve|re) 9 | - ours? 10 | - us 11 | - let's 12 | -------------------------------------------------------------------------------- /docs/styles/Microsoft/Wordiness.yml: -------------------------------------------------------------------------------- 1 | extends: substitution 2 | message: "Consider using '%s' instead of '%s'." 3 | link: https://docs.microsoft.com/en-us/style-guide/word-choice/use-simple-words-concise-sentences 4 | ignorecase: true 5 | level: warning 6 | action: 7 | name: replace 8 | swap: 9 | (?:give|gave) rise to: lead to 10 | (?:previous|prior) to: before 11 | a (?:large)? majority of: most 12 | a (?:large)? number of: many 13 | a myriad of: myriad 14 | adversely impact: hurt 15 | all across: across 16 | all of a sudden: suddenly 17 | all of these: these 18 | all of: all 19 | all-time record: record 20 | almost all: most 21 | almost never: seldom 22 | along the lines of: similar to 23 | an adequate number of: enough 24 | an appreciable number of: many 25 | an estimated: about 26 | any and all: all 27 | are in agreement: agree 28 | as a matter of fact: in fact 29 | as a means of: to 30 | as a result of: because of 31 | as of yet: yet 32 | as per: per 33 | at a later date: later 34 | at all times: always 35 | at the present time: now 36 | at this point in time: at this point 37 | based in large part on: based on 38 | based on the fact that: because 39 | basic necessity: necessity 40 | because of the fact that: because 41 | came to a realization: realized 42 | came to an abrupt end: ended abruptly 43 | carry out an evaluation of: evaluate 44 | close down: close 45 | closed down: closed 46 | complete stranger: stranger 47 | completely separate: separate 48 | concerning the matter of: regarding 49 | conduct a review of: review 50 | conduct an investigation: investigate 51 | conduct experiments: experiment 52 | continue on: continue 53 | despite the fact that: although 54 | disappear from sight: disappear 55 | drag and drop: drag 56 | drag-and-drop: drag 57 | doomed to fail: doomed 58 | due to the fact that: because 59 | during the period of: during 60 | during the time that: while 61 | emergency situation: emergency 62 | except when: unless 63 | excessive number: too many 64 | extend an invitation: invite 65 | fall down: fall 66 | fell down: fell 67 | for the duration of: during 68 | gather together: gather 69 | has the ability to: can 70 | has the capacity to: can 71 | has the opportunity to: could 72 | hold a meeting: meet 73 | if this is not the case: if not 74 | in a careful manner: carefully 75 | in a thoughtful manner: thoughtfully 76 | in a timely manner: timely 77 | in an effort to: to 78 | in between: between 79 | in lieu of: instead of 80 | in many cases: often 81 | in most cases: usually 82 | in order to: to 83 | in some cases: sometimes 84 | in spite of the fact that: although 85 | in spite of: despite 86 | in the (?:very)? near future: soon 87 | in the event that: if 88 | in the neighborhood of: roughly 89 | in the vicinity of: close to 90 | it would appear that: apparently 91 | lift up: lift 92 | made reference to: referred to 93 | make reference to: refer to 94 | mix together: mix 95 | none at all: none 96 | not in a position to: unable 97 | not possible: impossible 98 | of major importance: important 99 | perform an assessment of: assess 100 | pertaining to: about 101 | place an order: order 102 | plays a key role in: is essential to 103 | present time: now 104 | readily apparent: apparent 105 | some of the: some 106 | span across: span 107 | subsequent to: after 108 | successfully complete: complete 109 | sufficient number (?:of)?: enough 110 | take action: act 111 | take into account: consider 112 | the question as to whether: whether 113 | there is no doubt but that: doubtless 114 | this day and age: this age 115 | this is a subject that: this subject 116 | time (?:frame|period): time 117 | under the provisions of: under 118 | until such time as: until 119 | used for fuel purposes: used for fuel 120 | whether or not: whether 121 | with regard to: regarding 122 | with the exception of: except for 123 | -------------------------------------------------------------------------------- /docs/styles/Microsoft/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "feed": "https://github.com/errata-ai/Microsoft/releases.atom", 3 | "vale_version": ">=1.0.0" 4 | } 5 | -------------------------------------------------------------------------------- /docs/styles/Vocab/SDP/accept.txt: -------------------------------------------------------------------------------- 1 | [Jj]enkinsfiles? 2 | [Nn]amespaces? 3 | [Tt]emplat(e|es|ed|ing)\b 4 | [Aa]utowir(e|es|ed|ing)\b 5 | [Mm]etaprogramming 6 | [Ss]erializab(ility|le)\b 7 | [Gg]radle 8 | [Cc]lasspath 9 | [Pp]arameteriz(e|es|ed|ing)\b 10 | [Rr]eusability 11 | [Bb]ooleans? 12 | [Ii]nvocable 13 | [Ss]ubkeys? 14 | [Bb]asename 15 | [Hh]oc 16 | [Kk]ubernetes 17 | [Dd]ev 18 | [Jj]enkins 19 | [Cc]onfigs? 20 | [Ww]alkthroughs? 21 | Divio 22 | [Mm]arkdownlint 23 | md 24 | Booz 25 | [Ff]rontmatter 26 | [Oo]verrideable 27 | webhint 28 | [Ss]ubcharts? 29 | Twistlock 30 | Sysdig 31 | [Pp]rox(ying|ied) 32 | [Uu]nstash 33 | Trello 34 | Jira 35 | Splunk 36 | [Mm]ultibranch 37 | (npm|NPM) 38 | (nvm|NVM) 39 | [Bb]uildx 40 | [Rr]etag(|s|ging) 41 | [Dd]ockerfiles? 42 | Anchore 43 | [Pp]arsable 44 | [Ss]yft 45 | (SBOM|sbom)s? 46 | [gG]rype 47 | (json|JSON) 48 | (cli|CLI) 49 | snake_case 50 | -------------------------------------------------------------------------------- /docs/tutorials/overview.md: -------------------------------------------------------------------------------- 1 | # Overview 2 | 3 | Tutorials are learning oriented lessons to teach users about this project. 4 | 5 | !!! abstract "Coming Soon!" 6 | Tutorials and How-To Guides are next up on the priority list after this initial release of the new docs site is over! 7 | 8 | | Tutorial | Description | 9 | |----------|-------------| 10 | | | | 11 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boozallen/sdp-libraries/a2186fa3e8ecf9af1f8f83fdef9a06a05545fdf1/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.3-all.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /libraries/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | hide: 3 | - toc 4 | --- 5 | 6 | # Overview 7 | 8 | 9 | -------------------------------------------------------------------------------- /libraries/a11y/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: Leverages The A11y Machine to perform accessibility compliance scanning 3 | --- 4 | 5 | # a11y 6 | 7 | [The A11y Machine](https://github.com/liip/TheA11yMachine) is an automated accessibility testing tool which crawls and tests pages of a web application to produce detailed reports. 8 | 9 | It validates pages against the following specifications/laws: 10 | 11 | * [W3C Web Content Accessibility Guidelines](http://www.w3.org/TR/WCAG20/) (WCAG) 2.0, including A, AA and AAA levels ([understanding levels of conformance](http://www.w3.org/TR/UNDERSTANDING-WCAG20/conformance.html#uc-levels-head)) 12 | * U.S. [Section 508 legislation](http://www.section508.gov/) 13 | * [W3C HTML5 Recommendation](https://www.w3.org/TR/html) 14 | 15 | !!! warning "Deprecated" 16 | This library is no longer maintained because the A11y Machine is no longer maintained. 17 | Consider using [webhint](./webhint.md) instead. 18 | 19 | ## Steps 20 | 21 | | Step | Description | 22 | |-----------------------------------|----------------------------------------------------------------------------| 23 | | `accessibility_compliance_test()` | crawls the provided website and performs accessibility compliance scanning | 24 | 25 | ## Configuration 26 | 27 | | Field | Description | Default Value | 28 | |-------|--------------------------------------|---------------| 29 | | `URL` | The address a11y will crawl and scan | | 30 | 31 | A target `URL` can be given. However `env.FRONTEND_URL` supersedes all configurations. 32 | If no `env.FRONTEND_URL` is found then the provided target `URL` is used. If no `URL` is provided an error is thrown. 33 | 34 | ```groovy 35 | libraries{ 36 | a11y{ 37 | url = "https://example.com" 38 | } 39 | } 40 | ``` 41 | 42 | ## Results 43 | 44 | The results of the scan are captured in an HTML report that gets archived by Jenkins. 45 | 46 | ### Report Index 47 | 48 | ![HTML Report Landing Page](../../assets/images/a11y/index.png) 49 | 50 | ### Report from a specific address 51 | 52 | ![HTML Report Drill Down](../../assets/images/a11y/report.png) 53 | 54 | ## Dependencies 55 | -------------------------------------------------------------------------------- /libraries/a11y/steps/accessibility_compliance_test.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2018 Booz Allen Hamilton. All Rights Reserved. 3 | This software package is licensed under the Booz Allen Public License. The license can be found in the License file or at http://boozallen.github.io/licenses/bapl 4 | */ 5 | 6 | package libraries.a11y.steps 7 | 8 | void call(){ 9 | 10 | stage "Accessibility Compliance Scan", { 11 | 12 | def url = env.FRONTEND_URL ?: config.url ?: { 13 | error """ 14 | A11y Library needs the target url. 15 | libraries{ 16 | a11y{ 17 | url = "https://example.com" 18 | } 19 | } 20 | """ 21 | } () 22 | 23 | inside_sdp_image "a11y", { 24 | sh "a11ym -o accessibility_compliance ${url}" 25 | archive "accessibility_compliance/**" 26 | } 27 | } 28 | 29 | } -------------------------------------------------------------------------------- /libraries/a11y/test/AccessibilityComplianceTestSpec.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2018 Booz Allen Hamilton. All Rights Reserved. 3 | This software package is licensed under the Booz Allen Public License. The license can be found in the License file or at http://boozallen.github.io/licenses/bapl 4 | */ 5 | 6 | package libraries.a11y 7 | 8 | import org.junit.* 9 | import spock.lang.* 10 | 11 | public class AccessibilityComplianceTestSpec extends JTEPipelineSpecification { 12 | 13 | def AccessibilityComplianceTest 14 | 15 | def setup() { 16 | AccessibilityComplianceTest = loadPipelineScriptForStep("a11y","accessibility_compliance_test") 17 | explicitlyMockPipelineStep("inside_sdp_image") 18 | } 19 | 20 | def "Scan Runs With Given URL" () { 21 | setup: 22 | AccessibilityComplianceTest.getBinding().setVariable("config", [ url: "https://www.example.com" ]) 23 | when: 24 | AccessibilityComplianceTest() 25 | then: 26 | 1 * getPipelineMock("sh")("a11ym -o accessibility_compliance https://www.example.com") 27 | } 28 | 29 | def "Scan results are archived" () { 30 | setup: 31 | AccessibilityComplianceTest.getBinding().setVariable("config", [ url: "https://www.example.com" ]) 32 | when: 33 | AccessibilityComplianceTest() 34 | then: 35 | 1 * getPipelineMock("archive")("accessibility_compliance/**") 36 | } 37 | 38 | def "env.FRONTEND_URL Takes Priority Over config.url" () { 39 | setup: 40 | AccessibilityComplianceTest.getBinding().setVariable("env", [ FRONTEND_URL: "FRONTEND" ]) 41 | AccessibilityComplianceTest.getBinding().setVariable("config", [ url: "config" ]) 42 | when: 43 | AccessibilityComplianceTest() 44 | then: 45 | 1 * getPipelineMock("sh")("a11ym -o accessibility_compliance FRONTEND") 46 | 1 * getPipelineMock("archive")("accessibility_compliance/**") 47 | } 48 | 49 | def "Scan Fails Without URL" () { 50 | setup: 51 | AccessibilityComplianceTest.getBinding().setVariable("config", [ url: null ]) 52 | when: 53 | AccessibilityComplianceTest() 54 | then: 55 | 1 * getPipelineMock("error")(_) 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /libraries/anchore/library_config.groovy: -------------------------------------------------------------------------------- 1 | fields{ 2 | required{ 3 | cred = String 4 | anchore_engine_url = String 5 | } 6 | optional{ 7 | image_wait_timeout = int 8 | policy_id = String 9 | archive_only = Boolean 10 | bail_on_fail = Boolean 11 | perform_vulnerability_scan = Boolean 12 | perform_policy_evaluation = Boolean 13 | docker_registry_credential_id = String 14 | docker_registry_name = String 15 | k8s_credential = String 16 | k8s_context = String 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /libraries/anchore/steps/add_registry_creds.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2018 Booz Allen Hamilton. All Rights Reserved. 3 | This software package is licensed under the Booz Allen Public License. The license can be found in the License file or at http://boozallen.github.io/licenses/bapl 4 | */ 5 | 6 | package libraries.anchore.steps 7 | 8 | import groovy.json.* 9 | 10 | def generate_post_data(registry, registry_user, registry_password){ 11 | return JsonOutput.toJson([ 12 | registry: "$registry", 13 | registry_name: "$registry", 14 | registry_type: 'docker_v2', 15 | registry_user: "$registry_user", 16 | registry_verify: true, 17 | registry_pass: "$registry_password" 18 | ]) 19 | } 20 | 21 | void call(app_env = null){ 22 | stage("Ensure Anchore has Docker Creds"){ 23 | 24 | /* 25 | Docker registry creds for pulling helm image (has kubectl) 26 | */ 27 | def docker_registry_credential_id = app_env?.docker_registry_credential_id ?: 28 | config.docker_registry_credential_id ?: 29 | "docker_registry" 30 | 31 | def docker_registry_name = app_env?.docker_registry_name ?: 32 | config.docker_registry_name ?: 33 | "" 34 | 35 | /* 36 | k8s credential with kubeconfig 37 | */ 38 | def k8s_credential = app_env?.k8s_credential ?: 39 | config.k8s_credential ?: 40 | {error "Kubernetes Credential Not Defined"}() 41 | /* 42 | k8s context 43 | */ 44 | def k8s_context = app_env?.k8s_context ?: 45 | config.k8s_context ?: 46 | {error "Kubernetes Context Not Defined"}() 47 | 48 | /* 49 | Anchore engine url 50 | */ 51 | env.ANCHORE_ENGINE_URL = app_env?.anchore_engine_url ?: 52 | config.anchore_engine_url ?: 53 | {error "Anchore Engine Url Not Defined"}() 54 | 55 | try { 56 | withCredentials([ 57 | usernamePassword(credentialsId: config.cred, usernameVariable: 'ANCHORE_USERNAME', passwordVariable: "ANCHORE_PASSWORD"), 58 | usernamePassword(credentialsId: docker_registry_credential_id, usernameVariable: 'REGISTRY_USERNAME', passwordVariable: "REGISTRY_PASSWORD") 59 | ]) { 60 | inside_sdp_image "helm", { 61 | withKubeConfig([credentialsId: k8s_credential , contextName: k8s_context]) { 62 | sh "curl --header \"Content-Type: application/json\" -X POST -u $ANCHORE_USERNAME:$ANCHORE_PASSWORD $ANCHORE_ENGINE_URL/registries -d '${generate_post_data(docker_registry_name, REGISTRY_USERNAME, REGISTRY_PASSWORD)}'" 63 | 64 | sh 'curl -u $ANCHORE_USERNAME:$ANCHORE_PASSWORD $ANCHORE_ENGINE_URL/registries' 65 | } 66 | } 67 | } 68 | } catch (e) { 69 | println "anchore add_registry_creds step failed: $e" 70 | } 71 | } 72 | } -------------------------------------------------------------------------------- /libraries/cypress/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: This library allows you to run end-to-end tests with Cypress 3 | --- 4 | 5 | # Cypress 6 | 7 | This library allows you to run end-to-end tests with [Cypress](https://www.cypress.io/). 8 | 9 | ## Steps 10 | 11 | | Step | Description | 12 | | ------------------- | ------------------------------------------------------------------------------------- | 13 | | `end_to_end_test()` | Runs tests defined in the configured `npm_script` in your project `package.json` file | 14 | 15 | ## Configuration 16 | 17 | | Field | Description | Default Value | 18 | | -------------------------- | ------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------- | 19 | | `npm_script` | Cypress NPM script to run (defined in your `package.json` file) | N/A (Required) | 20 | | `report_path` | Path where Cypress reports can be found after tests are run (will be archived in Jenkins and accepts Ant-style wildcards) | N/A (Required) | 21 | | `test_repo` | Repository containing Cypress test code (leave as default if test code is in the same repository as your project) | `.` | 22 | | `test_repo_creds` | Test code repository credentials | `''` | 23 | | `branch` | If using a separate `test_repo` for Cypress test code, allows you to define the repository branch to use | `main` | 24 | | `container_image` | Cypress test runner container image to use | `cypress/browsers:node14.17.0-chrome91-ff89` | 25 | | `container_registry` | Container registry to use (if not hub.docker.com) | `https://index.docker.io/v1/` | 26 | | `container_registry_creds` | Container registry credentials to use | `''` | 27 | 28 | ## Example Configuration Snippet 29 | 30 | ``` groovy title='pipeline_config.groovy' 31 | libraries { 32 | cypress { 33 | npm_script = 'npm run cy:run:myTestSuite' 34 | report_path = 'cypress/reports/**' 35 | test_repo = 'https://github.com/username/my-cypress-test-repository' 36 | test_repo_creds = 'my-github-creds' 37 | branch = 'main' 38 | container_image = 'cypress/browsers:node14.17.0-chrome91-ff89' 39 | container_registry = 'https://index.docker.io/v1/' 40 | container_registry_creds = 'my-registry-creds' 41 | } 42 | } 43 | ``` 44 | 45 | ## Dependencies 46 | 47 | * None 48 | -------------------------------------------------------------------------------- /libraries/cypress/library_config.groovy: -------------------------------------------------------------------------------- 1 | fields { 2 | required { 3 | npm_script = String 4 | report_path = String 5 | } 6 | optional { 7 | test_repo = String 8 | test_repo_creds = String 9 | branch = String 10 | container_image = String 11 | container_registry = String 12 | container_registry_creds = String 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /libraries/cypress/steps/end_to_end_test.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2022 Booz Allen Hamilton. All Rights Reserved. 3 | This software package is licensed under the Booz Allen Public License. 4 | The license can be found in the License file or at http://boozallen.github.io/licenses/bapl 5 | */ 6 | 7 | package libraries.cypress.steps 8 | 9 | void call() { 10 | 11 | stage("End-to-End Testing (Cypress)") { 12 | // Required parameters 13 | String npmScript = config?.npm_script ?: null 14 | String reportPath = config?.report_path ?: null 15 | 16 | if (null in [npmScript, reportPath]) { 17 | error "Missing required parameter(s) (npm_script, report_path)" 18 | } 19 | 20 | // Optional parameters 21 | String testRepo = config?.test_repo ?: '.' 22 | String testRepoCreds = config?.test_repo_creds ?: '' 23 | String branch = config?.branch ?: 'main' 24 | String containerImage = config?.container_image ?: 'cypress/browsers:node14.17.0-chrome91-ff89' 25 | String containerRegistry = config?.container_registry ?: 'https://index.docker.io/v1/' 26 | String containerRegistryCreds = config?.container_registry_creds ?: '' 27 | 28 | unstash "workspace" 29 | 30 | // if test_repo isn't default ('.') 31 | if (testRepo != '.') { 32 | // make temp test directory 33 | // cd into new directory 34 | // pull down test repository 35 | String prepTestRepo = """ 36 | mkdir test_repo_dir 37 | cd test_repo_dir 38 | git clone ${testRepo} . 39 | git checkout ${branch} 40 | """ 41 | if (testRepoCreds != '') { 42 | withCredentials([usernamePassword(credentialsId: testRepoCreds, passwordVariable: 'PASS', usernameVariable: 'USER')]) { 43 | sh prepTestRepo.replaceFirst("://","://${USER}:${PASS}@") 44 | } 45 | } 46 | else { 47 | sh prepTestRepo 48 | } 49 | //update reportPath 50 | reportPath = "test_repo_dir/" + reportPath 51 | } 52 | 53 | // run tests inside container 54 | docker.withRegistry("${containerRegistry}", "${containerRegistryCreds}") { 55 | docker.image("${containerImage}").inside { 56 | String runTests = """ 57 | npm ci 58 | \$(npm bin)/cypress verify 59 | ${npmScript} 60 | """ 61 | if (testRepoCreds != '') { 62 | dir('test_repo_dir') { 63 | sh runTests 64 | } 65 | } 66 | else { 67 | sh runTests 68 | } 69 | } 70 | } 71 | 72 | // archive report(s) 73 | archiveArtifacts artifacts: "${reportPath}", allowEmptyArchive: true 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /libraries/cypress/test/EndToEndTestSpec.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2022 Booz Allen Hamilton. All Rights Reserved. 3 | This software package is licensed under the Booz Allen Public License. 4 | The license can be found in the License file or at http://boozallen.github.io/licenses/bapl 5 | */ 6 | 7 | package libraries.cypress 8 | 9 | public class EndToEndTestSpec extends JTEPipelineSpecification { 10 | def EndToEndTest = null 11 | 12 | static class DummyException extends RuntimeException { 13 | DummyException(String _message) { super( _message ) } 14 | } 15 | 16 | def setup() { 17 | LinkedHashMap env = [:] 18 | LinkedHashMap config = [:] 19 | 20 | EndToEndTest = loadPipelineScriptForStep("cypress", "end_to_end_test") 21 | 22 | EndToEndTest.getBinding().setVariable("env", env) 23 | EndToEndTest.getBinding().setProperty("config", config) 24 | } 25 | 26 | def "Fails when npm_script is missing" () { 27 | setup: 28 | EndToEndTest.getBinding().setVariable("config", [report_path: "cypress/report/path"]) 29 | getPipelineMock("docker.image")(_) >> explicitlyMockPipelineVariable("Image") 30 | when: 31 | try { 32 | EndToEndTest() 33 | } catch(DummyException e) {} 34 | then: 35 | 1 * getPipelineMock("error")("Missing required parameter(s) (npm_script, report_path)") 36 | } 37 | 38 | def "Fails when report_path is missing" () { 39 | setup: 40 | EndToEndTest.getBinding().setVariable("config", [npm_script: "npm run something"]) 41 | getPipelineMock("docker.image")(_) >> explicitlyMockPipelineVariable("Image") 42 | when: 43 | try { 44 | EndToEndTest() 45 | } catch(DummyException e) {} 46 | then: 47 | 1 * getPipelineMock("error")("Missing required parameter(s) (npm_script, report_path)") 48 | } 49 | 50 | def "External test repository steps work" () { 51 | setup: 52 | EndToEndTest.getBinding().setVariable("config", [npm_script: "npm run something", report_path: "cypress/report/path", test_repo: "https://github.com/boozallen/sdp-libraries", branch: "develop"]) 53 | getPipelineMock("docker.image")(_) >> explicitlyMockPipelineVariable("Image") 54 | when: 55 | try { 56 | EndToEndTest() 57 | } catch(DummyException e) {} 58 | then: 59 | 1 * getPipelineMock("sh")({it =~ / git clone https:\/\/github.com\/boozallen\/sdp-libraries /}) 60 | } 61 | 62 | def "Cypress test step runs" () { 63 | setup: 64 | EndToEndTest.getBinding().setVariable("config", [npm_script: "npm run something", report_path: "cypress/report/path"]) 65 | when: 66 | EndToEndTest() 67 | then: 68 | 1 * getPipelineMock("docker.image")(_) >> explicitlyMockPipelineVariable("Image") 69 | 1 * getPipelineMock("Image.inside")(_ as Closure) 70 | 1 * getPipelineMock("sh")({it =~ /npm run something/}) 71 | } 72 | 73 | def "Test artifacts are archived in Jenkins" () { 74 | setup: 75 | EndToEndTest.getBinding().setVariable("config", [npm_script: "npm run something", report_path: "cypress/report/path"]) 76 | getPipelineMock("docker.image")(_) >> explicitlyMockPipelineVariable("Image") 77 | when: 78 | try { 79 | EndToEndTest() 80 | } catch(DummyException e) {} 81 | then: 82 | 1 * getPipelineMock("archiveArtifacts.call")([artifacts: "cypress/report/path", allowEmptyArchive: true ]) 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /libraries/docker/steps/get_registry_info.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2018 Booz Allen Hamilton. All Rights Reserved. 3 | This software package is licensed under the Booz Allen Public License. The license can be found in the License file or at http://boozallen.github.io/licenses/bapl 4 | */ 5 | 6 | package libraries.docker.steps 7 | 8 | def call() { 9 | def ret = [] 10 | errors = [] 11 | ret << (config.registry ?: 12 | { errors << "Application Docker Image Registry, libraries.docker.registry, not defined in pipeline config" }() ) 13 | ret << ( config.cred ?: 14 | { errors << "Application Docker Image Registry Credential, libraries.docker.cred, not defined in pipeline config" }() ) 15 | 16 | if(!errors.empty) { 17 | error errors.join("; ") 18 | } 19 | 20 | return ret 21 | } 22 | -------------------------------------------------------------------------------- /libraries/docker/steps/login_to_registry.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2018 Booz Allen Hamilton. All Rights Reserved. 3 | This software package is licensed under the Booz Allen Public License. The license can be found in the License file or at http://boozallen.github.io/licenses/bapl 4 | */ 5 | 6 | package libraries.docker.steps 7 | 8 | void call(String _url = null, String _credentialId = null, def body){ 9 | 10 | def (repository, cred) = get_registry_info() 11 | 12 | String protocol = config.registry_protocol ?: "https://" 13 | String url = _url ?: "${protocol}${repository}" 14 | String credentialId = _credentialId ?: cred 15 | 16 | docker.withRegistry(url, credentialId, body) 17 | 18 | } 19 | -------------------------------------------------------------------------------- /libraries/docker/steps/retag.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2018 Booz Allen Hamilton. All Rights Reserved. 3 | This software package is licensed under the Booz Allen Public License. The license can be found in the License file or at http://boozallen.github.io/licenses/bapl 4 | */ 5 | 6 | package libraries.docker.steps 7 | 8 | void call(old_tag, new_tag){ 9 | def remove_local_image = false 10 | if (config.remove_local_image){ 11 | if (!(config.remove_local_image instanceof Boolean)){ 12 | error "remove_local_image must be a Boolean, received [${config.remove_local_image.getClass()}]" 13 | } 14 | remove_local_image = config.remove_local_image 15 | } 16 | node{ 17 | unstash "workspace" 18 | 19 | login_to_registry{ 20 | get_images_to_build().each{ img -> 21 | sh "docker pull ${img.registry}/${img.repo}:${old_tag}" 22 | sh "docker tag ${img.registry}/${img.repo}:${old_tag} ${img.registry}/${img.repo}:${new_tag}" 23 | sh "docker push ${img.registry}/${img.repo}:${new_tag}" 24 | if (remove_local_image) sh "docker rmi -f ${img.registry}/${img.repo}:${new_tag} 2> /dev/null" 25 | } 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /libraries/docker/test/BuildSpec.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2018 Booz Allen Hamilton. All Rights Reserved. 3 | This software package is licensed under the Booz Allen Public License. The license can be found in the License file or at http://boozallen.github.io/licenses/bapl 4 | */ 5 | 6 | package libraries.docker 7 | 8 | public class BuildSpec extends JTEPipelineSpecification { 9 | 10 | def Build = null 11 | 12 | def setup() { 13 | Build = loadPipelineScriptForStep("docker", "build") 14 | explicitlyMockPipelineStep("get_images_to_build") 15 | explicitlyMockPipelineStep("login_to_registry") 16 | Build.getBinding().setVariable("config", [:]) 17 | 18 | getPipelineMock("get_images_to_build")() >> { 19 | def images = [] 20 | images << [registry: "reg1", repo: "repo1", context: "context1", tag: "tag1", dockerfile: "Dockerfile"] 21 | images << [registry: "reg2", repo: "repo2", context: "context2", tag: "tag2", dockerfile: "Dockerfile.test"] 22 | return images 23 | } 24 | } 25 | 26 | def "Unstash workspace Before Building Images" () { 27 | when: 28 | Build() 29 | then: 30 | 1 * getPipelineMock("unstash")("workspace") 31 | then: 32 | (1.._) * getPipelineMock("sh")({it =~ /^docker build .*/}) 33 | } 34 | 35 | def "Log Into Registry Before Pushing Images" () { 36 | when: 37 | Build() 38 | then: 39 | 1 * getPipelineMock("login_to_registry")(_) 40 | then: 41 | (1.._) * getPipelineMock("sh")({it =~ /^docker push .*/}) 42 | } 43 | 44 | def "Each Image is Properly Built" () { 45 | when: 46 | Build() 47 | then: 48 | 1 * getPipelineMock("sh")("docker build -f Dockerfile context1 -t reg1/repo1:tag1 ") 49 | 1 * getPipelineMock("sh")("docker build -f Dockerfile.test context2 -t reg2/repo2:tag2 ") 50 | } 51 | 52 | def "Each Image is Properly Pushed" () { 53 | when: 54 | Build() 55 | then: 56 | 1 * getPipelineMock("sh")("docker push reg1/repo1:tag1") 57 | 1 * getPipelineMock("sh")("docker push reg2/repo2:tag2") 58 | } 59 | 60 | def "If value is true for config.remove_local_image,remove local image" () { 61 | setup: 62 | Build.getBinding().setVariable("config", [remove_local_image: true]) 63 | when: 64 | Build() 65 | then: 66 | 1 * getPipelineMock("sh")("docker rmi -f reg1/repo1:tag1 2> /dev/null") 67 | 1 * getPipelineMock("sh")("docker rmi -f reg2/repo2:tag2 2> /dev/null") 68 | } 69 | 70 | def "If value is false for config.remove_local_image,do not remove local image" () { 71 | setup: 72 | Build.getBinding().setVariable("config", [remove_local_image: false]) 73 | when: 74 | Build() 75 | then: 76 | 0 * getPipelineMock("sh")("docker rmi -f reg1/repo1:tag1 2> /dev/null") 77 | 0 * getPipelineMock("sh")("docker rmi -f reg2/repo2:tag2 2> /dev/null") 78 | } 79 | 80 | def "If value is null for config.remove_local_image,do not remove local image" () { 81 | setup: 82 | Build.getBinding().setVariable("config", [remove_local_image: null]) 83 | when: 84 | Build() 85 | then: 86 | 0 * getPipelineMock("sh")("docker rmi -f reg1/repo1:tag1 2> /dev/null") 87 | 0 * getPipelineMock("sh")("docker rmi -f reg2/repo2:tag2 2> /dev/null") 88 | } 89 | 90 | def "If value is not a Boolean for config.remove_local_image,throw error" () { 91 | setup: 92 | Build.getBinding().setVariable("config", [remove_local_image: "true"]) 93 | when: 94 | Build() 95 | then: 96 | 1 * getPipelineMock("error")(_) 97 | } 98 | 99 | } 100 | -------------------------------------------------------------------------------- /libraries/docker/test/BuildxSpec.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2018 Booz Allen Hamilton. All Rights Reserved. 3 | This software package is licensed under the Booz Allen Public License. The license can be found in the License file or at http://boozallen.github.io/licenses/bapl 4 | */ 5 | 6 | package libraries.docker 7 | 8 | public class BuildxSpec extends JTEPipelineSpecification { 9 | 10 | def Buildx = null 11 | 12 | def setup() { 13 | Buildx = loadPipelineScriptForStep("docker", "buildx") 14 | explicitlyMockPipelineStep("get_images_to_build") 15 | explicitlyMockPipelineStep("login_to_registry") 16 | Buildx.getBinding().setVariable("config", [build_strategy: 'buildx']) 17 | 18 | getPipelineMock("get_images_to_build")() >> { 19 | def images = [] 20 | images << [registry: "reg1", repo: "repo1", context: "context1", dockerfilePath: "", tag: "tag1", build_args: null, platforms: ["arm", "amd64"], useLatestTag: true] 21 | images << [registry: "reg2", repo: "repo2", context: "context2", dockerfilePath: "", tag: "tag2", build_args: [BASE_IMAGE:"image"], platforms: ["arm", "amd64"], useLatestTag: false] 22 | return images 23 | } 24 | } 25 | 26 | def "Unstash workspace Before Building Images" () { 27 | when: 28 | Buildx() 29 | then: 30 | 1 * getPipelineMock("unstash")("workspace") 31 | then: 32 | (1.._) * getPipelineMock("sh")({it =~ /^docker buildx */}) 33 | } 34 | 35 | def "Log Into Registry Before Pushing Images" () { 36 | when: 37 | Buildx() 38 | then: 39 | 1 * getPipelineMock("login_to_registry")(_) 40 | then: 41 | (1.._) * getPipelineMock("sh")({it =~ /^docker buildx */}) 42 | } 43 | 44 | def "Each Image is Properly Built and pushed" () { 45 | when: 46 | Buildx() 47 | then: 48 | 1 * getPipelineMock("sh")("docker buildx build context1 -t reg1/repo1:tag1 -t reg1/repo1:latest --platform arm,amd64 --push") 49 | 1 * getPipelineMock("sh")("docker buildx build context2 -t reg2/repo2:tag2 --build-arg BASE_IMAGE='image' --platform arm,amd64 --push") 50 | } 51 | 52 | def "If value is true for config.setExperimentalFlag,set the experimental flag" () { 53 | setup: 54 | Buildx.getBinding().setVariable("config", [setExperimentalFlag: true, build_strategy: 'buildx']) 55 | when: 56 | Buildx() 57 | then: 58 | 1 * getPipelineMock("sh")("DOCKER_CLI_EXPERIMENTAL=enabled docker buildx build context1 -t reg1/repo1:tag1 -t reg1/repo1:latest --platform arm,amd64 --push") 59 | 1 * getPipelineMock("sh")("DOCKER_CLI_EXPERIMENTAL=enabled docker buildx build context2 -t reg2/repo2:tag2 --build-arg BASE_IMAGE='image' --platform arm,amd64 --push") 60 | } 61 | def "If value is false for config.setExperimentalFlag,set the experimental flag to false" () { 62 | setup: 63 | Buildx.getBinding().setVariable("config", [setExperimentalFlag: false, build_strategy: 'buildx']) 64 | when: 65 | Buildx() 66 | then: 67 | 0 * getPipelineMock("sh")("DOCKER_CLI_EXPERIMENTAL=enabled docker buildx build context1 -t reg1/repo1:tag1 -t reg1/repo1:latest --platform arm,amd64 --push") 68 | 0 * getPipelineMock("sh")("DOCKER_CLI_EXPERIMENTAL=enabled docker buildx build context2 -t reg2/repo2:tag2 --build-arg BASE_IMAGE='image' --platform arm,amd64 --push") 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /libraries/docker/test/GetRegistryInfoSpec.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2018 Booz Allen Hamilton. All Rights Reserved. 3 | This software package is licensed under the Booz Allen Public License. The license can be found in the License file or at http://boozallen.github.io/licenses/bapl 4 | */ 5 | 6 | package libraries.docker 7 | 8 | public class GetRegistryInfoSpec extends JTEPipelineSpecification { 9 | 10 | def GetRegistryInfo = null 11 | 12 | def setup() { 13 | GetRegistryInfo = loadPipelineScriptForStep("docker","get_registry_info") 14 | } 15 | 16 | def "Missing Registry Throws Error" () { 17 | setup: 18 | GetRegistryInfo.getBinding().setVariable("config", [registry: null, cred: "credential"]) 19 | def missing_reg_message = "Application Docker Image Registry, libraries.docker.registry, not defined in pipeline config" 20 | when: 21 | GetRegistryInfo() 22 | then: 23 | 1 * getPipelineMock("error")(missing_reg_message) 24 | } 25 | 26 | def "Missing Credential Throws Error" () { 27 | setup: 28 | GetRegistryInfo.getBinding().setVariable("config", [registry: "registry", cred: null]) 29 | def missing_cred_message = "Application Docker Image Registry Credential, libraries.docker.cred, not defined in pipeline config" 30 | when: 31 | GetRegistryInfo() 32 | then: 33 | 1 * getPipelineMock("error")(missing_cred_message) 34 | } 35 | 36 | def "Missing Registry and Credential Outputs Combined Error Message" () { 37 | setup: 38 | GetRegistryInfo.getBinding().setVariable("config", [:]) 39 | def missing_reg_message = "Application Docker Image Registry, libraries.docker.registry, not defined in pipeline config" 40 | def missing_cred_message = "Application Docker Image Registry Credential, libraries.docker.cred, not defined in pipeline config" 41 | when: 42 | GetRegistryInfo() 43 | then: 44 | 1 * getPipelineMock("error")("${missing_reg_message}; ${missing_cred_message}") 45 | } 46 | 47 | def "Return Value is a Two-Element Array of [Registry, Credential]" () { 48 | setup: 49 | GetRegistryInfo.getBinding().setVariable("config", [registry: "reg", cred: "cred"]) 50 | when: 51 | def regInfo = GetRegistryInfo() 52 | then: 53 | 0 * getPipelineMock("error")(_) 54 | regInfo == ["reg", "cred"] 55 | } 56 | 57 | 58 | } 59 | -------------------------------------------------------------------------------- /libraries/docker/test/LoginToRegistrySpec.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2018 Booz Allen Hamilton. All Rights Reserved. 3 | This software package is licensed under the Booz Allen Public License. The license can be found in the License file or at http://boozallen.github.io/licenses/bapl 4 | */ 5 | 6 | package libraries.docker 7 | 8 | public class LoginToRegistrySpec extends JTEPipelineSpecification { 9 | 10 | def login_to_registry = null 11 | 12 | def setup() { 13 | login_to_registry = loadPipelineScriptForStep("docker", "login_to_registry") 14 | login_to_registry.getBinding().setVariable("config", [:]) 15 | explicitlyMockPipelineStep("get_registry_info") 16 | } 17 | 18 | def "login to registry executes closure"(){ 19 | when: 20 | 1 * getPipelineMock("get_registry_info")() >> { return ["test_registry", "test_cred_id"] } 21 | login_to_registry{ 22 | echo "hi" 23 | } 24 | then: 25 | 1 * getPipelineMock("echo")("hi") 26 | } 27 | 28 | 29 | def "login to registry passes method parameters to docker.withRegistry if provided"(){ 30 | when: 31 | 1 * getPipelineMock("get_registry_info")() >> { return ["test_registry", "test_cred_id"] } 32 | login_to_registry("parameter_registry_url", "parameter_credential_id"){ 33 | echo "hi" 34 | } 35 | then: 36 | 1 * getPipelineMock("docker.withRegistry")("parameter_registry_url", "parameter_credential_id", _) 37 | } 38 | 39 | def "default protocol for user-provided registry is https://"(){ 40 | when: 41 | 1 * getPipelineMock("get_registry_info")() >> { return ["test_registry", "test_cred_id"] } 42 | login_to_registry{ 43 | echo "hi" 44 | } 45 | then: 46 | 1 * getPipelineMock("docker.withRegistry")("https://test_registry", _, _) 47 | } 48 | 49 | def "setting the config.registry_protocol changes the protocol"(){ 50 | when: 51 | 1 * getPipelineMock("get_registry_info")() >> { return ["test_registry", "test_cred_id"] } 52 | login_to_registry.getBinding().setVariable("config", [ registry_protocol: "http://" ]) 53 | login_to_registry{ 54 | echo "hi" 55 | } 56 | then: 57 | 1 * getPipelineMock("docker.withRegistry")("http://test_registry", _, _) 58 | } 59 | 60 | 61 | 62 | } 63 | -------------------------------------------------------------------------------- /libraries/docker/test/RetagSpec.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2018 Booz Allen Hamilton. All Rights Reserved. 3 | This software package is licensed under the Booz Allen Public License. The license can be found in the License file or at http://boozallen.github.io/licenses/bapl 4 | */ 5 | 6 | package libraries.docker 7 | 8 | public class RetagSpec extends JTEPipelineSpecification { 9 | 10 | def Retag = null 11 | 12 | def setup() { 13 | Retag = loadPipelineScriptForStep("docker","retag") 14 | explicitlyMockPipelineStep("login_to_registry") 15 | explicitlyMockPipelineStep("get_images_to_build") 16 | Retag.getBinding().setVariable("config", [:]) 17 | } 18 | 19 | def "Log Into Registry Before Pushing or Pulling Images" () { 20 | when: 21 | Retag("tag_viejo", "tag_nuevo") 22 | then: 23 | 1 * getPipelineMock("login_to_registry")(_) 24 | then: 25 | 1 * getPipelineMock("get_images_to_build")() >> [[registry: "Reg", repo: "Repo"]] 26 | (1.._) * getPipelineMock("sh")({it =~ /^docker .*/}) 27 | 28 | } 29 | 30 | def "A Single Image is Properly Read & Retagged" () { 31 | 32 | when: 33 | Retag("tag_viejo", "tag_nuevo") 34 | then: 35 | 1 * getPipelineMock("get_images_to_build")() >> [[registry: "Reg", repo: "Repo"]] 36 | 1 * getPipelineMock("sh")("docker pull Reg/Repo:tag_viejo" ) 37 | 1 * getPipelineMock("sh")("docker tag Reg/Repo:tag_viejo Reg/Repo:tag_nuevo") 38 | 1 * getPipelineMock("sh")("docker push Reg/Repo:tag_nuevo") 39 | } 40 | 41 | def "Multiple Images are Properly Read & Retagged" () { 42 | def images = [] 43 | images << [registry: "Reg1", repo: "Repo1"] 44 | images << [registry: "Reg2", repo: "Repo2"] 45 | images << [registry: "Reg3", repo: "Repo3"] 46 | when: 47 | Retag("tag_viejo", "tag_nuevo") 48 | then: 49 | 1 * getPipelineMock("get_images_to_build")() >> images 50 | 51 | images.each{ img -> 52 | 1 * getPipelineMock("sh")("docker pull ${img.registry}/${img.repo}:tag_viejo" ) 53 | 1 * getPipelineMock("sh")("docker tag ${img.registry}/${img.repo}:tag_viejo ${img.registry}/${img.repo}:tag_nuevo") 54 | 1 * getPipelineMock("sh")("docker push ${img.registry}/${img.repo}:tag_nuevo") 55 | } 56 | } 57 | 58 | def "If value is true for config.remove_local_image,remove local image" () { 59 | setup: 60 | Retag.getBinding().setVariable("config", [remove_local_image: true]) 61 | when: 62 | Retag("tag_viejo", "tag_nuevo") 63 | then: 64 | 1 * getPipelineMock("get_images_to_build")() >> [[registry: "Reg", repo: "Repo"]] 65 | 1 * getPipelineMock("sh")("docker rmi -f Reg/Repo:tag_nuevo 2> /dev/null") 66 | } 67 | 68 | def "If value is false for config.remove_local_image,do not remove local image" () { 69 | setup: 70 | Retag.getBinding().setVariable("config", [remove_local_image: false]) 71 | when: 72 | Retag("tag_viejo", "tag_nuevo") 73 | then: 74 | 1 * getPipelineMock("get_images_to_build")() >> [[registry: "Reg", repo: "Repo"]] 75 | 0 * getPipelineMock("sh")("docker rmi -f Reg/Repo:tag_nuevo 2> /dev/null") 76 | } 77 | 78 | def "If value is null for config.remove_local_image,do not remove local image" () { 79 | setup: 80 | Retag.getBinding().setVariable("config", [remove_local_image: null]) 81 | when: 82 | Retag("tag_viejo", "tag_nuevo") 83 | then: 84 | 1 * getPipelineMock("get_images_to_build")() >> [[registry: "Reg", repo: "Repo"]] 85 | 0 * getPipelineMock("sh")("docker rmi -f Reg/Repo:tag_nuevo 2> /dev/null") 86 | } 87 | 88 | def "If value is not a Boolean for config.remove_local_image,throw error" () { 89 | setup: 90 | Retag.getBinding().setVariable("config", [remove_local_image: "true"]) 91 | when: 92 | Retag("tag_viejo", "tag_nuevo") 93 | then: 94 | 1 * getPipelineMock("error")(_) 95 | } 96 | 97 | } 98 | -------------------------------------------------------------------------------- /libraries/docker_compose/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: Uses docker compose to deploy and tear down containers 3 | --- 4 | 5 | # Docker Compose 6 | 7 | This library allows you to perform docker compose commands. 8 | 9 | ## Steps 10 | 11 | --- 12 | 13 | | Step | Description | 14 | |----------|----------------------------------------------------------------------| 15 | | `up()` | Runs `docker-compose up` with values taken from the configuration. | 16 | | `down()` | Runs `docker-compose down` with values taken from the configuration. | 17 | 18 | ## Example Usage 19 | 20 | --- 21 | 22 | ```groovy 23 | compose.up() 24 | compose.down() 25 | ``` 26 | 27 | ## Configuration 28 | 29 | --- 30 | 31 | The library configurations for `docker_compose` are as follows: 32 | 33 | | Parameter | Description | 34 | |-----------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| 35 | | `files` | Optional list of ordered docker compose files to run. Omitting this parameter causes the command `docker-compose up` to run on a file named `docker-compose.yml`. | 36 | | `env` | Optional environment file to pass to the docker-compose command. | 37 | | `sleep` | Optional configuration that controls how long to wait after running the `up()` command before continuing the pipeline execution. This is helpful when the Docker containers need to be started before other steps, like integration tests, may run. | 38 | 39 | ## Example Library Configuration 40 | 41 | --- 42 | 43 | ```groovy 44 | libraries{ 45 | docker_compose { 46 | files = ["docker-compose.it.yml"] 47 | env = ".env.ci" 48 | sleep { 49 | time: 1 50 | unit: "MINUTES" 51 | } 52 | } 53 | } 54 | ``` 55 | 56 | ## Dependencies 57 | 58 | --- 59 | 60 | * Docker and docker-compose installed on Jenkins. 61 | -------------------------------------------------------------------------------- /libraries/docker_compose/steps/compose.groovy: -------------------------------------------------------------------------------- 1 | package libraries.docker_compose.steps 2 | 3 | void up() { 4 | stage("Deploy: Docker Compose") { 5 | String command = "docker-compose " 6 | command = addFiles(command) 7 | command = addEnvFile(command) 8 | command += "up -d" 9 | 10 | sh command 11 | 12 | if (config.sleep) { 13 | sleep time: config.sleep.time, unit: config.sleep.unit 14 | } 15 | } 16 | } 17 | 18 | void down() { 19 | stage("Teardown: Docker Compose") { 20 | String command = "docker-compose " 21 | command = addFiles(command) 22 | command = addEnvFile(command) 23 | command += "down" 24 | 25 | sh command 26 | } 27 | } 28 | 29 | String addEnvFile(String command) { 30 | if (config.env) { 31 | command += "--env-file ${config.env} " 32 | } 33 | return command 34 | } 35 | 36 | String addFiles(String command) { 37 | if (config.files) { 38 | config.files.each { file -> command += "-f ${file} " } 39 | } 40 | return command 41 | } -------------------------------------------------------------------------------- /libraries/docker_compose/test/DockerComposeSpec.groovy: -------------------------------------------------------------------------------- 1 | package libraries.docker_compose 2 | 3 | import JTEPipelineSpecification 4 | 5 | class DockerComposeSpec extends JTEPipelineSpecification { 6 | def dockerCompose = null 7 | def DOCKER_COMPOSE_COMMAND = "docker-compose " 8 | 9 | def setup() { 10 | dockerCompose = loadPipelineScriptForTest("docker_compose/steps/compose.groovy") 11 | explicitlyMockPipelineStep("sh") 12 | explicitlyMockPipelineStep("sleep") 13 | dockerCompose.getBinding().setVariable("config", [:]) 14 | } 15 | 16 | def "Run up with default values" () { 17 | when: 18 | dockerCompose.up() 19 | then: 20 | 1 * getPipelineMock("stage")("Deploy: Docker Compose", _) 21 | then: 22 | 1 * getPipelineMock("sh")(DOCKER_COMPOSE_COMMAND + "up -d") 23 | } 24 | 25 | def "Run down with default values" () { 26 | when: 27 | dockerCompose.down() 28 | then: 29 | 1 * getPipelineMock("stage")("Teardown: Docker Compose", _) 30 | then: 31 | 1 * getPipelineMock("sh")(DOCKER_COMPOSE_COMMAND + "down") 32 | } 33 | 34 | def "Run up with configurations" () { 35 | Map sleepConfig = [time: 1, unit: "MINUTES"] 36 | dockerCompose.getBinding().setVariable("config", 37 | [files: ["docker-compose.it.yml", "docker-compose.ci.yml"], env: ".env.ci", sleep: sleepConfig]) 38 | when: 39 | dockerCompose.up() 40 | then: 41 | 1 * getPipelineMock("stage")("Deploy: Docker Compose", _) 42 | then: 43 | 1 * getPipelineMock("sh")(DOCKER_COMPOSE_COMMAND + "-f docker-compose.it.yml -f docker-compose.ci.yml " + 44 | "--env-file .env.ci up -d") 45 | then: 46 | 1 * getPipelineMock("sleep")(sleepConfig) 47 | } 48 | 49 | def "Run down with configurations" () { 50 | Map sleepConfig = [time: 1, unit: "MINUTES"] 51 | dockerCompose.getBinding().setVariable("config", 52 | [files: ["docker-compose.it.yml", "docker-compose.ci.yml"], env: ".env.ci", sleep: sleepConfig]) 53 | when: 54 | dockerCompose.down() 55 | then: 56 | 1 * getPipelineMock("stage")("Teardown: Docker Compose", _) 57 | then: 58 | 1 * getPipelineMock("sh")(DOCKER_COMPOSE_COMMAND + "-f docker-compose.it.yml -f docker-compose.ci.yml " + 59 | "--env-file .env.ci down") 60 | then: 61 | 0 * getPipelineMock("sleep")(sleepConfig) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /libraries/dotnet/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: This library allows you to perform .NET build and test commands in the SDP dotnet-sdk agent container 3 | --- 4 | 5 | # DotNet 6 | 7 | This library allows you to perform .NET build and test commands in the SDP `dotnet-sdk` agent container. 8 | 9 | ## Steps 10 | 11 | | Step | Description | 12 | | -------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | 13 | | `source_build` | This step leverages the `dotnet publish` command to build your application and output the results to the specified directory via `outDir` variable. `outDir` defaults to a folder named "bin." The specified folder is archived as a Jenkins artifact. | 14 | | `unit_test` | This step leverages the `dotnet test` command to run the unit, integration and functional tests specified in the application repository and outputs the results to a specified directory via `resultDir` variable. `resultDir` defaults to a folder named "coverage." The specified folder is archived as a Jenkins artifact. | 15 | 16 | ## Configuration 17 | 18 | ``` groovy title='pipeline_config.groovy' 19 | libraries { 20 | dotnet { 21 | sdk_image = 'dotnet-sdk:6.0.106' 22 | source_build { 23 | outDir = "applicationOutput" 24 | } 25 | unit_test { 26 | resultDir = "Results" 27 | } 28 | } 29 | } 30 | ``` 31 | 32 | ## Dependencies 33 | 34 | * The SDP library 35 | * Access to a `dotnet-sdk` build agent container via the repository defined in your SDP library configuration 36 | -------------------------------------------------------------------------------- /libraries/dotnet/steps/dotnet_invoke.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2018 Booz Allen Hamilton. All Rights Reserved. 3 | This software package is licensed under the Booz Allen Public License. The license can be found in the License file or at http://boozallen.github.io/licenses/bapl 4 | */ 5 | 6 | package libraries.dotnet.steps 7 | 8 | @StepAlias(["source_build", "unit_test"]) 9 | void call() { 10 | String stepName = "" 11 | String outDir = "" 12 | String resultDir = "" 13 | 14 | String sdkImage = config?.sdk_image ?: "dotnet-sdk:latest" 15 | 16 | switch(stepContext.name) { 17 | case "source_build": 18 | stepName = "DotNet Build" 19 | outDir = config?.source_build?.outDir ?: "bin" 20 | break 21 | case "unit_test": 22 | stepName = "DotNet Unit Test" 23 | resultDir = config?.unit_test?.resultDir ?: "coverage" 24 | break 25 | default: 26 | error("step name must be \"source_build\" or \"unit_test\" got \"${stepContext.name}\"") 27 | } 28 | 29 | stage(stepName) { 30 | inside_sdp_image "${sdkImage}", { 31 | unstash "workspace" 32 | 33 | if (stepName == "DotNet Build") { 34 | try { 35 | sh "dotnet publish -c release -o ${outDir}" 36 | } 37 | catch (any) { 38 | throw any 39 | } 40 | finally { 41 | archiveArtifacts artifacts: "${outDir}/*.*, allowEmptyArchive: true" 42 | } 43 | } 44 | else { 45 | //Execute dotnet tests and output to coverage directory 46 | try { 47 | sh "rm -drf ${resultDir}" 48 | sh "dotnet test --collect:'XPlat Code Coverage' --results-directory ${resultDir} --logger trx" 49 | } 50 | catch (any) { 51 | throw any 52 | } 53 | finally { 54 | archiveArtifacts artifacts: "${resultDir}/**/*, allowEmptyArchive: true" 55 | } 56 | } 57 | 58 | stash "workspace" 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /libraries/dotnet/test/DotNetInvokeSpec.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2018 Booz Allen Hamilton. All Rights Reserved. 3 | This software package is licensed under the Booz Allen Public License. The license can be found in the License file or at http://boozallen.github.io/licenses/bapl 4 | */ 5 | 6 | package libraries.dotnet 7 | 8 | public class DotNetInvokeSpec extends JTEPipelineSpecification { 9 | def DotNetInvoke = null 10 | 11 | def buildCommand = 'dotnet publish -c release -o ${outDir}' 12 | 13 | def testCommand = ''' 14 | rm -drf ${ResultDir} 15 | dotnet test --collect:'XPlat Code Coverage' --results-directory ${resultDir} --logger trx 16 | ''' 17 | 18 | LinkedHashMap minimalSourceBuildConfig = [ 19 | source_build: [ 20 | stepName: "source_build", 21 | outDir: "OutTest" 22 | ] 23 | ] 24 | LinkedHashMap minimalUnitTestConfig = [ 25 | unit_test: [ 26 | stepName: "unit_test", 27 | resultDir: "test" 28 | ] 29 | ] 30 | 31 | def setup() { 32 | explicitlyMockPipelineStep("inside_sdp_image") 33 | 34 | DotNetInvoke = loadPipelineScriptForStep("dotnet", "dotnet_invoke") 35 | } 36 | 37 | def "Unit tests run successfully" () { 38 | setup: 39 | DotNetInvoke.getBinding().setVariable("stepContext", [name: "unit_test"]) 40 | DotNetInvoke.getBinding().setVariable("config", [unit_test: [resultDir: "test"]]) 41 | when: 42 | DotNetInvoke() 43 | then: 44 | noExceptionThrown() 45 | 1 * getPipelineMock("sh")("rm -drf test") 46 | 1 * getPipelineMock("sh")("dotnet test --collect:'XPlat Code Coverage' --results-directory test --logger trx") 47 | } 48 | 49 | def "Source build runs successfully" () { 50 | setup: 51 | DotNetInvoke.getBinding().setVariable("stepContext", [name: "source_build"]) 52 | DotNetInvoke.getBinding().setVariable("config", [source_build: [outDir: "test"]]) 53 | when: 54 | DotNetInvoke() 55 | then: 56 | noExceptionThrown() 57 | 1 * getPipelineMock("sh")("dotnet publish -c release -o test") 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /libraries/git/library_config.groovy: -------------------------------------------------------------------------------- 1 | fields{ 2 | optional{ 3 | gitlab{ 4 | connection = String 5 | job_name = String 6 | job_status = String 7 | } 8 | github 9 | github_enterprise 10 | } 11 | } -------------------------------------------------------------------------------- /libraries/git/steps/git.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2020 Booz Allen Hamilton. All Rights Reserved. 3 | This software package is licensed under the Booz Allen Public License. The license can be found in the License file or at http://boozallen.github.io/licenses/bapl 4 | */ 5 | package libraries.git.steps 6 | 7 | import org.codehaus.groovy.runtime.GStringImpl 8 | 9 | void call(Map args){ 10 | 11 | // supported git actions 12 | actions = [ 13 | "add": { files -> 14 | if (files instanceof String) sh "git add ${files}" 15 | else if (files instanceof GStringImpl) sh "git add ${files}" 16 | else sh "git add ${files.join(" ")}" 17 | }, 18 | "commit": { message -> 19 | sh "git config user.email 'jenkins@nothing.com' && git config user.name 'Jenkins'" 20 | sh script: "git commit -m \"${message}\"", returnStatus: true 21 | }, 22 | "push": { flags -> 23 | sh "git push ${flags ?: ""} ${env.git_url_with_creds}" 24 | }, 25 | "checkout": { branch -> 26 | sh "git checkout ${branch}" 27 | } 28 | ] 29 | 30 | // validate that there are no invalid inputs 31 | invalid_args = args.findAll{ !(it.getKey() in actions.keySet()) } 32 | if (invalid_args) error "Unknown git actions: ${invalid_args.collect{ it.getKey() }.join(", ")}" 33 | 34 | // validate the user is doing an action 35 | if (!(args.subMap(actions.keySet()))) error "git: You must use an action: ${actions.keySet().join(", ")}" 36 | 37 | 38 | // do the things. 39 | args.each{ action, value -> 40 | def c = actions.get(action) 41 | c.resolveStrategy = Closure.DELEGATE_FIRST 42 | c.delegate = this 43 | c.call(value) 44 | } 45 | } 46 | 47 | void call(String action){ 48 | String a = action.toString() 49 | this.call([ 50 | (a): null 51 | ]) 52 | } -------------------------------------------------------------------------------- /libraries/git/steps/git_distributions.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2020 Booz Allen Hamilton. All Rights Reserved. 3 | This software package is licensed under the Booz Allen Public License. The license can be found in the License file or at http://boozallen.github.io/licenses/bapl 4 | */ 5 | package libraries.git.steps 6 | 7 | /* 8 | Validate library configuration 9 | */ 10 | @Init 11 | void call(){ 12 | 13 | /* 14 | define a map of distributions and a closure for their own validations 15 | */ 16 | def options = [ 17 | "gitlab": { c -> println "gitlab config is ${c}"}, 18 | "github": { c -> println "github config is ${c}"}, 19 | "github_enterprise": { c -> println "github enterprise config is ${c}"} 20 | ] 21 | 22 | def submap = config.subMap(options.keySet()) 23 | if(submap.size() != 1){ 24 | error "you must configure one distribution option, currently: ${submap.keySet()}" 25 | } 26 | 27 | // get the distribution 28 | String dist = submap.keySet().first() 29 | // invoke the distribution closure 30 | options[dist](config[dist]) 31 | 32 | env.GIT_LIBRARY_DISTRUBITION = dist 33 | this.init_env() 34 | } 35 | 36 | // Initialize Git configuration of env vars 37 | void init_env(){ 38 | node{ 39 | try{ unstash "workspace" } 40 | catch(ignored) { 41 | println "'workspace' stash not present. Skipping git library environment variable initialization. To change this behavior, ensure the 'sdp' library is loaded" 42 | return 43 | } 44 | 45 | env.GIT_URL = scm.getUserRemoteConfigs()[0].getUrl() 46 | env.GIT_CREDENTIAL_ID = scm.getUserRemoteConfigs()[0].credentialsId.toString() 47 | def parts = env.GIT_URL.split("/") 48 | for (part in parts){ 49 | parts = parts.drop(1) 50 | if (part.contains(".")) break 51 | } 52 | env.ORG_NAME = parts.getAt(0) 53 | env.REPO_NAME = parts[1..-1].join("/") - ".git" 54 | env.GIT_SHA = sh(script: "git rev-parse HEAD", returnStdout: true).trim() 55 | 56 | if (env.CHANGE_TARGET){ 57 | env.GIT_BUILD_CAUSE = "pr" 58 | } else { 59 | env.GIT_BUILD_CAUSE = sh ( 60 | script: 'git rev-list HEAD --parents -1 | wc -w', // will have 2 shas if commit, 3 or more if merge 61 | returnStdout: true 62 | ).trim().toInteger() > 2 ? "merge" : "commit" 63 | } 64 | 65 | println "Found Git Build Cause: ${env.GIT_BUILD_CAUSE}" 66 | } 67 | return 68 | } 69 | 70 | def fetch(){ 71 | def stepName = env.GIT_LIBRARY_DISTRUBITION 72 | 73 | if(jteVersion.lessThanOrEqualTo("2.0.4")){ 74 | return getBinding().getStep(stepName) 75 | } else { 76 | return this.getStepFromCollector(stepName) 77 | } 78 | } 79 | 80 | @NonCPS 81 | def getStepFromCollector(String stepName){ 82 | try { 83 | Class collector = Class.forName("org.boozallen.plugins.jte.init.primitives.TemplatePrimitiveCollector") 84 | List steps = collector.current().getSteps(stepName) 85 | 86 | if(steps.size() == 0){ 87 | error "Step '${stepName}' not found." 88 | } else if (steps.size() > 1){ 89 | error "Ambiguous step name '${stepName}'. Found multiple steps by that name." 90 | } else { 91 | return steps.first().getValue(null) 92 | } 93 | } catch(ClassNotFoundException ex) { 94 | error "can't find the TemplatePrimitiveCollector class. That's odd. current JTE version is '${jteVersion.get()}'. You should submit an issue at https://github.com/jenkinsci/templating-engine-plugin/issues/new/choose" 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /libraries/git/steps/github.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2020 Booz Allen Hamilton. All Rights Reserved. 3 | This software package is licensed under the Booz Allen Public License. The license can be found in the License file or at http://boozallen.github.io/licenses/bapl 4 | */ 5 | package libraries.git.steps 6 | 7 | // Import code required for GitHub functions 8 | import org.kohsuke.github.GitHub 9 | 10 | /* 11 | returns the name of the source branch in a Pull Request 12 | for example, in a MR from feature to development, the source branch 13 | would be "feature" 14 | */ 15 | def get_source_branch(){ 16 | 17 | def cred_id = env.GIT_CREDENTIAL_ID 18 | 19 | withCredentials([usernamePassword(credentialsId: cred_id, passwordVariable: 'PAT', usernameVariable: 'USER')]) { 20 | return GitHub.connectUsingOAuth(PAT). 21 | getRepository("${env.ORG_NAME}/${env.REPO_NAME}") 22 | .getPullRequest(env.CHANGE_ID.toInteger()) 23 | .getHead() 24 | .getRef() 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /libraries/git/steps/github_enterprise.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2020 Booz Allen Hamilton. All Rights Reserved. 3 | This software package is licensed under the Booz Allen Public License. The license can be found in the License file or at http://boozallen.github.io/licenses/bapl 4 | */ 5 | package libraries.git.steps 6 | 7 | // Import code required for GitHub functions 8 | import org.kohsuke.github.GitHub 9 | 10 | /* 11 | fetches the name of the source branch in a Pull Request. 12 | */ 13 | def get_source_branch(){ 14 | String ghUrl = "${env.GIT_URL.split("/")[0..-3].join("/")}/api/v3" 15 | def repo, org 16 | withCredentials([ 17 | usernamePassword(credentialsId: env.GIT_CREDENTIAL_ID, passwordVariable: 'PAT', usernameVariable: 'USER') 18 | ]) { 19 | return GitHub.connectToEnterprise(ghUrl, PAT).getRepository("${env.ORG_NAME}/${env.REPO_NAME}") 20 | .getPullRequest(env.CHANGE_ID.toInteger()) 21 | .getHead() 22 | .getRef() 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /libraries/git/steps/gitlab.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2020 Booz Allen Hamilton. All Rights Reserved. 3 | This software package is licensed under the Booz Allen Public License. The license can be found in the License file or at http://boozallen.github.io/licenses/bapl 4 | */ 5 | package libraries.git.steps 6 | // Import code required for GitLab functions 7 | 8 | 9 | /* 10 | returns the name of the source branch in a Merge Request 11 | for example, in a MR from feature to development, the source branch 12 | would be "feature" 13 | */ 14 | def get_source_branch(){ 15 | node{ 16 | def gitlabUrl = "${env.GIT_URL.split("/")[0..-3].join("/")}" 17 | withCredentials([ 18 | usernamePassword( 19 | credentialsId: env.GIT_CREDENTIAL_ID, 20 | passwordVariable: 'PAT', 21 | usernameVariable: 'USER' 22 | ) 23 | ]) { 24 | if ((env.ORG_NAME).isEmpty()){ 25 | projectName = "${env.REPO_NAME}" 26 | } else { 27 | projectName = "${env.ORG_NAME}/${env.REPO_NAME}" 28 | } 29 | 30 | return this.class.classLoader.loadClass( 'org.gitlab4j.api.GitLabApi', true, false )?.newInstance(gitlabUrl, PAT).getMergeRequestApi().getMergeRequest(projectName.toString(), env.CHANGE_ID.toInteger()).getSourceBranch() 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /libraries/git/steps/gitlab_status.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2020 Booz Allen Hamilton. All Rights Reserved. 3 | This software package is licensed under the Booz Allen Public License. The license can be found in the License file or at http://boozallen.github.io/licenses/bapl 4 | */ 5 | package libraries.git.steps 6 | 7 | /* 8 | glConnection = Name assigned to the GitLab connection set when configuring Jenkins. 9 | jobName = Name of the pipeline job triggered in Gitlab. Typically "/branch" 10 | jobStatus = Status of job being performed. Accepts the following: "pending", "running", "canceled", "failed", "success" 11 | */ 12 | def call(String gl_connection = null, String gl_job_name = null, String gl_job_status = null){ 13 | 14 | def gitlab = config.gitlab ?: [:] 15 | 16 | def job_connection = gl_connection ?: gitlab.connection ?: 17 | { error "gitlab connection must be a valid string" }() 18 | 19 | def job_name = gl_job_name ?: gitlab.job_name ?: 20 | { error "gitlab job name must be a valid string" }() 21 | 22 | def job_status = gl_job_status ?: gitlab.job_status ?: 23 | { error "gitlab job status must be a valid string" }() 24 | 25 | properties([gitLabConnection(job_connection)]) 26 | updateGitlabCommitStatus name: job_name , state: job_status 27 | 28 | } 29 | -------------------------------------------------------------------------------- /libraries/git/steps/on_change.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2018 Booz Allen Hamilton. All Rights Reserved. 3 | This software package is licensed under the Booz Allen Public License. The license can be found in the License file or at http://boozallen.github.io/licenses/bapl 4 | */ 5 | package libraries.git.steps 6 | 7 | void call(Map args = [:], body){ 8 | 9 | // do nothing if not commit or merge 10 | if (!(env.GIT_BUILD_CAUSE in ["commit", "merge"])) 11 | return 12 | 13 | def branch = env.BRANCH_NAME 14 | 15 | // do nothing if branch doesn't match regex 16 | if (args.to) 17 | if (!(branch ==~ args.to)) 18 | return 19 | 20 | println "running because of a commit to ${branch}" 21 | body() 22 | 23 | } 24 | -------------------------------------------------------------------------------- /libraries/git/steps/on_commit.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2018 Booz Allen Hamilton. All Rights Reserved. 3 | This software package is licensed under the Booz Allen Public License. The license can be found in the License file or at http://boozallen.github.io/licenses/bapl 4 | */ 5 | package libraries.git.steps 6 | 7 | void call(Map args = [:], body){ 8 | 9 | // do nothing if not commit 10 | if (!env.GIT_BUILD_CAUSE.equals("commit")) 11 | return 12 | 13 | def branch = env.BRANCH_NAME 14 | 15 | // do nothing if branch doesn't match regex 16 | if (args.to) 17 | if (!(branch ==~ args.to)) 18 | return 19 | 20 | println "running because of a commit to ${branch}" 21 | body() 22 | 23 | } 24 | -------------------------------------------------------------------------------- /libraries/git/steps/on_merge.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2018 Booz Allen Hamilton. All Rights Reserved. 3 | This software package is licensed under the Booz Allen Public License. The license can be found in the License file or at http://boozallen.github.io/licenses/bapl 4 | */ 5 | 6 | package libraries.git.steps 7 | 8 | void call(Map args = [:], body){ 9 | 10 | // do nothing if not merge 11 | if (!env.GIT_BUILD_CAUSE.equals("merge")) 12 | return 13 | 14 | env.FEATURE_SHA = get_feature_branch_sha() 15 | 16 | def source_branch = get_merged_from() 17 | def target_branch = env.BRANCH_NAME 18 | 19 | // do nothing if source branch doesn't match 20 | if (args.from) 21 | if (!source_branch.collect{ it ==~ args.from}.contains(true)) 22 | return 23 | 24 | // do nothing if target branch doesnt match 25 | if (args.to) 26 | if (!(target_branch ==~ args.to)) 27 | return 28 | 29 | def mergedFrom = source_branch.join(", ") 30 | // grammar essentially, remove oxford comma to follow git syntax 31 | if(mergedFrom.contains(", ")) { 32 | def oxford = mergedFrom.lastIndexOf(", ") 33 | mergedFrom = mergedFrom.substring(0, oxford) + " and" + mergedFrom.substring(oxford + 1) 34 | } 35 | 36 | println "running because of a merge from ${mergedFrom} to ${target_branch}" 37 | body() 38 | } 39 | 40 | String get_merged_from(){ 41 | node{ 42 | unstash "workspace" 43 | // update remote for git name-rev to properly work 44 | def remote = env.GIT_URL 45 | def cred_id = env.GIT_CREDENTIAL_ID 46 | withCredentials([usernamePassword(credentialsId: cred_id, passwordVariable: 'PASS', usernameVariable: 'USER')]){ 47 | remote = remote.replaceFirst("://", "://${USER}:${PASS}@") 48 | sh "git remote rm origin" 49 | sh "git remote add origin ${remote}" 50 | sh "git fetch --all > /dev/null 2>&1" 51 | } 52 | // list all shas, but trim the first two shas 53 | // the first sha is the current commit 54 | // the second sha is the current commit's parent 55 | def sourceShas = sh( 56 | script: "git rev-list HEAD --parents -1", 57 | returnStdout: true 58 | ).trim().split(" ")[2..-1] 59 | def branchNames = [] 60 | // loop through all shas and attempt to turn them into branch names 61 | for(sha in sourceShas) { 62 | def branch = sh( 63 | script: "git name-rev --name-only " + sha, 64 | returnStdout: true 65 | ).replaceFirst("remotes/origin/", "").trim() 66 | // trim the ~ off branch names which means commits back 67 | // e.g. master~4 means 4 commits ago on master 68 | if(branch.contains("~")) 69 | branch = branch.substring(0, branch.lastIndexOf("~")) 70 | if(!branch.contains("^")) 71 | branchNames.add(branch) 72 | } 73 | return branchNames 74 | } 75 | } 76 | 77 | String get_feature_branch_sha(){ 78 | node{ 79 | unstash "workspace" 80 | sh( 81 | script: "git rev-parse \$(git --no-pager log -n1 | grep Merge: | awk '{print \$3}')", 82 | returnStdout: true 83 | ).trim() 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /libraries/git/steps/on_pull_request.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2018 Booz Allen Hamilton. All Rights Reserved. 3 | This software package is licensed under the Booz Allen Public License. The license can be found in the License file or at http://boozallen.github.io/licenses/bapl 4 | */ 5 | 6 | package libraries.git.steps 7 | 8 | void call(Map args = [:], body){ 9 | 10 | // do nothing if not pr 11 | if (!(env.GIT_BUILD_CAUSE in ["pr"])) 12 | return 13 | 14 | def source_branch = git_distributions.fetch().get_source_branch() 15 | def target_branch = env.CHANGE_TARGET 16 | 17 | // do nothing in source branch doesn't match 18 | if (args.from) 19 | if (!(source_branch ==~ (~args.from) ))// convert string to regex 20 | return 21 | 22 | // do nothing if target branch doesnt match 23 | if (args.to) 24 | if (!(target_branch ==~ (~args.to) ))// convert string to regex 25 | return 26 | 27 | println "running because of a PR from ${source_branch} to ${target_branch}" 28 | body() 29 | 30 | } 31 | -------------------------------------------------------------------------------- /libraries/git/steps/withGit.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2018 Booz Allen Hamilton. All Rights Reserved. 3 | This software package is licensed under the Booz Allen Public License. The license can be found in the License file or at http://boozallen.github.io/licenses/bapl 4 | */ 5 | 6 | package libraries.git.steps 7 | 8 | void call(Map args, Closure body){ 9 | 10 | // check required parameters 11 | if (!args.url || !args.cred) 12 | error """ 13 | withGit syntax error. 14 | Input Parameters: 15 | url: https git url to repository (required) 16 | cred: jenkins credential ID for github. (required) 17 | branch: branch in the repository to checkout. defaults to master. (optional) 18 | """ 19 | 20 | withCredentials([usernamePassword(credentialsId: args.cred, passwordVariable: 'PASS', usernameVariable: 'USER')]) { 21 | repo = args.url.split("/").last() - ".git" 22 | withEnv(["git_url_with_creds=${args.url.replaceFirst("://","://${USER}:${PASS}@")}"]) { 23 | node { 24 | sh "rm -rf ${repo}" 25 | sh "set +x && git clone ${env.git_url_with_creds}" 26 | dir(repo){ 27 | sh "git checkout ${args.branch ?: "master"}" 28 | push = "push" 29 | body.resolveStrategy = Closure.DELEGATE_FIRST 30 | body.delegate = this 31 | body.run() 32 | } 33 | } 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /libraries/git/test/GitSpec.groovy: -------------------------------------------------------------------------------- 1 | package libraries.git 2 | 3 | public class GitSpec extends JTEPipelineSpecification { 4 | 5 | def Git = null 6 | 7 | public static class DummyException extends RuntimeException { 8 | public DummyException(String _message) { super( _message ); } 9 | } 10 | 11 | def setup() { 12 | Git = loadPipelineScriptForStep("git", "git") 13 | Git.getBinding().setVariable("env", [git_url_with_creds: "git_url_with_creds"]) 14 | } 15 | 16 | // call(Map args) 17 | def "If inputs are invalid, throw error" () { 18 | when: 19 | try{Git([foo: "bar"])}catch(any){} 20 | then: 21 | 1 * getPipelineMock("error")({ it =~ /Unknown git actions/ }) >> { throw new DummyException("invalid action")} 22 | } 23 | 24 | def "If no valid Action is given, throw an error" () { 25 | when: 26 | try{Git([:])}catch(any){} 27 | then: 28 | 1 * getPipelineMock("error")({ it =~ /You must use an action\:/}) >> { throw new DummyException("no action given")} 29 | } 30 | 31 | def "Each Action in the args map is called as expected" () { 32 | setup: 33 | 34 | when: 35 | Git(actions) 36 | then: 37 | x * getPipelineMock("sh")({it =~ /^git add files$/}) 38 | y * getPipelineMock("sh")({if (it instanceof Map) it.script =~ /^git commit -m \"${m}\"$/; else false}) 39 | z * getPipelineMock("sh")({it =~ /^git push git_url_with_creds/}) //note: two spaces between push and git_url 40 | where: 41 | actions | x | y | z | m 42 | [add: "files"] | 1 | 0 | 0 | null 43 | [commit: null] | 0 | 1 | 0 | null 44 | [push: null] | 0 | 0 | 1 | null 45 | [add: "files", commit: null] | 1 | 1 | 0 | null 46 | [add: "files", commit: 'commit msg'] | 1 | 1 | 0 | 'commit msg' 47 | [add: "files", push: null] | 1 | 0 | 1 | null 48 | [commit: null, push: null] | 0 | 1 | 1 | null 49 | [commit: 'commit msg', push: null] | 0 | 1 | 1 | 'commit msg' 50 | [add: "files", commit: null, push: null] | 1 | 1 | 1 | null 51 | } 52 | 53 | def "Each Action's closure's resolveStrategy is set to DELEGATE_FIRST" () { 54 | 55 | } 56 | 57 | def "Each Action's closure's delegate is set to the current script" () { 58 | 59 | } 60 | 61 | // call(String action) 62 | def "The call method with a Map args parameter is called with args action: null" () { 63 | 64 | } 65 | 66 | // "add" action 67 | def "If adds argument is a String, use git add to add the file" () { 68 | 69 | } 70 | 71 | def "If add's argument is a GStringImpl, use git add to add the files" () { 72 | 73 | } 74 | 75 | def "If add's argument is anything else, assume it's an array or list of files, join the elements, and call git add" () { 76 | 77 | } 78 | 79 | // "commit" action 80 | def "The git config is modified appropriately before a commit is made" () { 81 | 82 | } 83 | 84 | def "Git commit is used to create a commit" () { 85 | 86 | } 87 | 88 | def "The commit is created using the given message" () { 89 | 90 | } 91 | 92 | def "If the commit message is null, use a placeholder" () { 93 | 94 | } 95 | 96 | //"push" action 97 | def "Git push is called" () { 98 | 99 | } 100 | 101 | def "The given flags are used when git push is called" () { 102 | 103 | } 104 | 105 | def "The git push command uses the URL supplied by withGit" () { 106 | 107 | } 108 | 109 | } 110 | -------------------------------------------------------------------------------- /libraries/git/test/GitlabStatusSpec.groovy: -------------------------------------------------------------------------------- 1 | package libraries.git 2 | 3 | public class GitlabStatusSpec extends JTEPipelineSpecification { 4 | 5 | def GitlabStatus = null 6 | def config = [distribution:"gitlab"] 7 | def env = [git_url_with_creds: "git_url_with_creds"] 8 | 9 | def setup() { 10 | GitlabStatus = loadPipelineScriptForStep("git", "gitlab_status") 11 | GitlabStatus.getBinding().setProperty("config", config) 12 | GitlabStatus.getBinding().setVariable("env", env) 13 | } 14 | 15 | def "If inputs are null, throw error" () { 16 | when: 17 | GitlabStatus() 18 | then: 19 | thrown(RuntimeException) 20 | 1 * getPipelineMock("error")({ it =~ /gitlab connection must be a valid string/ }) >> { throw new RuntimeException("invalid action")} 21 | } 22 | 23 | def "If config.gitlab values are set, call properties and updateGitlabCommitStatus" () { 24 | setup: 25 | config.gitlab = [connection: 'https://gitlab.com/test', 26 | job_name : "j1", 27 | job_status : "pending"] 28 | when: 29 | GitlabStatus() 30 | then: 31 | 1 * getPipelineMock("properties")([config.gitlab.connection]) >> { return } 32 | 1 * explicitlyMockPipelineStep('gitLabConnection').call(config.gitlab.connection) >> { x -> return x[0] } 33 | 1 * explicitlyMockPipelineStep("updateGitlabCommitStatus").call([name: config.gitlab.job_name, state: config.gitlab.job_status]) >> { return } 34 | } 35 | 36 | def "Argument should be used even with config.gitlab values are set, call properties and updateGitlabCommitStatus" () { 37 | setup: 38 | String job_name = 'j0' 39 | String job_status = "running" 40 | String connection = "https://gitlab.com/arg" 41 | config.gitlab = [connection: 'https://gitlab.com/test', 42 | job_name : "j1", 43 | job_status : "pending"] 44 | when: 45 | GitlabStatus(connection,job_name,job_status) 46 | then: 47 | 1 * getPipelineMock("properties")([connection]) >> { return } 48 | 0 * explicitlyMockPipelineStep('gitLabConnection').call(config.gitlab.connection) 49 | 1 * explicitlyMockPipelineStep('gitLabConnection').call(connection) >> { x -> return x[0] } 50 | 1 * explicitlyMockPipelineStep("updateGitlabCommitStatus").call([name: job_name, state: job_status]) >> { return } 51 | } 52 | 53 | 54 | } 55 | -------------------------------------------------------------------------------- /libraries/google_lighthouse/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: Performs accessibility compliance, performance, search engine optimization, and best practice validations on a frontend application 3 | --- 4 | 5 | # Google Lighthouse 6 | 7 | This library integrates [Google Lighthouse](https://developers.google.com/web/tools/lighthouse) to scan a frontend application for performance, 8 | accessibility compliance, search engine optimization, and best practice violations. 9 | 10 | The great part about this library is that developers can also use Google Lighthouse when developing locally in Chrome and these practices can be enforced via the pipeline. 11 | 12 | ## Steps 13 | 14 | --- 15 | 16 | | Step | Description | 17 | | ----------- | ----------- | 18 | | ``accessibility_compliance_scan()`` | performs a lighthouse analysis against the configured address | 19 | 20 | ## Configuration 21 | 22 | --- 23 | 24 | | Field | Type | Description | Default Value | 25 | | ----------- | ----------- | ----------- | ----------- | 26 | | `url` | String | The address to scan | | 27 | | `thresholds.performance.fail` | Double | Performance scores less than or equal to this will fail the build | `49.0` | 28 | | `thresholds.performance.warn` | Double | Performance above the failure threshold but less than this will mark the build unstable | `89.0` | 29 | | `thresholds.accessibility.fail` | Double | Accessibility scores less than or equal to this will fail the build | `49.0` | 30 | | `thresholds.accessibility.warn` | Double | Accessibility above the failure threshold but less than this will mark the build unstable | `89.0` | 31 | | `thresholds.best_practices.fail` | Double | Best Practice scores less than or equal to this will fail the build | `49.0` | 32 | | `thresholds.best_practices.warn` | Double | Best Practice above the failure threshold but less than this will mark the build unstable | `89.0` | 33 | | `thresholds.search_engine_optimization.fail` | Double | Search Engine Optimization scores less than or equal to this will fail the build | `49.0` | 34 | | `thresholds.search_engine_optimization.warn` | Double | Search Engine Optimization above the failure threshold but less than this will mark the build unstable | `89.0` | 35 | 36 | ```groovy 37 | libraries{ 38 | google_lighthouse{ 39 | url = "https://google.com" 40 | thresholds{ 41 | performance{ 42 | fail = 75 43 | } 44 | } 45 | } 46 | } 47 | ``` 48 | 49 | ## Results 50 | 51 | --- 52 | 53 | An example HTML report generated by this library is available as a PDF [here](../../assets/attachments/google_lighthouse/google_lighthouse.pdf). 54 | 55 | ## Dependencies 56 | 57 | --- 58 | 59 | * network connectivity from the Jenkins build agent running the scan to the provided address 60 | -------------------------------------------------------------------------------- /libraries/google_lighthouse/library_config.groovy: -------------------------------------------------------------------------------- 1 | fields{ 2 | optional{ 3 | thresholds{ 4 | performance{ 5 | fail = Double 6 | warn = Double 7 | } 8 | accessibility { 9 | fail = Double 10 | warn = Double 11 | } 12 | best_practices{ 13 | fail = Double 14 | warn = Double 15 | } 16 | search_engine_optimization{ 17 | fail = Double 18 | warn = Double 19 | } 20 | } 21 | } 22 | required{ 23 | url = String 24 | } 25 | } -------------------------------------------------------------------------------- /libraries/google_lighthouse/steps/accessibility_compliance_scan.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2018-present Booz Allen Hamilton. All Rights Reserved. 3 | This software package is licensed under the Booz Allen Public License. The license can be found in the License file or at http://boozallen.github.io/licenses/bapl 4 | */ 5 | 6 | package libraries.google_lighthouse.steps 7 | 8 | void call(){ 9 | stage("Accessibility Compliance: Google Lighthouse"){ 10 | inside_sdp_image "google-lighthouse", { 11 | String url = config.url 12 | String resultsDir = "google-lighthouse" 13 | sh """ 14 | mkdir -p ${resultsDir}; 15 | lighthouse ${url} \ 16 | --chrome-flags='--headless --no-sandbox' \ 17 | --output json --output html \ 18 | --output-path=${resultsDir}/lighthouse 19 | """ 20 | archiveArtifacts allowEmptyArchive: true, artifacts: "${resultsDir}/" 21 | this.validateResults(resultsDir) 22 | } 23 | } 24 | } 25 | 26 | /* 27 | validate results via generated JSON file 28 | 29 | libraries{ 30 | google_lighthouse{ 31 | thresholds{ 32 | performance{ 33 | fail = 49 34 | warn = 89 35 | } 36 | accessibility { 37 | fail = 49 38 | warn = 89 39 | } 40 | best_practices{ 41 | fail = 49 42 | warn = 89 43 | } 44 | search_engine_optimization{ 45 | fail = 49 46 | warn = 89 47 | } 48 | } 49 | } 50 | } 51 | */ 52 | void validateResults(String resultsDir){ 53 | if(!fileExists("${resultsDir}/lighthouse.report.json")){ 54 | return 55 | } 56 | 57 | def results = readJSON file: "${resultsDir}/lighthouse.report.json" 58 | 59 | boolean shouldFail = false 60 | boolean shouldWarn = false 61 | ArrayList output = [ """ 62 | ------------------------ 63 | Google Lighthouse Scores 64 | ------------------------""".stripIndent()] 65 | 66 | [ 67 | [ 68 | configKey: "performance", 69 | jsonKey: "performance", 70 | title: "Performance" 71 | ], 72 | [ 73 | configKey: "accessibility", 74 | jsonKey: "accessibility", 75 | title: "Accessibility" 76 | ], 77 | [ 78 | configKey: "best_practices", 79 | jsonKey: "best-practices", 80 | title: "Best Practices" 81 | ], 82 | [ 83 | configKey: "search_engine_optimization", 84 | jsonKey: "seo", 85 | title: "Search Engine Optimization" 86 | ] 87 | ].each{ category -> 88 | def failThreshold = config.thresholds?."${category.configKey}"?.fail 89 | if(!(failThreshold instanceof Number)){ 90 | failThreshold = 49 91 | } 92 | 93 | def warnThreshold = config.thresholds?."${category.configKey}"?.warn 94 | if(!(warnThreshold instanceof Number)){ 95 | warnThreshold = 89 96 | } 97 | 98 | def score = results.categories[category.jsonKey]?.score * 100 99 | 100 | if( score <= failThreshold ){ 101 | shouldFail = true 102 | output << "${category.title}: ${score} <-- failing" 103 | } else if ( score <= warnThreshold ){ 104 | shouldWarn = true 105 | output << "${category.title}: ${score} <-- warning" 106 | } else { 107 | output << "${category.title}: ${score}" 108 | } 109 | } 110 | 111 | println output.join("\n") 112 | String oopsMessage = "Google Lighthouse results did not meet thresholds" 113 | if(shouldFail) error(oopsMessage) 114 | if(shouldWarn) unstable(oopsMessage) 115 | 116 | } 117 | -------------------------------------------------------------------------------- /libraries/grype/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: Uses the Grype CLI to scan container images for vulnerabilities. 3 | --- 4 | 5 | # Grype 6 | 7 | Uses the [Grype CLI](https://github.com/anchore/grype) to scan container images for vulnerabilities. 8 | 9 | ## Steps 10 | 11 | | Step | Description | 12 | |------------------------|------------------------------------------------------------| 13 | | container_image_scan() | Performs the Grype scan against your scaffold build image. | 14 | 15 | ## Configuration 16 | 17 | | Library Configuration | Description | Type | Default Value | Options | 18 | |-----------------------|----------------------------------------------------------|---------|---------------|-----------------------------------------------------------| 19 | | `grype_container` | The container image to execute the scan within | String | grype:0.38.0 | | 20 | | `report_format` | The output format of the generated report | String | json | `json`, `table`, `cyclonedx`, `template` | 21 | | `fail_on_severity` | The severity level threshold that will fail the pipeline | String | high | `none`, `negligible`, `low`, `medium`, `high`, `critical` | 22 | | `grype_config` | A custom path to a grype configuration file | String | `null` | | 23 | | `scan_sbom` | Boolean to turn on SBOM scanning | Boolean | false | true, false | 24 | 25 | ``` groovy title='pipeline_config.groovy' 26 | libraries { 27 | grype { 28 | grype_container = "grype:0.38.0" 29 | report_format = "json" 30 | fail_on_severity = "high" 31 | grype_config = "Path/to/Grype.yaml" 32 | scan_sbom = false 33 | } 34 | } 35 | ``` 36 | 37 | ## Grype Configuration File 38 | 39 | If `grype_config` isn't provided, the default locations for an application are `.grype.yaml`, `.grype/config.yaml`. 40 | 41 | 42 | Read [the grype docs](https://github.com/anchore/grype#configuration) to learn more about the Grype configuration file 43 | 44 | ## Dependencies 45 | 46 | --- 47 | 48 | * This library requires that the `docker` library also be loaded and `build()` be invoked before `container_image_scan()` 49 | * If the default `grype_container` is replaced, it must be able to run docker containers (packages: docker-ce, docker-ce-cli and containerd.io). 50 | -------------------------------------------------------------------------------- /libraries/grype/library_config.groovy: -------------------------------------------------------------------------------- 1 | fields{ 2 | optional{ 3 | grype_container = String 4 | report_format = ["json", "table", "cyclonedx", "template"] 5 | fail_on_severity = ["none", "negligible", "low", "medium", "high", "critical"] 6 | grype_config = String 7 | scan_sbom = Boolean 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /libraries/grype/resources/transform-grype-scan-results.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | RAW_RESULTS=$1 4 | GRYPE_CONFIG=$2 5 | 6 | # show whitelist count 7 | WHITELIST_COUNT=$(cat $GRYPE_CONFIG | python3 -m yq -r '.ignore | length') 8 | echo "${WHITELIST_COUNT} CVE(s) were whitelisted." 9 | printf "The whitelist can be found in $GRYPE_CONFIG.\n\n" 10 | 11 | # transform the results into an organized array 12 | cat "$RAW_RESULTS" \ 13 | | jq -r ' 14 | def severity_to_number: 15 | { 16 | "Critical": 0, 17 | "High": 1, 18 | "Medium": 2, 19 | "Low": 3, 20 | "None": 4, 21 | }[.]; 22 | 23 | .matches 24 | | map(. | { 25 | cve: .vulnerability.id, 26 | severity: .vulnerability.severity, 27 | package: .artifact.name, 28 | version: .artifact.version, 29 | type: .artifact.type, 30 | location: .artifact.locations[].path, 31 | url: .vulnerability.dataSource 32 | }) 33 | | sort_by([(.severity | severity_to_number), .package])' \ 34 | > transformed-results.json 35 | 36 | # get the CVE count 37 | CVE_COUNT=$(cat transformed-results.json | jq -r 'length') 38 | 39 | if [ "$CVE_COUNT" -eq "0" ] 40 | then 41 | echo "No CVEs detected! :)" 42 | else 43 | # transform the results into table columns 44 | cat transformed-results.json \ 45 | | jq -r ' 46 | map(join("|")) 47 | | .[]' \ 48 | > results.txt 49 | 50 | # display results as a table 51 | echo -e "Vulnerability|Severity|Package|Version|Type|Location|Link\n$(cat results.txt)" \ 52 | | column -t -s "|" 53 | fi 54 | -------------------------------------------------------------------------------- /libraries/maven/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: This library allows you to perform Maven commands in a defined build agent container 3 | --- 4 | 5 | # Maven 6 | 7 | This library allows you to perform Maven commands in a defined build agent container. 8 | 9 | ## Steps 10 | 11 | | Step | Description | 12 | | ----------- | ----------- | 13 | | `[dynamic]()` | Step name can be any non-reserved word. This library uses [dynamic step aliasing](https://jenkinsci.github.io/templating-engine-plugin/2.4/concepts/library-development/step-aliasing/#dynamic-step-aliases) to run the Maven phases, goals, and options defined in the step configuration. | 14 | 15 | ## Configuration 16 | 17 | ``` groovy title='pipeline_config.groovy' 18 | libraries { 19 | maven { 20 | myMavenStep { 21 | stageName = 'Initial Maven Lifecycle' 22 | buildContainer = 'mvn:3.8.5-openjdk-11' 23 | phases = ['clean', 'validate'] 24 | goals = ['compiler:testCompile'] 25 | options = ['-q'] 26 | secrets { 27 | myToken { 28 | type = 'text' 29 | name = 'token-name' 30 | id = 'my-token-id' 31 | } 32 | myCredentials { 33 | type = 'usernamePassword' 34 | usernameVar = 'USER' 35 | passwordVar = 'PASS' 36 | id = 'my-credentials-id' 37 | } 38 | } 39 | } 40 | anotherMavenStep { 41 | stageName = 'Maven Build' 42 | buildContainer = 'mvn' 43 | phases = ['build'] 44 | artifacts = ['target/*.jar'] 45 | } 46 | } 47 | } 48 | ``` 49 | 50 | ## Dependencies 51 | 52 | * The `sdp` library 53 | * Access to an appropriate Maven build agent container via the repository defined in your `sdp` library configuration 54 | 55 | ## Migrating to 4.0 56 | 57 | SDP `4.0` reworked this library to use dynamic step aliasing. 58 | 59 | The Maven tool configuration within Jenkins is no longer required to use this library. 60 | 61 | To recreate the previous `maven.run()` functionality of prior versions, the below minimal pipeline configuration and template can be used: 62 | 63 | ### Sample Pipeline Configuration 64 | 65 | === "Post-4.0" 66 | ``` groovy title="pipeline_config.groovy" 67 | libraries { 68 | maven { 69 | build { 70 | stageName = "Maven Build" 71 | buildContainer = 'mvn' 72 | phases = ['clean', 'install'] 73 | options = ['-P integration-test'] 74 | } 75 | } 76 | } 77 | ``` 78 | === "Pre-4.0" 79 | ``` groovy title="pipeline_config.groovy" 80 | libraries { 81 | maven { 82 | mavenId = "maven" 83 | } 84 | } 85 | ``` 86 | 87 | ### Sample Pipeline Template 88 | 89 | === "Post-4.0" 90 | ``` groovy title="Jenkinsfile" 91 | build() 92 | ``` 93 | === "Pre-4.0" 94 | ``` groovy title="Jenkinsfile" 95 | maven.run(["clean", "install"], profiles: ["integration-test"]) 96 | ``` 97 | -------------------------------------------------------------------------------- /libraries/maven/test/MavenInvokeSpec.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2022 Booz Allen Hamilton. All Rights Reserved. 3 | This software package is licensed under the Booz Allen Public License. 4 | The license can be found in the License file or at http://boozallen.github.io/licenses/bapl 5 | */ 6 | 7 | package libraries.maven 8 | 9 | public class MavenInvokeSpec extends JTEPipelineSpecification { 10 | def MavenInvoke = null 11 | 12 | LinkedHashMap minimalUnitTestingConfig = [ 13 | unit_test: [ 14 | stageName: 'Maven Unit Tests', 15 | buildContainer: 'mvn', 16 | phases: ['test'] 17 | ] 18 | ] 19 | 20 | static class DummyException extends RuntimeException { 21 | DummyException(String _message) { super( _message ) } 22 | } 23 | 24 | def setup() { 25 | LinkedHashMap config = [:] 26 | LinkedHashMap env = [:] 27 | LinkedHashMap stepContext = [ name: 'unit_test' ] 28 | 29 | MavenInvoke = loadPipelineScriptForStep('maven', 'maven_invoke') 30 | 31 | explicitlyMockPipelineStep('inside_sdp_image') 32 | 33 | MavenInvoke.getBinding().setVariable('config', config) 34 | MavenInvoke.getBinding().setVariable('env', env) 35 | MavenInvoke.getBinding().setVariable('stepContext', stepContext) 36 | } 37 | 38 | def 'Completes a mvn test successfully' () { 39 | setup: 40 | getPipelineMock('sh')('mvn archetype:generate -DgroupId=com.mycompany.app -DartifactId=my-app -DarchetypeArtifactId=maven-archetype-quickstart -DarchetypeVersion=1.4 -DinteractiveMode=false && cd my-app') 41 | MavenInvoke.getBinding().setVariable('config', minimalUnitTestingConfig) 42 | when: 43 | MavenInvoke() 44 | then: 45 | 1 * getPipelineMock('sh').call('mvn test ') 46 | } 47 | 48 | def 'Application environment settings take precendence over library config' () { 49 | setup: 50 | MavenInvoke.getBinding().setVariable('config', [ 51 | unit_test: [ 52 | stageName: 'LibConfig Maven Stage', 53 | buildContainer: 'mvn', 54 | phases: ['clean', 'build'] 55 | ] 56 | ]) 57 | when: 58 | MavenInvoke([ 59 | maven: [ 60 | unit_test: [ 61 | stageName: 'AppEnv Defined Maven Stage', 62 | options: ['-v'], 63 | phases: [] 64 | ] 65 | ] 66 | ]) 67 | then: 68 | MavenInvoke.getBinding().variables.env.buildContainer == 'mvn' 69 | MavenInvoke.getBinding().variables.env.options == ['-v'] 70 | MavenInvoke.getBinding().variables.env.stageName == 'AppEnv Defined Maven Stage' 71 | MavenInvoke.getBinding().variables.env.phases == [] 72 | } 73 | 74 | def 'Artifacts get archived as expected' () { 75 | setup: 76 | getPipelineMock('sh')('mvn archetype:generate -DgroupId=com.mycompany.app -DartifactId=my-app -DarchetypeArtifactId=maven-archetype-quickstart -DarchetypeVersion=1.4 -DinteractiveMode=false && cd my-app') 77 | MavenInvoke.getBinding().setVariable('stepContext', [name: 'build']) 78 | MavenInvoke.getBinding().setVariable('config', [ 79 | build: [ 80 | stageName: 'Maven Build', 81 | buildContainer: 'mvn', 82 | phases: ['clean', 'install'], 83 | artifacts: ['target/*.jar'] 84 | ] 85 | ]) 86 | when: 87 | MavenInvoke() 88 | then: 89 | 1 * getPipelineMock('archiveArtifacts.call')(_ as Map) 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /libraries/owasp_dep_check/steps/application_dependency_scan.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2018 Booz Allen Hamilton. All Rights Reserved. 3 | This software package is licensed under the Booz Allen Public License. The license can be found in the License file or at http://boozallen.github.io/licenses/bapl 4 | */ 5 | 6 | package libraries.owasp_dep_check.steps 7 | 8 | void call() { 9 | stage('Application Dependency Scan: OWASP Dep Checker') { 10 | String resultsDir = "owasp-dependency-check" 11 | String args = "--out ${resultsDir} --enableExperimental --format ALL" 12 | 13 | ArrayList scan = config?.scan ?: [ '.' ] 14 | scan.each{ s -> args += " -s ${s}" } 15 | 16 | ArrayList exclude = config?.exclude ?: [] 17 | exclude.each{ e -> args += " --exclude ${e}" } 18 | 19 | // vulnerabilities greater than this will fail the build 20 | // max value 10 21 | if (config?.containsKey("cvss_threshold")) { 22 | Double threshold = config?.cvss_threshold 23 | if (threshold <= 10.0) { 24 | args += " --failOnCVSS ${threshold} --junitFailOnCVSS ${threshold}" 25 | } 26 | } 27 | 28 | String image_tag = config?.image_tag ?: "7.3.0-8.6-2" 29 | inside_sdp_image "owasp-dep-check:$image_tag", { 30 | unstash "workspace" 31 | 32 | // suppress whitelisted vulnerabilities 33 | Boolean allowSuppressionFile = config?.allow_suppression_file ?: true 34 | if (allowSuppressionFile) { 35 | String suppressionFile = config?.suppression_file ?: "dependency-check-suppression.xml" 36 | Boolean suppressionFileExists = fileExists suppressionFile 37 | 38 | if (suppressionFileExists) { 39 | args += " --suppression ${suppressionFile}" 40 | } 41 | else { 42 | echo "\"${suppressionFile}\" does not exist. Skipping suppression." 43 | } 44 | } 45 | 46 | Boolean skipNodeAudit = config?.skip_node_audit ?: false 47 | if (skipNodeAudit) { 48 | args += " --disableNodeAudit" 49 | } 50 | 51 | // perform the scan 52 | try { 53 | sh "mkdir -p ${resultsDir} && mkdir -p owasp-data && /usr/share/dependency-check/bin/dependency-check.sh ${args} -d owasp-data" 54 | } catch (ex) { 55 | error "Error occured when running OWASP Dependency Check: ${ex.getMessage()}" 56 | } finally { 57 | archiveArtifacts allowEmptyArchive: true, artifacts: "${resultsDir}/" 58 | junit allowEmptyResults: true, healthScaleFactor: 0.0, testResults: "${resultsDir}/dependency-check-junit.xml" 59 | } 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /libraries/owasp_dep_check/test/ApplicationDependencySpec.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2018 Booz Allen Hamilton. All Rights Reserved. 3 | This software package is licensed under the Booz Allen Public License. The license can be found in the License file or at http://boozallen.github.io/licenses/bapl 4 | */ 5 | 6 | package libraries.owasp_dep_check 7 | 8 | public class ApplicationDependencyScanSpec extends JTEPipelineSpecification { 9 | def ApplicationDependencyScan = null 10 | 11 | String fileDoesNotExistWarning = "\"dependency-check-suppression.xml\" does not exist. Skipping suppression." 12 | 13 | String commandBeginning = "mkdir -p owasp-dependency-check && mkdir -p owasp-data && /usr/share/dependency-check/bin/dependency-check.sh" 14 | String defaultArgs = "--out owasp-dependency-check --enableExperimental --format ALL -s ." 15 | String expectedAdditionalArgs = "" 16 | String commandEnd = "-d owasp-data" 17 | 18 | def setup() { 19 | ApplicationDependencyScan = loadPipelineScriptForStep("owasp_dep_check", "application_dependency_scan") 20 | 21 | ApplicationDependencyScan.getBinding().setVariable("config", [:]) 22 | 23 | explicitlyMockPipelineStep("inside_sdp_image") 24 | } 25 | 26 | def "Does not print warning message if the suppression file is found" () { 27 | setup: 28 | getPipelineMock("fileExists")(_) >> { return true } 29 | when: 30 | ApplicationDependencyScan() 31 | then: 32 | 0 * getPipelineMock("echo")(fileDoesNotExistWarning) 33 | } 34 | 35 | def "Prints warning message if the suppression file is not found" () { 36 | setup: 37 | getPipelineMock("fileExists")(_) >> { return false } 38 | when: 39 | ApplicationDependencyScan() 40 | then: 41 | 1 * getPipelineMock("echo")(fileDoesNotExistWarning) 42 | } 43 | 44 | def "Uses --suppression flag when using suppression file" () { 45 | setup: 46 | getPipelineMock("fileExists")(_) >> { return true } 47 | expectedAdditionalArgs = " --suppression dependency-check-suppression.xml" 48 | when: 49 | ApplicationDependencyScan() 50 | then: 51 | 1 * getPipelineMock("sh")("${commandBeginning} ${defaultArgs}${expectedAdditionalArgs} ${commandEnd}") 52 | } 53 | 54 | def "Does not use --supppression flag when not using suppression file" () { 55 | setup: 56 | getPipelineMock("fileExists")(_) >> { return false } 57 | expectedAdditionalArgs = "" 58 | when: 59 | ApplicationDependencyScan() 60 | then: 61 | 1 * getPipelineMock("sh")("${commandBeginning} ${defaultArgs}${expectedAdditionalArgs} ${commandEnd}") 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /libraries/owasp_zap/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: Leverages OWASP ZAP to perform penetration testing 3 | --- 4 | 5 | # OWASP ZAP 6 | 7 | [OWASP Zed Attack Proxy (ZAP)](https://www.owasp.org/index.php/OWASP_Zed_Attack_Proxy_Project) is a tool that can help you automatically find security vulnerabilities in your web applications while you are developing and testing your applications. 8 | It's also a great tool for experienced penetration-testers to use for manual security testing. 9 | 10 | ## Steps 11 | 12 | --- 13 | 14 | | Step | Description | 15 | | ----------- | ----------- | 16 | | `penetration_test()` | Uses the OWASP ZAP CLI to perform penetration testing against the configured web application | 17 | 18 | ## Configuration 19 | 20 | --- 21 | 22 | OWASP ZAP Library Configuration Options 23 | 24 | | Field | Description | Default Value | Options | 25 | |-------|-------------|---------------|---------| 26 | | `target` | The target web application address to test | | | 27 | | `vulnerability_threshold` | Minimum alert level to include in report | `High` | one of `Ignore`, `Low`, `Medium`, `High`, or `Informational` | 28 | 29 | `target` is set to `env.FRONTEND_URL` if available. If not then it uses the provided `target`. If neither is provided, an error is thrown. 30 | 31 | ### Example Configuration Snippet 32 | 33 | ```groovy 34 | libraries{ 35 | owasp_zap{ 36 | target = "https://example.com" 37 | vulnerability_threshold = "Low" 38 | } 39 | } 40 | ``` 41 | 42 | ## Results 43 | 44 | --- 45 | 46 | ![OWASP ZAP example](../../assets/images/owasp_zap/report.png) 47 | -------------------------------------------------------------------------------- /libraries/owasp_zap/steps/penetration_test.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2018 Booz Allen Hamilton. All Rights Reserved. 3 | This software package is licensed under the Booz Allen Public License. The license can be found in the License file or at http://boozallen.github.io/licenses/bapl 4 | */ 5 | 6 | package libraries.owasp_zap.steps 7 | 8 | void call() { 9 | stage "Penetration Test", { 10 | 11 | def target = env.FRONTEND_URL ?: config.target ?: { 12 | error """ 13 | OWASP Zap target url undefined. Set: 14 | libraries{ 15 | owasp_zap{ 16 | target = 17 | } 18 | } 19 | """.stripIndent(6) 20 | }() 21 | 22 | def vuln_threshold 23 | if (config.vulnerability_threshold){ 24 | if ( !(config.vulnerability_threshold in ["Ignore", "Low", "Medium", "High", "Informational"])){ 25 | error "OWASP Zap: Vulnerability Threshold ${config.vulnerability_threshold} not Ignore, Low, Medium, High, or Informational" 26 | } 27 | vuln_threshold = config.vulnerability_threshold 28 | } else { 29 | vuln_threshold = "High" 30 | } 31 | 32 | inside_sdp_image "zap", { 33 | // start zap daemon 34 | sh """zap.sh -daemon \ 35 | -host 127.0.0.1 \ 36 | -port 8080 \ 37 | -config api.disablekey=true \ 38 | -config scanner.attackOnStart=true \ 39 | -config view.mode=attack \ 40 | -config connection.dnsTtlSuccessfulQueries=-1 \ 41 | -config api.addrs.addr.name=.* \ 42 | -config api.addrs.addr.regex=true & 43 | """ 44 | // validate daemon is running 45 | sh "zap-cli status -t 60" 46 | // prep-url, do scan, generate report 47 | sh """ zap-cli open-url ${target} && \ 48 | zap-cli spider ${target} && \ 49 | zap-cli active-scan -r ${target} && \ 50 | zap-cli report -o zap.html -f html 51 | """ 52 | archiveArtifacts allowEmptyArchive: true, artifacts: "zap.html" 53 | // fail if vuln_threshold met 54 | if(!vuln_threshold.equals("Ignore")){ 55 | def n_vulns = sh script: "zap-cli alerts -l ${vuln_threshold}", returnStatus: true 56 | if (n_vulns){ 57 | error "OWASP Zap found ${n_vulns} ${vuln_threshold} vulnerabilities while performing a scan" 58 | } 59 | } 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /libraries/protractor/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: Leverages Protractor, a front-end unit testing utility, to perform unit tests 3 | --- 4 | 5 | # Protractor 6 | 7 | Protractor is a test framework built for Angular and AngularJS applications that's used for end-to-end testing. 8 | The framework simulates user activity using the web application by running a developer's tests on a real browser. 9 | It adds a layer of tests to help ensure that newly added front-end code doesn't break already existing functionality or the build itself. 10 | 11 | ## Steps 12 | 13 | --- 14 | 15 | | Step | Description | 16 | | ----------- | ----------- | 17 | | `functional_test()` | leverages Protractor CLI to perform configured Protractor tests | 18 | 19 | ## Configuration 20 | 21 | --- 22 | 23 | Library Configuration Options 24 | 25 | | Field | Description | Default Value | 26 | | ----------- | ----------- | ----------- | 27 | | `url` | Address of the website that will be tested | | 28 | | `enforce` | Boolean value that determines if a build will fail if a Protractor test fails | | 29 | | `config_file` | Name of the file where the Protractor configurations are set | | 30 | 31 | ### Example Configuration Snippet 32 | 33 | ```groovy 34 | libraries{ 35 | protractor { 36 | url = "http://frontend-website.com" 37 | enforce = true 38 | config_file = "protractor.conf.js" 39 | } 40 | } 41 | ``` 42 | -------------------------------------------------------------------------------- /libraries/protractor/steps/functional_test.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2018 Booz Allen Hamilton. All Rights Reserved. 3 | This software package is licensed under the Booz Allen Public License. The license can be found in the License file or at http://boozallen.github.io/licenses/bapl 4 | */ 5 | 6 | package libraries.protractor.steps 7 | 8 | def call(){ 9 | stage "Functional Test", { 10 | 11 | // configurable params in this lib 12 | def lib_spec = """ 13 | libraries{ 14 | protractor{ 15 | url = # base url to test 16 | enforce = # fail the build? defaults to true 17 | config_file = # path to protractor config file 18 | } 19 | } 20 | """ 21 | 22 | // base url to pass to protractor 23 | def url = env.FRONTEND_URL ?: config.url ?: { 24 | error """ 25 | Protractor base url undefined. Set: 26 | ${lib_spec} 27 | """.stripIndent(6) 28 | }() 29 | 30 | // fail the build if tests fail? 31 | def enforce = true 32 | if (config.enforce){ 33 | if (!(config.enforce instanceof Boolean)){ 34 | error """ 35 | Protractor enforce field must be Boolean, found: ${config.enforce} 36 | ${lib_spec} 37 | """.stripIndent(8) 38 | } 39 | enforce = config.enforce 40 | } 41 | 42 | 43 | // path to protractor configuration file 44 | def conf = config.config_file ?: "" 45 | 46 | // do tests 47 | inside_sdp_image "protractor", { 48 | unstash "workspace" 49 | 50 | if (fileExists("package.json")) 51 | sh "npm install --only=dev" 52 | 53 | if (!fileExists(conf)) 54 | error "Protractor configuration file ${conf} not found." 55 | 56 | // runs XVFB to render browser in memory 57 | sh "Xvfb -ac :99 -screen 0 1280x1024x16 &" 58 | // starts a selenium server in the container 59 | sh "webdriver-manager start &" 60 | // run tests 61 | def tests_fail = sh script: "protractor ${conf} --baseUrl='${url}'", 62 | returnStatus: true 63 | 64 | if (tests_fail && enforce) error "Protractor tests failed" 65 | 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /libraries/pytest/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: Leverages PyTest, a Python unit testing library, to perform unit tests 3 | --- 4 | 5 | # PyTest 6 | 7 | This library will execute Python unit tests leveraging the [PyTest](https://docs.pytest.org/en/latest/) framework. 8 | 9 | ## Steps 10 | 11 | | Step | Description | 12 | | ----------- | ----------- | 13 | | `unit_test()` | executes unit tests via PyTest | 14 | 15 | ## Configuration 16 | 17 | --- 18 | 19 | Configuration Options 20 | 21 | | Field | Description | Required | Default Value | 22 | | ----------- | ----------- | ----------- | ----------- | 23 | | `enforce_success` | Set to false if failing tests shouldn't fail the build | No | `true` | 24 | | `requirements_file` | Relative path within the repository pointing to a Python requirements file | No | `requirements.txt` | 25 | 26 | ```groovy 27 | libraries{ 28 | pytest{ 29 | requirements_file = "path/to/my/requirements.txt" 30 | } 31 | } 32 | ``` 33 | 34 | ## Results 35 | 36 | --- 37 | 38 | View an example of the HTML output that's been saved as a PDF [here](../../assets/attachments/pytest/pytest.pdf). 39 | 40 | ## Dependencies 41 | 42 | --- 43 | -------------------------------------------------------------------------------- /libraries/pytest/library_config.groovy: -------------------------------------------------------------------------------- 1 | fields{ 2 | optional{ 3 | enforce_success = Boolean 4 | requirements_file = String 5 | } 6 | } -------------------------------------------------------------------------------- /libraries/pytest/steps/unit_test.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2018-present Booz Allen Hamilton. All Rights Reserved. 3 | This software package is licensed under the Booz Allen Public License. The license can be found in the License file or at http://boozallen.github.io/licenses/bapl 4 | */ 5 | 6 | package libraries.pytest.steps 7 | 8 | /* 9 | This step runs python unit tests using the pytest framework and generates a 10 | html report for archiving and junit xml for Jenkins consumption and display. 11 | 12 | for full configuration options, refer to docs/modules/pytest/pages/index.adoc 13 | 14 | more configuration options are welcome, please submit a pull request 15 | */ 16 | void call(){ 17 | stage("unit test: pytest"){ 18 | boolean enforceSuccess = config.containsKey("enforce_success") ? config.enforce_success : true 19 | String requirementsFile = config.requirements_file ?: "requirements.txt" 20 | inside_sdp_image "pytest", { 21 | unstash "workspace" 22 | String resultsDir = "pytest-results" 23 | try{ 24 | if(fileExists(requirementsFile)){ 25 | sh "pip install -r ${requirementsFile}" 26 | } else if (config.containsKey("requirements_file")){ 27 | unstable "PyTest: Configured requirements file '${requirementsFile}' does not exist" 28 | } 29 | sh "pytest --html=${resultsDir}/report.html --junitxml=${resultsDir}/junit.xml" 30 | }catch(any){ 31 | String message = "error running unit tests." 32 | enforceSuccess ? error(message) : unstable(message) 33 | }finally{ 34 | archiveArtifacts allowEmptyArchive: true, artifacts: "${resultsDir}/" 35 | junit allowEmptyResults: true, healthScaleFactor: 0.0, testResults: "${resultsDir}/junit.xml" 36 | } 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /libraries/sdp/steps/archive_pipeline_config.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2018 Booz Allen Hamilton. All Rights Reserved. 3 | This software package is licensed under the Booz Allen Public License. The license can be found in the License file or at http://boozallen.github.io/licenses/bapl 4 | */ 5 | 6 | package libraries.sdp.steps 7 | 8 | import org.boozallen.plugins.jte.init.governance.config.dsl.PipelineConfigurationObject 9 | import org.boozallen.plugins.jte.init.governance.config.dsl.PipelineConfigurationDsl 10 | 11 | @Init 12 | void call(){ 13 | 14 | PipelineConfigurationObject aggregated = new PipelineConfigurationObject(null) 15 | aggregated.config = pipelineConfig 16 | 17 | node{ 18 | writeFile text: (new PipelineConfigurationDsl(null)).serialize(aggregated), file: "pipeline_config.groovy" 19 | archiveArtifacts "pipeline_config.groovy" 20 | cleanWs() 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /libraries/sdp/steps/create_workspace_stash.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2018 Booz Allen Hamilton. All Rights Reserved. 3 | This software package is licensed under the Booz Allen Public License. The license can be found in the License file or at http://boozallen.github.io/licenses/bapl 4 | */ 5 | 6 | package libraries.sdp.steps 7 | 8 | import hudson.AbortException 9 | 10 | @Validate // validate so this runs prior to other @Init steps 11 | void call(){ 12 | node{ 13 | cleanWs() 14 | try{ 15 | checkout scm 16 | }catch(AbortException ex) { 17 | println "scm var not present, skipping source code checkout" 18 | }catch(err){ 19 | println "exception ${err}" 20 | } 21 | 22 | stash name: 'workspace', allowEmpty: true, useDefaultExcludes: false 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /libraries/sdp/steps/inside_sdp_image.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2018 Booz Allen Hamilton. All Rights Reserved. 3 | This software package is licensed under the Booz Allen Public License. The license can be found in the License file or at http://boozallen.github.io/licenses/bapl 4 | */ 5 | 6 | package libraries.sdp.steps 7 | 8 | /* 9 | A helper function for running pipeline code inside 10 | an SDP pipeline image. 11 | 12 | ex: 13 | inside_sdp_image "openshift_helm", { 14 | sh "helm version" 15 | } 16 | */ 17 | void call(String img, Closure body){ 18 | 19 | config.images ?: { error "SDP Image Config not defined in Pipeline Config" } () 20 | 21 | def sdp_img_reg = config.images.registry ?: 22 | { error "SDP Image Registry not defined in Pipeline Config" } () 23 | 24 | def sdp_img_repo = config.images.repository ?: 25 | { return "sdp" }() 26 | 27 | def sdp_img_repo_cred = config.images.cred ?: 28 | { error "SDP Image Repository Credential not defined in Pipeline Config" }() 29 | 30 | def docker_args = config.images.docker_args ?: 31 | { return ""}() 32 | 33 | docker.withRegistry(sdp_img_reg, sdp_img_repo_cred){ 34 | docker.image("${sdp_img_repo}/${img}").inside("${docker_args}"){ 35 | body() 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /libraries/sdp/steps/jteVersion.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2018 Booz Allen Hamilton. All Rights Reserved. 3 | This software package is licensed under the Booz Allen Public License. 4 | The license can be found in the License file or at http://boozallen.github.io/licenses/bapl 5 | */ 6 | package libraries.sdp.steps 7 | 8 | import jenkins.model.Jenkins 9 | import groovy.transform.Field 10 | 11 | @Field static private final int LESS_THAN = -1 12 | @Field static private final int GREATER_THAN = 1 13 | @Field static private final int EQUAL_TO = 0 14 | 15 | String get(){ 16 | return Jenkins.get().pluginManager.getPlugin("templating-engine").version 17 | } 18 | 19 | boolean lessThan(String version){ 20 | return compare(this.get(), version) == LESS_THAN 21 | } 22 | 23 | boolean greaterThan(String version){ 24 | return compare(this.get(), version) == GREATER_THAN 25 | } 26 | 27 | boolean equalTo(String version){ 28 | return compare(this.get(), version) == EQUAL_TO 29 | } 30 | 31 | boolean lessThanOrEqualTo(String version){ 32 | return compare(this.get(), version) in [LESS_THAN, EQUAL_TO] 33 | } 34 | 35 | boolean greaterThanOrEqualTo(String version){ 36 | return compare(this.get(), version) in [LESS_THAN, GREATER_THAN] 37 | } 38 | 39 | int compare(String v1, String v2){ 40 | return compareVersions(v1: v1, v2: v2) 41 | } 42 | 43 | -------------------------------------------------------------------------------- /libraries/slack/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: Facilitates pipeline notifications to the configured Slack channel 3 | --- 4 | 5 | # Slack 6 | 7 | Slack is a collection of tools and services that helps teams collaborate and work more efficiently. 8 | Services that it provides include a messaging platform as well as the capability to integrate with other 3rd party tools such as Trello, Jira, Splunk, Jenkins, and more. 9 | 10 | ## Steps 11 | 12 | --- 13 | 14 | * slack() 15 | 16 | ## Dependencies 17 | 18 | --- 19 | 20 | More information can be found on the [Slack plugin website](https://plugins.jenkins.io/slack/). 21 | -------------------------------------------------------------------------------- /libraries/slack/steps/slack.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2018 Booz Allen Hamilton. All Rights Reserved. 3 | This software package is licensed under the Booz Allen Public License. The license can be found in the License file or at http://boozallen.github.io/licenses/bapl 4 | */ 5 | 6 | package libraries.slack.steps 7 | 8 | def call(){ 9 | switch(currentBuild.result){ 10 | case null: // no result set yet means success 11 | case "SUCCESS": 12 | slackSend color: "good", message: "Build Successful: ${env.JOB_URL}" 13 | break; 14 | case "FAILURE": 15 | slackSend color: '#ff0000', message: "Build Failure: ${env.JOB_URL}" 16 | break; 17 | default: 18 | echo "Slack Notifier doing nothing: ${currentBuild.result}" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /libraries/slack/test/SlackSpec.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2018 Booz Allen Hamilton. All Rights Reserved. 3 | This software package is licensed under the Booz Allen Public License. The license can be found in the License file or at http://boozallen.github.io/licenses/bapl 4 | */ 5 | 6 | package libraries.slack 7 | 8 | public class SlackSpec extends JTEPipelineSpecification { 9 | 10 | def SlackTest = null 11 | 12 | def setup() { 13 | SlackTest = loadPipelineScriptForStep("slack","slack") 14 | explicitlyMockPipelineStep("echo") 15 | } 16 | 17 | def "Successful Build Sends Success Result" () { 18 | setup: 19 | SlackTest.getBinding().setVariable("currentBuild", [ result: "SUCCESS" ]) 20 | when: 21 | SlackTest() 22 | then: 23 | 1 * getPipelineMock("slackSend")(_ as Map) >> { _arguments -> 24 | assert _arguments[0]["message"] =~ /Build Successful:.*/ 25 | } 26 | } 27 | 28 | def "Failed Build Sends Fail Result" () { 29 | setup: 30 | SlackTest.getBinding().setVariable("currentBuild", [ result: "FAILURE" ]) 31 | when: 32 | SlackTest() 33 | then: 34 | 1 * getPipelineMock("slackSend")(_ as Map) >> { _arguments -> 35 | assert _arguments[0]["message"] =~ /Build Failure:.*/ 36 | } 37 | } 38 | 39 | def "Other Builds Send No Result" () { 40 | setup: 41 | SlackTest.getBinding().setVariable("currentBuild", [ result: "ILLOGICAL" ]) 42 | explicitlyMockPipelineVariable("out") //not sure why, but this tests fails w/o this mock 43 | when: 44 | SlackTest() 45 | then: 46 | 0 * getPipelineMock("slackSend")(_) 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /libraries/sonarqube/library_config.groovy: -------------------------------------------------------------------------------- 1 | fields{ 2 | required{} 3 | optional{ 4 | wait_for_quality_gate = Boolean 5 | enforce_quality_gate = Boolean 6 | credential_id = String 7 | installation_name = String 8 | stage_display_name = String 9 | timeout_duration = Number 10 | timeout_unit = [ "NANOSECONDS", "MICROSECONDS", "MILLISECONDS", "SECONDS", "MINUTES", "HOURS", "DAYS" ] 11 | cli_parameters = List 12 | unstash = List 13 | } 14 | } -------------------------------------------------------------------------------- /libraries/syft/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: This library allows you to generate a Software Bill of Materials (SBOM) for each container built in your project 3 | --- 4 | 5 | # Syft 6 | 7 | This library allows you to generate a Software Bill of Materials (SBOM) for each container built in your project using the [Syft tool](https://github.com/anchore/syft). 8 | 9 | ## Steps 10 | 11 | | Step | Description | 12 | |-------------------|--------------------------------------------------| 13 | | `generate_sbom()` | Generates and archives SBOM files in JSON format | 14 | 15 | ## Configuration 16 | 17 | | Library Configuration | Description | Type | Default Value | Options | 18 | |-----------------------|---------------------------------------------------------------|-------------|---------------------|-----------------------------------------------------------------------------------------------------------| 19 | | `raw_results_file` | The base name of the report file generated. Omit Extension. | String | `syft-sbom-results` | | 20 | | `sbom_container` | Name of the container image containing the syft executable. | String | `syft:0.47.0` | | 21 | | `sbom_format` | The valid formats a report can be generated in. | ArrayList | `['json']` | `['json', 'text', 'cyclonedx-xml', 'cyclonedx-json', 'spdx-tag-value', 'spdx-json', 'github', 'table']` | 22 | | `remove_syft_config` | Removes .syft.yaml from the workspace if needed. | Boolean | `True` | `True, False` | 23 | | `config_name` | Name of config to remove. | String | `.syft.yaml` | | 24 | 25 | ``` groovy title='pipeline_config.groovy' 26 | libraries { 27 | syft { 28 | raw_results_file = "syft-scan" 29 | sbom_container = "syft:v0.47.0" 30 | sbom_format = ['json', 'spdx-json', 'table'] 31 | remove_syft_config = true 32 | config_name = ".syft.yaml" 33 | } 34 | } 35 | ``` 36 | 37 | ## Dependencies 38 | 39 | * Base SDP library 40 | * Docker SDP library 41 | -------------------------------------------------------------------------------- /libraries/syft/library_config.groovy: -------------------------------------------------------------------------------- 1 | fields { 2 | required { 3 | } 4 | optional { 5 | raw_results_file = String 6 | sbom_container = String 7 | sbom_format = ArrayList 8 | remove_syft_config = Boolean 9 | config_name = String 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /libraries/syft/steps/generate_sbom.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2022 Booz Allen Hamilton. All Rights Reserved. 3 | This software package is licensed under the Booz Allen Public License. 4 | The license can be found in the License file or at http://boozallen.github.io/licenses/bapl 5 | */ 6 | package libraries.syft.steps 7 | 8 | void call() { 9 | stage('Generate SBOM using Syft') { 10 | //Import settings from config 11 | String raw_results_file = config?.raw_results_file ?: 'syft-sbom-results' // leave off file extension so that it can be added based off off selected formats 12 | String sbom_container = config?.sbom_container ?: 'syft:0.47.0' 13 | String config_name = config?.config_name ?: '.syft.yaml' 14 | ArrayList sbom_format = config?.sbom_format ?: ["json"] 15 | String artifacts = "" 16 | boolean remove_syft_config = config?.remove_syft_config ?: true 17 | boolean shouldFail = false 18 | 19 | //Get list of images to scan (assuming same set built by Docker) 20 | def images = get_images_to_build() 21 | inside_sdp_image "${sbom_container}", { 22 | login_to_registry { 23 | unstash "workspace" 24 | if(remove_syft_config) { 25 | if(fileExists(config_name)) { 26 | sh "rm ${config_name}" 27 | } 28 | } 29 | images.each { img -> 30 | String ARGS = "-q" 31 | String results_name = "${img.repo}-${img.tag}-${raw_results_file}".replaceAll("/","-") 32 | sbom_format.each { format -> 33 | String formatter = "" 34 | if(format == "json" || format == "cyclonedx-json" || format == "spdx-json" || format == "github") { 35 | formatter += "${results_name}-${format}.json" 36 | } 37 | else if(format == "text" || format == "spdx-tag-value" || format == "table") { 38 | formatter += "${results_name}-${format}.txt" 39 | } 40 | else if (format == "cyclonedx-xml") { 41 | formatter += "${results_name}-${format}.xml" 42 | } 43 | 44 | ARGS += " -o ${format}=${formatter} " 45 | artifacts += "${formatter}," 46 | } 47 | 48 | // perform the syft scan 49 | try { 50 | sh "syft ${img.registry}/${img.repo}:${img.tag} ${ARGS}" 51 | } 52 | catch(Exception err) { 53 | shouldFail = true 54 | echo "SBOM generation Failed: ${err}" 55 | } 56 | finally { 57 | if(shouldFail){ 58 | error("SBOM Stage Failed") 59 | } 60 | else { 61 | archiveArtifacts artifacts: "${artifacts.replaceAll(',$', "")}" 62 | } 63 | } 64 | } 65 | 66 | stash "workspace" 67 | } 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /libraries/syft/test/GenerateSBOMSpec.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2022 Booz Allen Hamilton. All Rights Reserved. 3 | This software package is licensed under the Booz Allen Public License. 4 | The license can be found in the License file or at http://boozallen.github.io/licenses/bapl 5 | */ 6 | 7 | package libraries.syft 8 | 9 | public class GenerateSBOMSpec extends JTEPipelineSpecification { 10 | def GenerateSBOM = null 11 | 12 | def setup() { 13 | GenerateSBOM = loadPipelineScriptForStep("syft", "generate_sbom") 14 | 15 | GenerateSBOM.getBinding().setVariable("config", [:]) 16 | 17 | explicitlyMockPipelineStep("login_to_registry") 18 | explicitlyMockPipelineStep("inside_sdp_image") 19 | explicitlyMockPipelineVariable("get_images_to_build") 20 | 21 | getPipelineMock("get_images_to_build.call")() >> { 22 | def images = [] 23 | images << [registry: "ghcr.io/boozallen/sdp-images", repo: "syft", context: "syft", tag: "latest"] 24 | images << [registry: "ghcr.io/boozallen/sdp-images", repo: "grype", context: "grype", tag: "latest"] 25 | return images 26 | } 27 | } 28 | 29 | def "Generates Software Bill of Materials file" () { 30 | given: 31 | GenerateSBOM.getBinding().setVariable("config", [sbom_format: ["json"]]) 32 | when: 33 | GenerateSBOM() 34 | then: 35 | 1 * getPipelineMock('sh').call('syft ghcr.io/boozallen/sdp-images/syft:latest -q -o json=syft-latest-syft-sbom-results-json.json ') 36 | 1 * getPipelineMock('sh').call('syft ghcr.io/boozallen/sdp-images/grype:latest -q -o json=grype-latest-syft-sbom-results-json.json ') 37 | } 38 | 39 | def "Archives SBOM file as expected" () { 40 | when: 41 | GenerateSBOM() 42 | then: 43 | 2 * getPipelineMock('archiveArtifacts.call')(_ as Map) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /libraries/sysdig_secure/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: Performs container image scanning with Sysdig Secure's inline scanner 3 | --- 4 | 5 | # Sysdig Secure 6 | 7 | This library leverages a script from Sysdig Secure ([inline scanning script](https://github.com/sysdiglabs/secure-inline-scan)) to scan container images, 8 | report the information to the Sysdig Secure server, and download a PDF report of the findings. 9 | 10 | ## Steps 11 | 12 | --- 13 | 14 | | Step | Description | 15 | | ----------- | ----------- | 16 | | `scan_container_image()` | Scans container images determined by `get_images_to_build()` | 17 | 18 | ## Configuration 19 | 20 | --- 21 | 22 | Configuration Options 23 | 24 | | Field | Type | Description | Default Value | 25 | | ----------- | ----------- | ----------- | ----------- | 26 | | `scan_script_url` | String | An address from which to download the inline_scan.sh file | | 27 | | `sysdig_secure_url` | String | The Sysdig Secure address to publish results to | | 28 | | `cred` | String | A string matching a credential id of a secret text credential in the Jenkins Credential store holding an API token to authenticate to the Sysdig Secure API || 29 | | `enforce_success` | Boolean | Whether to fail the build if the scan fails | `true` | 30 | 31 | ```groovy 32 | libraries{ 33 | sysdig_secure{ 34 | cred = "sysdig-secure-api-token" 35 | } 36 | } 37 | ``` 38 | 39 | ## Results 40 | 41 | --- 42 | 43 | The `scan_container_images()` step will generate a PDF report of the scan if the upload to the Sysdig Secure API is successful. 44 | [Here's an example](../../assets/attachments/sysdig_secure/sysdig_secure_report.pdf). 45 | 46 | ## Dependencies 47 | 48 | --- 49 | 50 | This library, by nature of the inline scanning script, requires that: 51 | 52 | * a running docker daemon is available 53 | * internet access to pull an image from [Docker Hub](https://hub.docker.com/r/anchore/inline-scan) 54 | 55 | **Note** At the time of writing, this library could be expanded to pass a custom image to perform the scanning, 56 | perhaps helpful if proxying through a local registry, by setting the environment variable `SYSDIG_CI_IMAGE` as part of the command invocation. 57 | 58 | ## Troubleshooting 59 | 60 | --- 61 | -------------------------------------------------------------------------------- /libraries/sysdig_secure/library_config.groovy: -------------------------------------------------------------------------------- 1 | fields{ 2 | required{ 3 | cred = String 4 | } 5 | optional{ 6 | scan_script_url = String 7 | sysdig_secure_url = String 8 | enforce_success = Boolean 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /libraries/sysdig_secure/steps/scan_container_image.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2018 Booz Allen Hamilton. All Rights Reserved. 3 | This software package is licensed under the Booz Allen Public License. The license can be found in the License file or at http://boozallen.github.io/licenses/bapl 4 | */ 5 | 6 | package libraries.sysdig_secure.steps 7 | 8 | void call(){ 9 | stage("Scanning Container Image: Sysdig Secure"){ 10 | node{ 11 | String inlineScriptLocation = config.scan_script_url ?: "https://download.sysdig.com/stable/inline_scan.sh" 12 | String sysdig_secure_url = config.sysdig_secure_url ?: null 13 | boolean enforce_success = config.containsKey("enforce_success") ? config.enforce_success : true 14 | String sArg = sysdig_secure_url ? "-s ${sysdig_secure_url}" : "-s https://secure.sysdig.com" 15 | withCredentials([string(credentialsId: config.cred, variable: 'TOKEN')]) { 16 | catchError(message: 'Failed to fetch inline_scan.sh from GitHub', stageResult: 'FAILURE') { 17 | sh "curl -o inline_scan.sh ${inlineScriptLocation}" 18 | } 19 | String resultsDir = "sysdig-secure" 20 | sh "mkdir -p ${resultsDir}" 21 | def imageThreads = [:] 22 | get_images_to_build().each{ img -> 23 | String image = "${img.registry}/${img.repo}:${img.tag}" 24 | imageThreads[image] = { 25 | login_to_registry{ 26 | sh "docker pull ${image}" 27 | sh "sh inline_scan.sh analyze -R ${resultsDir} ${sArg} -k $TOKEN ${image}" 28 | } 29 | } 30 | } 31 | try{ 32 | parallel imageThreads 33 | }catch(any){ 34 | String msg = "Sysdig Secure: Failed to scan images" 35 | enforce_success ? error(msg) : unstable(msg) 36 | }finally{ 37 | archiveArtifacts allowEmptyArchive: true, artifacts: "${resultsDir}/" 38 | } 39 | } 40 | } 41 | } 42 | } -------------------------------------------------------------------------------- /libraries/terraform/steps/deploy_to.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2018 Booz Allen Hamilton. All Rights Reserved. 3 | This software package is licensed under the Booz Allen Public License. The license can be found in the License file or at http://boozallen.github.io/licenses/bapl 4 | */ 5 | 6 | package libraries.terraform.steps 7 | 8 | void call(app_env){ 9 | 10 | LinkedHashMap libSecrets = config.secrets ?: [:] 11 | LinkedHashMap envSecrets = app_env.terraform?.secrets ?: [:] 12 | LinkedHashMap secrets = libSecrets + envSecrets 13 | this.validateParameters(secrets) 14 | 15 | String workingDir = app_env.terraform?.working_directory ?: 16 | config.working_directory ?: "." 17 | 18 | ArrayList creds = [] 19 | secrets.keySet().each{ key -> 20 | def secret = secrets[key] 21 | switch(secret.type){ 22 | case "text": 23 | creds << string(credentialsId: secret.id, variable: secret.name) 24 | break 25 | case "usernamePassword": 26 | creds << usernamePassword(credentialsId: secret.id, usernameVariable: secret.usernameVar, passwordVariable: secret.passwordVar) 27 | break 28 | } 29 | } 30 | 31 | inside_sdp_image "terraform", { 32 | unstash "workspace" 33 | if(!fileExists(workingDir)){ 34 | error "specified working directory '${workingDir}' does not exist" 35 | } 36 | dir(workingDir){ 37 | withCredentials(creds){ 38 | sh "terraform init -plugin-dir=/plugins -input=false" 39 | sh "terraform apply -auto-approve -input=false" 40 | } 41 | } 42 | } 43 | } 44 | 45 | void validateParameters(secrets){ 46 | ArrayList errors = [] 47 | secrets.keySet().each{ key -> 48 | def secret = secrets[key] 49 | println "secret -> ${secret}" 50 | if(!secret.id){ 51 | errors << "secret '${key}' must define 'id'" 52 | } 53 | switch(secret.type){ 54 | case "text": 55 | if(!secret.name) errors << "secret '${key}' must define 'name'" 56 | break 57 | case "usernamePassword": 58 | if(!secret.usernameVar) errors << "secret '${key}' must define 'usernameVar'" 59 | if(!secret.passwordVar) errors << "secret '${key}' must define 'passwordVar'" 60 | break 61 | default: 62 | errors << "secret '${key}': type '${secret.type}' is not defined" 63 | } 64 | } 65 | 66 | if(errors){ 67 | error (["Terraform Library Validation Errors: "] + errors.collect{ "- ${it}"}).join("\n") 68 | } 69 | 70 | } -------------------------------------------------------------------------------- /libraries/twistlock/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: Performs container image scanning with TwistLock 3 | --- 4 | 5 | # Twistlock 6 | 7 | Twistlock is an automated and scalable container cyber-security platform. 8 | Twistlock manages a full-lifecycle vulnerability and compliance management to application-tailored runtime defense and cloud native firewalls. 9 | Twistlock helps secure your containers and modern applications against threats across the entire application lifecycle. 10 | 11 | SDP can integrate with Twistlock to perform **container image scanning**. 12 | 13 | ## Steps 14 | 15 | --- 16 | 17 | | Step | Description | 18 | | ----------- | ----------- | 19 | | `scan_container_image()` | Downloads the Twistlock CLI from the Twistlock Console and performs container image scanning | 20 | 21 | ## Configuration 22 | 23 | --- 24 | 25 | Twistlock Library Configuration Options 26 | 27 | | Field | Description | Default Value | 28 | | ----------- | ----------- | ----------- | 29 | | `url` | The Twistlock Console address | | 30 | | `credential` | The Jenkins credential ID to access Twistlock Console | | 31 | 32 | ### Example Configuration Snippet 33 | 34 | ```groovy 35 | libraries{ 36 | twistlock{ 37 | url = "https://twistlock.apps.ocp.microcaas.net" 38 | credential = "twistlock" 39 | } 40 | } 41 | ``` 42 | 43 | ## Dependencies 44 | 45 | --- 46 | 47 | * Twistlock is deployed and accessible from Jenkins 48 | * A credential has been placed in the Jenkins credential store to access the console 49 | * A separate container building library that implements `get_images_to_build()` 50 | 51 | ## Twistlock Scan Results 52 | 53 | --- 54 | 55 | Jenkins will output a text based table of the scan results. 56 | A more descriptive JSON file is archived that contains details of CVE and compliance vulnerabilities found during the scan. 57 | 58 | ```txt 59 | CVE Results: 60 | ----------------------------------------- 61 | Low: [0-9]* Number of Low vulnerabilities 62 | Medium: [0-9]* Number of Medium vulnerabilities 63 | High: [0-9]* Number of High vulnerabilities 64 | Critical: [0-9]* Number of Critical vulnerabilities 65 | 66 | Compliance Results: 67 | ----------------------------------------- 68 | Low: [0-9]* Number of Low compliance violations 69 | Medium: [0-9]* Number of Medium compliance violations 70 | High: [0-9]* Number of High compliance violations 71 | Critical: [0-9]* Number of Critical compliance violations 72 | ``` 73 | -------------------------------------------------------------------------------- /libraries/twistlock/steps/scan_container_image.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2018 Booz Allen Hamilton. All Rights Reserved. 3 | This software package is licensed under the Booz Allen Public License. The license can be found in the License file or at http://boozallen.github.io/licenses/bapl 4 | */ 5 | 6 | package libraries.twistlock.steps 7 | 8 | def call() { 9 | 10 | if (!config.url) 11 | error "Twistlock url not defined in library spec" 12 | 13 | if (!config.credential) 14 | error "Twistlock credential not defined in library spec" 15 | 16 | stage "Scanning Container Images", { 17 | node { 18 | withCredentials([usernamePassword(credentialsId: config.credential, passwordVariable: 'pass', usernameVariable: 'user')]) { 19 | // from container image building library 20 | // comes from whatever library builds container images 21 | // for now .. just docker 22 | login_to_registry{ 23 | unstash "workspace" 24 | this.get_twistcli() 25 | def images = "" 26 | get_images_to_build().each { img -> 27 | image = "${img.registry}/${img.repo}:${img.tag} " //The trailing space is intentional 28 | sh "docker pull ${image}" 29 | images += image 30 | } 31 | def scan_url = this.do_scan(images) 32 | def results = this.parse_results(scan_url) 33 | results.images.each { 34 | echo """ 35 | Twistlock Scan Results: ${it.info.tags.registry}/${it.info.tags.repo}/${it.info.tags.tag} 36 | ----------------------------------------- 37 | CVE Results: 38 | Low: ${it.info.cveVulnerabilityDistribution.low} 39 | Medium: ${it.info.cveVulnerabilityDistribution.medium} 40 | High: ${it.info.cveVulnerabilityDistribution.high} 41 | Critical: ${it.info.cveVulnerabilityDistribution.critical} 42 | Compliance Results: 43 | Low: ${it.info.complianceDistribution.low} 44 | Medium: ${it.info.complianceDistribution.medium} 45 | High: ${it.info.complianceDistribution.high} 46 | Critical: ${it.info.complianceDistribution.critical} 47 | """.stripIndent() 48 | } 49 | } 50 | } 51 | } 52 | } 53 | } 54 | 55 | void get_twistcli() { 56 | echo "getting twistlock CLI" 57 | sh "curl -k -u '${user}':'${pass}' -H 'Content-Type: application/json' -X GET -o /usr/local/bin/twistcli ${config.url}/api/v1/util/twistcli" 58 | sh "chmod +x /usr/local/bin/twistcli" 59 | sh "twistcli -v" 60 | } 61 | 62 | String do_scan(images) { 63 | def output = sh( 64 | script: "twistcli images scan --details --upload --address ${config.url} -u ${user} -p '${pass}' ${images}", 65 | returnStdout: true 66 | ).trim() 67 | 68 | return (output =~ /Results at: (.*)$/)[0][1] 69 | } 70 | 71 | def parse_results(scan_url) { 72 | auth_64 = sh(returnStdout: true, script: "echo -n '${user}:${pass}' | openssl base64").trim() 73 | sh "curl -k -H 'Authorization: Basic ${auth_64}' ${scan_url} > scan.tar.gz" 74 | sh "tar -xvf scan.tar.gz" 75 | sh "mv analysis.json twistlock_results.json" 76 | archiveArtifacts 'twistlock_results.json' 77 | return readJSON(file: "twistlock_results.json") 78 | } 79 | -------------------------------------------------------------------------------- /libraries/webhint/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: A customizable linting tool that helps you improve your site's accessibility, speed, cross-browser compatibility, and more by checking your code for best practices and common errors 3 | --- 4 | 5 | # webhint 6 | 7 | [webhint](https://webhint.io) is a customizable linting tool that helps you improve your site's accessibility, 8 | speed, cross-browser compatibility, and more by checking your code for best practices and common errors. 9 | 10 | ## Steps 11 | 12 | --- 13 | 14 | | Step | Description | 15 | | ----------- | ----------- | 16 | | `accessibility_compliance_scan()` | generates website developer hints from the given web address | 17 | 18 | ## Library Configuration Options 19 | 20 | --- 21 | 22 | Configuration Options 23 | 24 | | Field | Description | Default Value | 25 | | ----------- | ----------- | ----------- | 26 | | `url` | web address to analyze | | 27 | | `extender` | Array - Optional - Hint types. See [the documentation](https://webhint.io/docs/user-guide/configurations/configuration-development/) for more information. | `["accessibility"]` | 28 | | `failThreshold` | Optional - Hint limit at which the jenkins build will fail | 25 | 29 | | `warnThreshold` | Optional - Hint limit at which the jenkins build will issue a warning | 10 | 30 | 31 | ```groovy 32 | libraries{ 33 | url = "your_url_here" 34 | extender = ["progressive-web-apps"] 35 | failThreshold = 35 36 | warnThreshold = 25 37 | } 38 | ``` 39 | 40 | ## Results 41 | 42 | --- 43 | 44 | [View an example HTML report, saved to PDF](../../assets/attachments/webhint/webhint_mockaroo.pdf). 45 | 46 | ## External Dependencies 47 | 48 | --- 49 | 50 | * none 51 | 52 | ## Troubleshooting 53 | 54 | --- 55 | -------------------------------------------------------------------------------- /libraries/webhint/library_config.groovy: -------------------------------------------------------------------------------- 1 | fields{ 2 | required{ 3 | url = String 4 | } 5 | 6 | optional{ 7 | extender = List 8 | failThreshold = Number 9 | warnThreshold = Number 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /libraries/webhint/steps/accessibility_compliance_scan.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2018 Booz Allen Hamilton. All Rights Reserved. 3 | This software package is licensed under the Booz Allen Public License. The license can be found in the License file or at http://boozallen.github.io/licenses/bapl 4 | */ 5 | 6 | package libraries.webhint.steps 7 | 8 | void call(){ 9 | stage("Webhint: Lint") { 10 | def url = config.url ?: { 11 | error """ 12 | Webhint.io Library needs the target url. 13 | libraries{ 14 | webhint{ 15 | url = "https://example.com" 16 | } 17 | } 18 | """ 19 | } () 20 | 21 | inside_sdp_image "webhint", { 22 | String resultsDir = "hint-report" 23 | String resultsText = "hint.results.log" 24 | 25 | this.createAndAddHintrcFile("${resultsDir}") 26 | this.processUrl(url, resultsDir, resultsText) 27 | archiveArtifacts allowEmptyArchive: true, artifacts: "${resultsDir}/" 28 | this.validateResults("${resultsDir}/${resultsText}") 29 | } 30 | } 31 | } 32 | 33 | void createAndAddHintrcFile(String path) { 34 | def hintrc = [ 35 | extends: config.extender ?: [ "accessibility" ], 36 | formatters: [ "html", "summary" ] 37 | ] 38 | 39 | sh "mkdir -p ${path}" 40 | writeJSON file: "${path}/.hintrc", json: hintrc 41 | sh "cp ${path}/.hintrc .;" 42 | } 43 | 44 | void processUrl(String url, String resultsDir, String resultsText) { 45 | sh "cat ${resultsDir}/.hintrc" 46 | sh script: "hint ${url} > ${resultsDir}/${resultsText}", returnStatus: true 47 | } 48 | 49 | void validateResults(String filePath) { 50 | if(!fileExists(filePath)) { 51 | return 52 | } 53 | 54 | def file = readFile file: "${filePath}" 55 | def lines = file.readLines() 56 | def lastline=lines.get(lines.size()-1) 57 | def total = 0 58 | 59 | for (String item : lastline.split(' ')) { 60 | if (item.isNumber()) total += item.toInteger() 61 | } 62 | 63 | int fail = config.failThreshold ?: 25 64 | int warn = config.warnThreshold ?: 10 65 | 66 | boolean shouldFail = total >= fail 67 | boolean shouldWarn = total >= warn 68 | 69 | echo "[total hints:${total}] [fail threshold:${fail}] [warn threshold:${warn}]" 70 | 71 | if(shouldFail) error("Webhint.io found ${total} suggestion(s) meeting or exceeding the fail threshold of ${fail}.") 72 | else if(shouldWarn) unstable("Webhint.io found ${total} suggestion(s). Consider fixing a few of them.") 73 | } 74 | -------------------------------------------------------------------------------- /mkdocs.yml: -------------------------------------------------------------------------------- 1 | site_name: Solutions Delivery Platform 2 | 3 | theme: 4 | name: material 5 | palette: 6 | primary: navy 7 | features: 8 | - navigation.instant 9 | - navigation.tabs 10 | - navigation.sections 11 | - navigation.top 12 | - content.code.annotate 13 | logo: assets/images/jte.svg 14 | favicon: assets/images/jte.svg 15 | ## this is a hack 16 | ## we add the libraries dir as a custom dir for the theme so that 17 | ## we get live reloading of the docs site via `just serve` by passing 18 | ## --watch-theme to the `mkdocs serve` command 19 | custom_dir: libraries/ 20 | 21 | repo_url: https://github.com/boozallen/sdp-libraries 22 | 23 | plugins: 24 | - search 25 | - awesome-pages 26 | - gen-files: 27 | scripts: 28 | - resources/docs/copy_docs.py 29 | - resources/docs/add_glossary.py 30 | 31 | extra_css: 32 | - css/extra.css 33 | 34 | extra: 35 | version: 36 | provider: mike 37 | default: latest 38 | social: 39 | - icon: fontawesome/brands/github 40 | link: https://github.com/boozallen/sdp-libraries.git 41 | 42 | markdown_extensions: 43 | - admonition 44 | - pymdownx.details 45 | - abbr 46 | - footnotes 47 | - attr_list 48 | - pymdownx.tabbed: 49 | alternate_style: true 50 | - pymdownx.highlight 51 | - pymdownx.superfences 52 | - toc: 53 | permalink: true 54 | - pymdownx.snippets: 55 | base_path: "docs" 56 | - pymdownx.betterem: 57 | smart_enable: all 58 | - pymdownx.emoji: 59 | emoji_index: !!python/name:materialx.emoji.twemoji 60 | emoji_generator: !!python/name:materialx.emoji.to_svg 61 | 62 | nav: 63 | - Home: 'index.md' 64 | - Concepts: 65 | - 'concepts/overview.md' 66 | - Unit Testing: 67 | - 'concepts/unit-testing/index.md' 68 | - 'concepts/unit-testing/jenkins-spock.md' 69 | - 'concepts/unit-testing/writing-tests.md' 70 | - 'concepts/unit-testing/executing-tests.md' 71 | - 'concepts/unit-testing/faq.md' 72 | - Tutorials: 73 | - 'tutorials/overview.md' 74 | - How-To Guides: 75 | - 'how-to/overview.md' 76 | - 'how-to/pin-a-library-source-to-a-specific-release.md' 77 | # navigation for Libraries section is driven by the gen_scripts and awesome-pages plugins 78 | # see: /resources/docs/copy_docs.py 79 | - ... | glob=libraries/** 80 | - Contributing: 81 | - 'contributing/index.md' 82 | - 'contributing/create-new-library.md' -------------------------------------------------------------------------------- /resources/docs/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM squidfunk/mkdocs-material:8.0.4 2 | RUN pip install \ 3 | mkdocs-gen-files \ 4 | mike \ 5 | pymdown-extensions \ 6 | mkdocs-awesome-pages-plugin \ 7 | pyyaml \ 8 | python-frontmatter \ 9 | beautifulsoup4 \ 10 | markdown \ 11 | pytablewriter && \ 12 | git config --global user.name "docs deployer" && \ 13 | git config --global user.email "null@null.com" && \ 14 | git config --global credential.helper 'store --file=/root/.git-credentials' -------------------------------------------------------------------------------- /resources/docs/README.template.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: 3 | --- 4 | 5 | # $LIB 6 | 7 | ## Steps 8 | 9 | | Step | Description | 10 | |------|-------------| 11 | | | | 12 | 13 | ## Configuration 14 | 15 | | Library Configuration | Type | Default Value | 16 | |-----------------------|------|---------------| 17 | | | | | 18 | 19 | ## Dependencies 20 | -------------------------------------------------------------------------------- /resources/docs/add_glossary.py: -------------------------------------------------------------------------------- 1 | import mkdocs_gen_files 2 | import os 3 | import glob 4 | 5 | # iterate over pages and append glossary 6 | for file in glob.glob("/docs/docs/**/*.md", recursive = True): 7 | if file.endswith('.md'): 8 | text = open(file).read() 9 | with mkdocs_gen_files.open(file.replace('/docs/docs/', ''), "w") as f: 10 | print(text + '\n--8<-- "./glossary.md"', file=f) -------------------------------------------------------------------------------- /resources/docs/copy_docs.py: -------------------------------------------------------------------------------- 1 | import mkdocs_gen_files 2 | import yaml 3 | import frontmatter 4 | import os 5 | import markdown 6 | from pytablewriter import MarkdownTableWriter 7 | from bs4 import BeautifulSoup 8 | 9 | # Move README to index unless there's already an index.md 10 | if(os.path.exists("/docs/docs/index.md") != True): 11 | readme = open("/docs/README.md").read() 12 | with mkdocs_gen_files.open("index.md", "w") as f: 13 | print(readme, file=f) 14 | 15 | # Move Library Docs 16 | # Collect frontmatter description for overview page 17 | libraries = [] 18 | section_name = "SDP Pipeline Libraries" 19 | rootdir = "/docs/libraries" 20 | for library in os.listdir(rootdir): 21 | file = os.path.join(rootdir, library, "README.md") 22 | if(os.path.exists(file)): 23 | lib_data = { 24 | "file": f"{library}.md", 25 | "description": "Library is missing description in README frontmatter", 26 | "name": None 27 | } 28 | readme = open(file).read() 29 | # determine description from frontmatter 30 | post = frontmatter.loads(readme) 31 | if "description" in post.keys(): 32 | lib_data["description"] = post["description"] 33 | # determine library name by extracting H1 from markdown 34 | ## to avoid regex... we'll convert to html and then extract first

tag 35 | html = markdown.markdown(readme) 36 | soup = BeautifulSoup(html, 'html.parser') 37 | lib_data["name"] = soup.h1.get_text() 38 | # add lib_data to libraries array 39 | libraries.append(lib_data) 40 | # virtually add library README to docs/ 41 | with mkdocs_gen_files.open(f"libraries/{section_name}/{library}.md", "w") as f: 42 | print(readme, file=f) 43 | 44 | # sort libraries by name ignoring case 45 | libraries.sort(key = lambda i: str.lower(i["name"])) 46 | 47 | # build markdown table for overview page 48 | table = [] 49 | for lib in libraries: 50 | table.append([ 51 | f"[{lib['name']}](./{section_name}/{lib['file']})", 52 | lib["description"] 53 | ]) 54 | writer = MarkdownTableWriter(headers=["Library", "Description"], value_matrix=table) 55 | overview_table = writer.dumps() 56 | 57 | # Move library index 58 | if (os.path.exists('libraries/README.md')): 59 | file = open("/docs/libraries/README.md").read() 60 | with mkdocs_gen_files.open(f"libraries/README.md", "w") as f: 61 | print(f"{file}\n\n## Available Libraries\n\n{overview_table}", file=f) 62 | 63 | # Create Libraries section navigation using mkdocs-awesome-pages 64 | pages = { 65 | "nav": { 66 | "Home": "README.md", 67 | section_name: [ lib["file"] for lib in libraries ] 68 | } 69 | } 70 | 71 | with mkdocs_gen_files.open("libraries/.pages", "w") as f: 72 | print(yaml.dump(pages), file=f) -------------------------------------------------------------------------------- /resources/test/JTEPipelineSpecification.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2018 Booz Allen Hamilton. All Rights Reserved. 3 | This software package is licensed under the Booz Allen Public License. The license can be found in the License file or at http://boozallen.github.io/licenses/bapl 4 | */ 5 | 6 | import com.homeaway.devtools.jenkins.testing.JenkinsPipelineSpecification 7 | import com.homeaway.devtools.jenkins.testing.InvalidlyNamedScriptWrapper 8 | import javax.lang.model.SourceVersion 9 | import org.codehaus.groovy.control.CompilerConfiguration 10 | import org.codehaus.groovy.control.customizers.ImportCustomizer 11 | 12 | public class JTEPipelineSpecification extends JenkinsPipelineSpecification { 13 | @Override 14 | Script loadPipelineScriptForTest(String _path) { 15 | String[] path_parts = _path.split( "/" ) 16 | String filename = path_parts[path_parts.length-1] 17 | String resource_path = "/" 18 | if( path_parts.length >= 2 ) { 19 | resource_path = String.join( "/", path_parts[0..path_parts.length-2] ) 20 | resource_path = "/${resource_path}/" 21 | } 22 | 23 | GroovyScriptEngine script_engine = new GroovyScriptEngine(generateScriptClasspath(resource_path)) 24 | CompilerConfiguration cc = script_engine.getConfig() 25 | ImportCustomizer ic = new ImportCustomizer() 26 | ic.addStarImports("org.boozallen.plugins.jte.init.primitives.hooks") 27 | ic.addImport("org.boozallen.plugins.jte.init.primitives.injectors.StepAlias") 28 | ic.addImport("com.cloudbees.groovy.cps.NonCPS") 29 | cc.addCompilationCustomizers(ic) 30 | script_engine.setConfig(cc) 31 | 32 | Class