From 5a7864b86cc4fd87b680e5d886c5d42df2249247 Mon Sep 17 00:00:00 2001 From: aleks-ivanov Date: Thu, 25 Feb 2021 10:18:05 +0000 Subject: [PATCH 1/5] add Pipeline Foundation templates --- .github/dependabot.yml | 15 ++ .github/workflows/codeql-analysis.yml | 39 +++ .github/workflows/main.yml | 252 ++++++++++++++++++ CI-CD_DOCUMENTATION.md | 354 ++++++++++++++++++++++++++ 4 files changed, 660 insertions(+) create mode 100644 .github/dependabot.yml create mode 100644 .github/workflows/codeql-analysis.yml create mode 100644 .github/workflows/main.yml create mode 100644 CI-CD_DOCUMENTATION.md diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 000000000..fa38860ea --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,15 @@ +version: 2 +updates: + - package-ecosystem: "github-actions" + # default location of `.github/workflows` + directory: "/" + schedule: + interval: "weekly" + + - package-ecosystem: "nuget" + # location of package manifests + directory: "/" + schedule: + interval: "daily" + +# Built with ❤ by [Pipeline Foundation](https://pipeline.foundation) \ No newline at end of file diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml new file mode 100644 index 000000000..e73bc1e00 --- /dev/null +++ b/.github/workflows/codeql-analysis.yml @@ -0,0 +1,39 @@ +name: CodeQL Analysis + +on: + push: + pull_request: + schedule: + - cron: '0 8 * * *' + +jobs: + analyze: + name: codeql-analysis + runs-on: windows-latest + steps: + # Due to the insufficient memory allocated by default, CodeQL sometimes requires more to be manually allocated + - name: Configure Pagefile + id: config_pagefile + uses: al-cheb/configure-pagefile-action@v1.2 + with: + minimum-size: 8GB + maximum-size: 32GB + disk-root: "D:" + + - name: Checkout repository + id: checkout_repo + uses: actions/checkout@v2 + + - name: Initialize CodeQL + id: init_codeql + uses: github/codeql-action/init@v1 + with: + queries: security-and-quality + + - name: Build project + + - name: Perform CodeQL Analysis + id: analyze_codeql + uses: github/codeql-action/analyze@v1 + +# Built with ❤ by [Pipeline Foundation](https://pipeline.foundation) \ No newline at end of file diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 000000000..a86d39bdd --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,252 @@ +name: PROJECT_NAME CI/CD Pipeline + +# There's is specific logic connected to the trigger events by exclusion: +# (github.event_name != 'pull_request') +# When adding new events, make sure they conform to the logic +# or if needed, make appropriate changes to keep the logic consistent +on: [push, pull_request, workflow_dispatch] + +jobs: + ci: + runs-on: windows-latest + outputs: + new_version: ${{ steps.tag_generator.outputs.new_version }} + is_push_to_master: ${{ steps.step_conditionals_handler.outputs.is_push_to_master }} + steps: + - name: Steps' conditionals handler + id: step_conditionals_handler + shell: pwsh + run: | + $IS_PUSH_TO_MASTER = 'false' + $IS_NOT_PR = 'true' + if ( ($env:GITHUB_EVENT_NAME -ceq 'push') -and ($env:GITHUB_REF -ceq 'refs/heads/master') ) { + $IS_PUSH_TO_MASTER = 'true' + } + if ( $env:GITHUB_EVENT_NAME -ceq 'pull_request' ) { + $IS_NOT_PR = 'false' + } + echo "::set-output name=is_push_to_master::$(echo $IS_PUSH_TO_MASTER)" + echo "::set-output name=is_not_pr::$(echo $IS_NOT_PR)" + env: + GITHUB_EVENT_NAME: ${{ github.event_name }} + GITHUB_REF: ${{ github.ref }} + + - name: Checkout repository + id: checkout_repo + uses: actions/checkout@v2 + with: + fetch-depth: 50 + + - if: steps.step_conditionals_handler.outputs.is_not_pr == 'true' + name: Install latest Java LTS + id: install_latest_java_lts + uses: actions/setup-java@v1 + with: + # Java 11 is the current long-term support version of JSE + # and is the officially suggested version by SonarCloud for GitHub Actions + java-version: 1.11 + + - if: steps.step_conditionals_handler.outputs.is_not_pr == 'true' + name: Activate TestSpace tool + id: testspace_tool + uses: testspace-com/setup-testspace@v1 + with: + domain: ${{ github.repository_owner }} + token: ${{ secrets.TESTSPACE_TOKEN }} + + # TAG GENERATOR # + + - if: steps.step_conditionals_handler.outputs.is_push_to_master == 'true' + name: Bump release version and create release tag + id: tag_generator + uses: mathieudutour/github-tag-action@v5 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + default_bump: false + + # | TAG GENERATOR # + + # CUSTOM TAG GENERATION # + # includes extraction of assembly version from Appxmanifest # + + - if: steps.step_conditionals_handler.outputs.is_push_to_master == 'true' + name: Get assembly version from appxmanifest + id: get_assembly_version + shell: pwsh + run: | + cd src/Notepads/ + $xml = [xml](Get-Content Package.appxmanifest) + $ASSEMBLY_VERSION_NUMBER = $xml.Package.Identity | Select -ExpandProperty Version + echo "::set-output name=version_num::$(echo $ASSEMBLY_VERSION_NUMBER)" + echo "::set-output name=version_tag::$(echo v"$ASSEMBLY_VERSION_NUMBER")" + + - if: steps.step_conditionals_handler.outputs.is_push_to_master == 'true' + name: Get latest tag + id: get_latest_tag + shell: pwsh + run: | + $LATEST_TAG = git -c 'versionsort.suffix=-' ls-remote --exit-code --refs --sort='version:refname' --tags "https://github.com/$env:GIT_URL.git" '*.*.*' | tail --lines=1 | cut --delimiter='/' --fields=3 + echo "::set-output name=tag::$(echo $LATEST_TAG)" + env: + GIT_URL: ${{ github.repository }} + + - if: steps.step_conditionals_handler.outputs.is_push_to_master == 'true' && steps.get_assembly_version.outputs.version_tag != steps.get_latest_tag.outputs.tag + name: Add new tag to repo + id: add_new_tag_to_repo + shell: pwsh + run: | + git config --global user.name $env:GIT_USER_NAME + git config --global user.email $env:GIT_USER_EMAIL + git tag -a -m "$env:NEW_VERSION_TAG" $env:NEW_VERSION_TAG + git push --follow-tags + env: + GIT_USER_NAME: ${{ secrets.GIT_USER_NAME }} + GIT_USER_EMAIL: ${{ secrets.GIT_USER_EMAIL }} + NEW_VERSION_TAG: ${{ steps.get_assembly_version.outputs.version_tag }} + + # | CUSTOM TAG GENERATION # + + - if: steps.step_conditionals_handler.outputs.is_not_pr == 'true' + name: Cache SonarCloud packages + id: cache_sonar_packages + uses: actions/cache@v2 + with: + path: ~\sonar\cache + key: ${{ runner.os }}-sonar + restore-keys: ${{ runner.os }}-sonar + + - if: steps.step_conditionals_handler.outputs.is_not_pr == 'true' + name: Cache SonarCloud scanner + id: cache_sonar_scanner + uses: actions/cache@v2 + with: + path: .\.sonar\scanner + key: ${{ runner.os }}-sonar-scanner + restore-keys: ${{ runner.os }}-sonar-scanner + + - if: steps.step_conditionals_handler.outputs.is_not_pr == 'true' && steps.cache_sonar_scanner.outputs.cache-hit != 'true' + name: Install SonarCloud scanner + id: install_sonar_scanner + shell: pwsh + run: | + New-Item -Path .\.sonar\scanner -ItemType Directory + dotnet tool update dotnet-sonarscanner --tool-path .\.sonar\scanner + + - if: steps.step_conditionals_handler.outputs.is_not_pr == 'true' + name: Lowercase string generator + id: lowercase_string_gen + shell: pwsh + run: | + $LOWERCASE_OWNER = "${{ github.repository_owner }}".ToLower() + echo "::set-output name=owner_name::$LOWERCASE_OWNER" + + - if: steps.step_conditionals_handler.outputs.is_not_pr == 'true' + name: Initialize SonarCloud scanner + id: init_sonar_scanner + shell: pwsh + run: | + .\.sonar\scanner\dotnet-sonarscanner begin ` + /k:"${{ github.repository_owner }}_${{ github.event.repository.name }}" ` + /o:"${{ steps.lowercase_string_gen.outputs.owner_name }}" ` + /d:sonar.login="$env:SONAR_TOKEN" ` + /d:sonar.host.url="https://sonarcloud.io" + env: + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + + # CREATION OF A PFX CERTICATE # + # used to sign packages generated by builds # + + - if: steps.step_conditionals_handler.outputs.is_push_to_master == 'true' + name: Create PFX certificate + id: create_pfx_cert + shell: pwsh + run: | + $BASE64_STR = $env:BASE64_STR + $TARGET_FILE = "$env:DEFAULT_DIR\cert.pfx" + $FROM_BASE64_STR = [Convert]::FromBase64String($BASE64_STR) + [IO.File]::WriteAllBytes($TARGET_FILE, $FROM_BASE64_STR) + env: + BASE64_STR: ${{ secrets.PFX_TO_BASE64 }} + DEFAULT_DIR: ${{ github.workspace }} + + # | CREATION OF A PFX CERTICATE # + + - name: Build project + + - if: steps.step_conditionals_handler.outputs.is_push_to_master == 'true' + name: Upload build artifacts + id: upload_artifacts + uses: actions/upload-artifact@v1 + with: + name: Build artifacts + path: Artifacts/ + + - if: steps.step_conditionals_handler.outputs.is_not_pr == 'true' && always() + name: Push result to Testspace server + id: push_to_testspace + shell: pwsh + run: | + testspace "__test/results.xml{TestsFolder/}" + + - if: steps.step_conditionals_handler.outputs.is_not_pr == 'true' + name: Send SonarCloud results + id: send_sonar_results + shell: pwsh + run: | + .\.sonar\scanner\dotnet-sonarscanner end ` + /d:sonar.login="${{ secrets.SONAR_TOKEN }}" + env: + GITHUB_TOKEN: ${{ secrets.SONAR_GITHUB_TOKEN }} + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + + cd: + # "This job will execute when the workflow is triggered on a 'push event', the target branch is 'master' and the commit is intended to be a release." + if: needs.ci.outputs.is_push_to_master == 'true' && needs.ci.outputs.new_version != '' + needs: ci + runs-on: windows-latest + env: + NEW_VERSION: ${{ needs.ci.outputs.new_version }} + NEW_TAG: v${{ needs.ci.outputs.new_version }} + steps: + - name: Checkout repository + id: checkout_repo + uses: actions/checkout@v2 + + - name: Download and extract MSIX package + id: dl_package_artifact + uses: actions/download-artifact@v2 + with: + name: Build artifacts + path: Artifacts/ + + - name: Publish *.nupkg on NuGet + id: publish_nuget + continue-on-error: true + uses: rohith/publish-nuget@v2 + with: + PROJECT_FILE_PATH: 'project.csproj' + TAG_COMMIT: true + NUGET_KEY: ${{ secrets.NUGET_TOKEN }} + PACKAGE_NAME: Project.${{ env.NEW_VERSION }}.nupkg + + - name: Create and publish release + id: create_release + uses: actions/create-release@v1 + with: + tag_name: ${{ env.NEW_TAG }} + release_name: Release ${{ env.NEW_TAG }} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Upload release asset + id: upload_release_asset + uses: actions/upload-release-asset@v1 + with: + upload_url: ${{ steps.create_release.outputs.upload_url }} + asset_path: Artifacts/ + asset_name: artifact.zip + asset_content_type: application/zip + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + +# Built with ❤ by [Pipeline Foundation](https://pipeline.foundation) \ No newline at end of file diff --git a/CI-CD_DOCUMENTATION.md b/CI-CD_DOCUMENTATION.md new file mode 100644 index 000000000..f14427b5d --- /dev/null +++ b/CI-CD_DOCUMENTATION.md @@ -0,0 +1,354 @@ +# !_PROJECT_NAME_! CI/CD documentation + +* after merging the PR, the first run of the main workflow will not complete successfully, because it requires specific setup explained in this documentation + +## *. Set up TestSpace + +The TestSpace tool is used to display the unit test results, that are generated by the internal testing sequence of the project. + +1. Go to https://www.testspace.com/ + +2. Click on the "Start for free" button + +3. Click on the "Get started" button, under the "Open" plan box + +4. Go to the bottom of the page, under "Pricing and setup" choose the "Free" option on the left and click on "Install it for free" button on the right +* the "Free" option is for personal accounts +* the "Open" option is for organization accounts + +5. Click on the "Complete order and begin installation" button + +6. Select which repositories to work with this TestSpace installation +* select "Only select repositories" for better overall control +* from the dropdown, select the repositories you would like to see on the TestSpace dashboard + +7. Enter your password, if prompt + +8. You are going to be re-directed to the TestSpace dashboard + +9. At the top right corner, click on your account name and choose **Edit** from the dropdown menu + +10. Copy the value from the input field under the **Access Token** label + +11. Go to GitHub and enter your repo + +12. In the **Settings** tab, go to **Secrets**, add a new secret with the name **TESTSPACE_TOKEN** and paste the value you copied in step 10 + +- the TestSpace token is optional for public repos, but we've made it part of the implementation, to give the owner of the repo additional security + +13. Go back to your TestSpace dashboard + +14. On the righthand side at the top, click on "+ New Project" button + +15. Select the GitHub repo you would like to display and click "OK" + +16. Click on the name of the project + +17. As soon as a workflow is ran successfully for the connected GitHub project, you would be able to see the results listed here +* "space" refers to a GitHub repo branch (master, feature/new-feature etc.) and each branch is displayed as a separate entity + +
+ +## *. Set up SonarCloud +### SonarCloud is a cloud-based code quality and security service + +#### Create SonarCloud project + +- Go to https://sonarcloud.io/ + +- Click the "Log in" button and create a new account or connect with GitHub account (recommended) + +- At the top right corner click the "+" sign + +- From the dropdown select "Create new Organization" + +- Click the "Choose an organization on GitHub" button + +- Select an account for the organization setup + +- On Repository Access select "Only select repositories" and select the project and click the "Save" button + +- On the "Create organization page" don't change the Key and click "Continue" + +- Select the Free plan then click the "Create Organization" button to finalize the creation of the Organization + +#### Configure SonarCloud project + +- At the top right corner click the "+" sign and select "Analyze new project" + +- Select the project and click the "Set Up" button in the box on the right + +- Under "Choose your analysis method" click "With GitHub Actions" and **keep the following page open** + +- [Create a new PAT with **repo_deployment** and **read:packages** permissions](#how-to-create-a-pat) and copy the value of the generated token + +- In the project's GitHub repository, go to the **Settings** tab -> Secrets + +- Click on **New Repository secret** and create a new secret with the name **SONAR_GITHUB_TOKEN** and the token you just copied as the value + +- Create another secret with the two values from the SonarCloud page you kept open, which you can close after completing this step + +![SonarCloud_1](/CI-CD_DOCUMENTATION/SonarCloud_1.png) + +- [Run the "!_PROJECT_NAME_! CI/CD Pipeline" workflow manually](#2-run-workflow-manually) + +#### Set Quality Gate + +- After the "!_PROJECT_NAME_! CI/CD Pipeline" workflow has executed successfully, go to https://sonarcloud.io/projects and click on the project + +- In the alert bar above the results, click the "Set new code definition" button and select "Previous version" (notice the "New Code definition has been updated" alert at the top) + +- The Quality Gate will become active as soon as the next SonarCloud scan completes successfully + +
+ +## *. Set up NuGet + +NuGet is a package manager and is responsible for downloading, installing, updating, and configuring software in your system. From the term software we don't mean end users software like Microsoft Word or Notepad 2, etc. but pieces of software, which you want to use in the project, assembly references. + +1. Go to https://www.nuget.org/ . + +2. Log in or create your account. + +3. From the dropdown in the right top corner (with your account name on it) choose **API Keys**. + +4. Create a new token by clicking the "+ Create" button. + +5. Set the **Key name** to "NUGET TOKEN" so it's compatible with your workflow. + +6. On **Package Owner** choose your username from the dropdown. + +7. To select which packages to associate with a key, use a glob pattern, select individual packages, or both. + +8. Set your package name in the **Glob Pattern** field. + +9. Now you have to set the token as a **secret** in GitHub in order to make it work. + +### Creating encrypted secrets for a repository + +1. On GitHub, navigate to the main page of the repository. + +2. Under your repository name, click Settings. + +3. Repository settings button. + +4. In the left sidebar, click Secrets. + +5. Click New repository secret. + +6. Type the name "NUGET_TOKEN" as a name of your secret. + +7. Enter the value for your secret. + +8. Click Add secret. + **You are all set up** + +
+ +## *. Run workflow manually + +Once you've set up all the steps above correctly, you should be able to successfully complete a manual execution of the "!_PROJECT_NAME_! CI/CD Pipeline" workflow. + + 1. Go to the project's GitHub repository and click on the **Actions** tab + + 2. From the "Workflows" list on the left, click on "!_PROJECT_NAME_! CI/CD Pipeline" + + 3. On the right, next to the "This workflow has a workflow_dispatch event trigger" label, click on the "Run workflow" dropdown, make sure the default branch is selected (if not manually changed, should be main or master) in the "Use workflow from" dropdown and click the "Run workflow" button + +![Actions_workflow_dispatch](/ScreenShots/CI-CD_DOCUMENTATION/Actions_workflow_dispatch.png) + + 4. Once the workflow run has completed successfully, move on to the next step of the documentation + +NOTE: **screenshots are only exemplary** + +
+ +## *. Create a TestSpace badge + +Create a TestSpace badge in your README.md to quickly reference your internal test results + +**NOTE: this step is available only after the manually executed workflow run has completed successfully** + +1. Go to https://www.testspace.com/ + +2. Click "Sign in" at the right corner + +3. In the "Project Listing", find the project and click on the "master" space + +4. Click on the badge, that is above the data table + +5. Click "Embed link" + +6. From the dropdown select "Markdown" + +7. Copy the contents below + +8. Go to the GitHub repo of the project + +9. Open the README.md + +10. Paste the Markdown code at your preferred place + +
+ +## *. Set up Dependabot + +Dependabot is a GitHub native security tool that goes through the dependencies in the project and creates alerts, and PRs with updates when a new and/or non-vulnerable version is found. + +- for PRs with version updates, this pipeline comes pre-configured for all current dependency sources in the project, so at "Insights" tab -> "Dependency graph" -> "Dependabot", you should be able to see all tracked sources of dependencies, when they have been checked last and view a full log of the last check + +![Dependabot_tab](/Docs/CI-CD_DOCUMENTATION/Dependabot_tab.png) + +![Dependabot_log_page](/Docs/CI-CD_DOCUMENTATION/Dependabot_log_page.png) + +### Set up security alerts and updates +##### - GitHub, through Dependabot, also natively offers a security check for vulnerable dependencies + +1. Go to the project's GitHub repository and click on the **Settings** tab + +2. Go to **Security & analysis** section + +3. Click "Enable" for both "Dependabot alerts" and "Dependabot security updates" + +- By enabling "Dependabot alerts", you would be notified for any vulnerable dependencies in the project. At "Security" tab -> "Dependabot alerts", you can manage all alerts. By clicking on an alert, you would be able to see a detailed explanation of the vulnerability and a viable solution. + +![Dependabot_alerts_page](/Docs/CI-CD_DOCUMENTATION/Dependabot_alerts_page.png) + +![Dependabot_alert_page](/Docs/CI-CD_DOCUMENTATION/Dependabot_alert_page.png) + +- By enabling "Dependabot security updates", you authorize Dependabot to create PRs specifically for **security updates** + +![Dependabot_PRs](/Docs/CI-CD_DOCUMENTATION/Dependabot_PRs.png) + +### Set up Dependency graph +##### - The "Dependency graph" option should be enabled by default for all public repos, but in case it isn't: + +1. Go to "Settings" tab of your repo + +2. Go to "Security&Analysis" section + +3. Click "Enable" for the "Dependency graph" option + +- this option enables the "Insights" tab -> "Dependency graph" section -> "Dependencies" tab, in which all the dependencies for the project are listed, under the different manifests they are included in + +![Dependabot_dependency_graph](/Docs/CI-CD_DOCUMENTATION/Dependabot_dependency_graph.png) + +NOTE: **screenshots are only exemplary** + +
+ +## *. CodeQL + +CodeQL is GitHub's own industry-leading semantic code analysis engine. CodeQL requires no setup, because it comes fully pre-configured by us. + +To activate it and see its results, only a push commit or a merge of a PR to the default branch of your repository, is required. + +We've also configured CodeQL to run on schedule, so every day at 8:00AM UTC, it automatically tests the code. + +- you can see the results here at **Security** tab -> **Code scanning alerts** -> **CodeQL**: + +![CodeQL_results](/ScreenShots/CI-CD_DOCUMENTATION/CodeQL_results.png) + +- on the page of each result, you can see an explanation of what the problem is and also one or more solutions: + +![CodeQL_alert_page](/ScreenShots/CI-CD_DOCUMENTATION/CodeQL_alert_page.png) + +
+ +## *. How to create a release + +The "Release sequence" encompasses creating a release in your GitHub repo and adding the necessary files to it. + +Files added are: +- Source code (zip) +- Source code (tar.gz) + +Note: **not every commit to your master branch creates a release** + +Follow these instructions for any commit (push or PR merge) to your master branch, you would like to create a release. + +Whenever you would like to trigger the "Create release" sequence, you would need one of three keywords at the start of your commit title. Each of the three keywords corresponds to a number in your release version i.e. v1.2.3. The release versioning uses the ["Conventional Commits" specification](https://www.conventionalcommits.org/en/v1.0.0/): + +* "fix: ..." - this keyword corresponds to the last number v1.2.**3**, also known as PATCH; +* "feat: ..." - this keyword corresponds to the middle number v1.**2**.3, also known as MINOR; +* "perf: ..." - this keyword corresponds to the first number v**1**.2.3, also known as MAJOR. In addition, to trigger a MAJOR release, you would need to write "BREAKING CHANGE: ..." in the description of the commit, with an empty line above it to indicate it is in the