<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Petr Švihlík</title>
    <description>The latest articles on Forem by Petr Švihlík (@petrsvihlik).</description>
    <link>https://forem.com/petrsvihlik</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F134598%2F81603489-c6fa-4933-b93a-f04adc4f6657.png</url>
      <title>Forem: Petr Švihlík</title>
      <link>https://forem.com/petrsvihlik</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/petrsvihlik"/>
    <language>en</language>
    <item>
      <title>Lessons Learned: Migrating from AppVeyor to GitHub Actions</title>
      <dc:creator>Petr Švihlík</dc:creator>
      <pubDate>Fri, 05 Mar 2021 21:26:01 +0000</pubDate>
      <link>https://forem.com/petrsvihlik/lessons-learned-migrating-from-appveyor-to-github-actions-1gh8</link>
      <guid>https://forem.com/petrsvihlik/lessons-learned-migrating-from-appveyor-to-github-actions-1gh8</guid>
      <description>&lt;p&gt;The third article about GitHub Actions in a row? One must be thinking I am up to something. And they'd be right. Over the past week, I migrated more than 10 repositories from AppVeyor to GitHub Actions.&lt;/p&gt;

&lt;p&gt;"Why?" you are asking? My team did extensive research on which CI provider to use next as we started hitting some limits with Travis and we weren't happy with how scattered our build procedures were over multiple CIs. We've been using a different CI provider for nearly every stack. Considering the number of SDKs that we provide (JavaScript, .NET, PHP, Java, Swift, Android, Ruby), it should come as no surprise that we wanted to reduce the maintenance required. And we decided that GitHub Actions are simply the best to fit our needs, closely followed by CircleCI.&lt;/p&gt;

&lt;p&gt;Over the course of the migration, I ran into multiple issues. Most of them are pretty easy to solve (at least if you pay attention to the documentation) so the intention of this article is not to give you solutions to simple problems but rather to give you an overview of what types of problems you might face along the way and how much time (based on the knowledge of your codebase) is required to convert a repo to GH Actions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Linux vs. Windows
&lt;/h2&gt;

&lt;p&gt;By default, all actions run on Linux by default. And if possible, it's a good idea to keep it that way as it's the least resource-demanding option (which you'll also learn if you check out &lt;a href="https://docs.github.com/en/github/setting-up-and-managing-billing-and-payments-on-github/about-billing-for-github-actions"&gt;the minute multiplier&lt;/a&gt; on the pricing page).&lt;br&gt;
Another reason to stick to Linux is that, if you use Windows for local development, you have verification of cross-platform compatibility. Obviously, you can also use &lt;a href="https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idstrategymatrix"&gt;the strategy matrix&lt;/a&gt; to run multiple platforms at once.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;PRO TIP: For more complex build scripts, it's a good idea to take an iterative approach and first migrate to &lt;code&gt;windows-latest&lt;/code&gt;, make sure everything works, and then switch to &lt;code&gt;ubuntu-latest&lt;/code&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Case sensitivity
&lt;/h3&gt;

&lt;p&gt;As you know, on Linux, the file system is case-sensitive. So beware of files like &lt;code&gt;Directory.Build.props&lt;/code&gt;. Yeah, &lt;code&gt;Directory.build.props&lt;/code&gt; is something completely different.&lt;/p&gt;
&lt;h3&gt;
  
  
  Directory separator
&lt;/h3&gt;

&lt;p&gt;Some of your code or unit tests may depend on the file system. In that case, make sure you assemble all paths using &lt;code&gt;Path.Combine()&lt;/code&gt; or &lt;code&gt;Path.DirectorySeparatorChar&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  Quotes
&lt;/h3&gt;

&lt;p&gt;Some shell commands require different syntax than on Windows. A nice example is &lt;code&gt;dotnet nuget push&lt;/code&gt; which requires the first parameter to be &lt;a href="https://github.com/dotnet/docs/issues/7146#issuecomment-604057410"&gt;enclosed in apostrophes&lt;/a&gt; on Linux to properly parse the wildcard when publishing multiple NuGet packages.&lt;/p&gt;
&lt;h3&gt;
  
  
  Running PowerShell
&lt;/h3&gt;

&lt;p&gt;Yes, you can run PowerShell Core on Linux, which is absolutely brilliant if you're migrating from Windows and want to keep just one set of scripts!&lt;br&gt;
The only thing you need to do is to &lt;a href="https://docs.github.com/en/actions/reference/encrypted-secrets#example-using-powershell"&gt;specify &lt;code&gt;pwsh&lt;/code&gt;&lt;/a&gt; as the type of your shell.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;shell&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;pwsh&lt;/span&gt;
    &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;SUPER_SECRET&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.SuperSecret }}&lt;/span&gt;
    &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
      &lt;span class="s"&gt;example-command "$env:SUPER_SECRET"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Just notice the different syntax for accessing environment variables - &lt;code&gt;$SUPER_SECRET&lt;/code&gt; in Bash vs &lt;code&gt;$env:SUPER_SECRET&lt;/code&gt; in PowerShell and don't mistake the &lt;code&gt;pwsh&lt;/code&gt; moniker with &lt;code&gt;powershell&lt;/code&gt; which is the non-Core version of PowerShell supported only on Windows. &lt;br&gt;
See the shell platform compatibility matrix &lt;a href="https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#using-a-specific-shell"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  WSL is your best friend
&lt;/h3&gt;

&lt;p&gt;If you don't have the Windows Subsystem for Linux installed, or worse, you don't know what it is, I encourage you to head to the &lt;a href="https://docs.microsoft.com/en-us/windows/wsl/install-win10"&gt;docs&lt;/a&gt; and be amazed. Being able to execute any Linux command on Windows is, in my opinion, one of the most brilliant engineering achievements of Microsoft in the past years.&lt;br&gt;
Personally, I'm a fan of &lt;a href="https://www.microsoft.com/en-us/p/opensuse-leap-152/9mzd0n9z4m4h"&gt;openSUSE&lt;/a&gt; but you can choose from many distros.&lt;/p&gt;
&lt;h3&gt;
  
  
  HttpClient
&lt;/h3&gt;

&lt;p&gt;The HTTP request and response objects look slightly different when you serialize them on Windows and Linux. If, for instance, your unit tests heavily depend on serialized responses, you might want to address this issue or choose to run the workflow on &lt;code&gt;windows-latest&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  &lt;code&gt;.csproj&lt;/code&gt; patching
&lt;/h2&gt;

&lt;p&gt;AppVeyor offers features such as &lt;a href="https://www.appveyor.com/docs/build-configuration/#net-core-csproj-files-patching"&gt;&lt;code&gt;.csproj&lt;/code&gt; files patching&lt;/a&gt; and &lt;a href="https://www.appveyor.com/docs/build-configuration/#assemblyinfo-patching"&gt;&lt;code&gt;AssemblyInfo&lt;/code&gt; patching&lt;/a&gt;.&lt;br&gt;
These features are not available in GitHub Actions and the easiest way to overcome this is to use parameters that the .NET CLI offers &lt;a href="https://github.com/Kentico/kontent-delivery-sdk-net/blob/ec9ad537199f88a2f88a7c6545a7aeec8d7cbe39/.github/workflows/release.yml#L24"&gt;&lt;code&gt;dotnet build -p:Version=1.2.3&lt;/code&gt;&lt;/a&gt; and &lt;a href="https://github.com/Kentico/kontent-delivery-sdk-net/blob/ec9ad537199f88a2f88a7c6545a7aeec8d7cbe39/.github/workflows/release.yml#L30"&gt;&lt;code&gt;dotnet pack -p:PackageVersion=1.2.3&lt;/code&gt;&lt;/a&gt; or &lt;a href="https://github.com/Kentico/kontent-boilerplate-net/blob/8afced3a3d537e39372e7e84f49921d3057f5a33/.github/workflows/release.yml#L30"&gt;&lt;code&gt;dotnet pack -p:NuspecProperties="Version=1.2.3"&lt;/code&gt;&lt;/a&gt; if you use a &lt;code&gt;.nuspec&lt;/code&gt; file.&lt;/p&gt;

&lt;p&gt;This is how it can look like in a real workflow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Extract version from tag&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;get_version&lt;/span&gt;
  &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;battila7/get-version-action@v2&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Build&lt;/span&gt;
  &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dotnet build /p:Version="${{ steps.get_version.outputs.version-without-v }}"&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Pack&lt;/span&gt;
  &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dotnet pack /p:PackageVersion="${{ steps.get_version.outputs.version-without-v }}"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Symbols
&lt;/h2&gt;

&lt;p&gt;If you want to enable &lt;a href="https://github.com/dotnet/sourcelink"&gt;Source Link&lt;/a&gt; for the consumers of your NuGets, make sure you turn on deterministic builds using &lt;code&gt;dotnet build /p:ContinuousIntegrationBuild=true&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using custom actions
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;GitHub Actions use &lt;a href="https://semver.org/"&gt;Semantic Versioning&lt;/a&gt; so you don't have to include the minor and patch version (&lt;code&gt;codecov/codecov-action@v1.2.3&lt;/code&gt;) when referencing them. It's actually a good idea to use just the major version (&lt;code&gt;codecov/codecov-action@v1&lt;/code&gt;) because you get any potential fixes free of charge.&lt;/li&gt;
&lt;li&gt;You can use actions that are not published on the Marketplace. For instance, you can create your own actions and refer the them by &lt;code&gt;gh_username/repo_with_action@version&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Creating custom actions
&lt;/h2&gt;

&lt;p&gt;Two things I learned about creating custom GitHub Actions that you might find asking yourself too:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It's impossible to nest GitHub Actions (create GitHub Actions containing other GitHub Actions). However, according to &lt;a href="https://github.com/actions/runner/issues/646#issuecomment-788373907"&gt;this&lt;/a&gt;, it seems the topic of templating is about to be addressed this year.&lt;/li&gt;
&lt;li&gt;Hosting multiple actions in a single repo IS &lt;a href="https://stackoverflow.com/questions/59259783/how-to-combine-two-action-yml-files-in-one-repo"&gt;possible&lt;/a&gt;. You then refer to such actions as  &lt;code&gt;gh_username/repo_with_actions/action1@version&lt;/code&gt;. I'm not sure if it's possible to &lt;a href="https://docs.github.com/en/actions/creating-actions/publishing-actions-in-github-marketplace"&gt;publish&lt;/a&gt; them to the marketplace in this setup though.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;And that's it. There we no other issues during the migration. That was a great surprise for me. Learning GitHub Actions and migrating most of the repos took me a single weekend. Everything was documented properly, I found many examples, and didn't run to any bugs!&lt;/p&gt;

&lt;p&gt;Good job GitHub Actions! &lt;/p&gt;

</description>
      <category>githubactions</category>
      <category>appveyor</category>
      <category>ci</category>
      <category>dotnet</category>
    </item>
    <item>
      <title>Using Environment Protection Rules to Secure Secrets When Building External Forks with pull_request_target 🤐</title>
      <dc:creator>Petr Švihlík</dc:creator>
      <pubDate>Fri, 05 Mar 2021 19:13:05 +0000</pubDate>
      <link>https://forem.com/petrsvihlik/using-environment-protection-rules-to-secure-secrets-when-building-external-forks-with-pullrequesttarget-hci</link>
      <guid>https://forem.com/petrsvihlik/using-environment-protection-rules-to-secure-secrets-when-building-external-forks-with-pullrequesttarget-hci</guid>
      <description>&lt;p&gt;Building pull requests from forked repositories with GitHub Actions can be a bit tricky when it comes to secrets. As per the &lt;a href="https://docs.github.com/en/actions/reference/events-that-trigger-workflows#pull_request" rel="noopener noreferrer"&gt;documentation&lt;/a&gt;, with the exception of &lt;code&gt;GITHUB_TOKEN&lt;/code&gt;, secrets are not passed to the runner when a workflow is triggered from a forked repository. This is to prevent the automatic execution of untrusted code that may be contained within the forked repo.&lt;br&gt;
In other words, we can't use the &lt;code&gt;pull_request&lt;/code&gt; trigger if there are secrets that need to be involved in the workflow. &lt;/p&gt;

&lt;p&gt;Fortunately, &lt;code&gt;pull_request_target&lt;/code&gt; &lt;a href="https://github.blog/2020-08-03-github-actions-improvements-for-fork-and-pull-request-workflows/#improvements-for-public-repository-forks" rel="noopener noreferrer"&gt;comes to rescue&lt;/a&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;...the &lt;code&gt;pull_request_target&lt;/code&gt; event behaves in an almost identical way to the pull_request event with the same set of filters and payload. However, instead of running against the workflow and code from the merge commit, the event runs against the workflow and code from the base of the pull request. This means the workflow is running from a trusted source...&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Ok, so now we have access to secrets but we're building the wrong code. &lt;/p&gt;

&lt;p&gt;Apparently, there are some people who try to overcome this problem with the following code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;#INSECURE&lt;/span&gt;
&lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v2&lt;/span&gt;
  &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ github.event.pull_request.head.sha }}&lt;/span&gt; &lt;span class="c1"&gt;# Check out the code of the PR&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is highly discouraged and rightfully so, as it's insecure if no other security measures are taken.&lt;/p&gt;

&lt;p&gt;In GitHub's own article &lt;a href="https://securitylab.github.com/research/github-actions-preventing-pwn-requests" rel="noopener noreferrer"&gt;Preventing pwn requests&lt;/a&gt;, the author - &lt;a href="https://blog.devsecurity.eu/" rel="noopener noreferrer"&gt;Jaroslav Lobačevski&lt;/a&gt; - suggests using the &lt;code&gt;pull_request_target&lt;/code&gt; in combination with a condition checking whether the PR is labeled &lt;code&gt;safe to test&lt;/code&gt;. Like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;    &lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Build and test&lt;/span&gt;
        &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
        &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;contains(github.event.pull_request.labels.*.name, 'safe to test')&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a perfectly valid approach but I think I may have found a better and more convenient way of preventing unauthorized code execution during the build of forks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Environment protection rules
&lt;/h2&gt;

&lt;p&gt;Just a couple of months ago, GitHub introduced the &lt;a href="https://github.blog/changelog/2020-12-15-github-actions-environments-environment-protection-rules-and-environment-secrets-beta/" rel="noopener noreferrer"&gt;Environment protection rules&lt;/a&gt;. The main intent of this feature is to protect environments during deployments by applying rules that will pause the execution of a workflow until given conditions are met - e.g. a human approval is given, the certain time elapsed, etc. But it can serve any general purpose. In our case, we'll use it to protect our repository secrets and to prevent the execution of untrusted code.&lt;/p&gt;

&lt;h3&gt;
  
  
  Protecting the build
&lt;/h3&gt;

&lt;p&gt;Let's start with adding a dummy environment called "Integrate Pull Request" that will require human approval. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7804n29q151kjjtx01gb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7804n29q151kjjtx01gb.png" alt="Integrate Pull Request Environment"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Our main build procedure will be associated with this environment and preceded by a dummy workflow step &lt;code&gt;approve&lt;/code&gt; that will kick off the workflow and inform the author of the pull request that a review is necessary before proceeding any further.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;pull_request_target&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;master&lt;/span&gt; &lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;approve&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="c1"&gt;# First step&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;

    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Approve&lt;/span&gt;
      &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;echo For security reasons, all pull requests need to be approved first before running any automated CI.&lt;/span&gt;

  &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="c1"&gt;# Second step&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;

    &lt;span class="na"&gt;needs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;approve&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt; &lt;span class="c1"&gt;# Require the first step to finish&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Integrate Pull Request&lt;/span&gt; &lt;span class="c1"&gt;# Our dummy environment&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This way the workflow won't proceed until someone reviews the submitted code and therefore, we can safely check out the &lt;code&gt;${{ github.event.pull_request.head.sha }}&lt;/code&gt; in the next step and build it. So the build is executed using a trusted workflow from the base of the PR and the actual code of the PR.&lt;/p&gt;

&lt;h3&gt;
  
  
  How it works in practice
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Someone submits a pull request and a workflow is triggered and immediately paused&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Facdm7xpymeukwqyldpg5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Facdm7xpymeukwqyldpg5.png" alt="Build is waiting for human approval"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The reviewer or a group of reviewers receive an e-mail notification&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Figfbcrhtmyz8nd8mc6z1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Figfbcrhtmyz8nd8mc6z1.png" alt="Email notification about a pending review"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The reviewer clicks the link, navigates to the repo, verifies that the submitted PR doesn't contain any unwanted code, and finally gives an approval&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fax8ttre5tamgqq2licbg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fax8ttre5tamgqq2licbg.png" alt="Approve the workflow step"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The build proceeds&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffrg9744w7z66zq4e93fz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffrg9744w7z66zq4e93fz.png" alt="Build is approved"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;All approvals are audited&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fylb6vipf2gi7dtouzu3u.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fylb6vipf2gi7dtouzu3u.png" alt="Approval audit log"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  A few words on Codecov
&lt;/h2&gt;

&lt;p&gt;While implementing this workflow, I ran into an issue where the Codecov action, similarly to the &lt;a href="https://github.com/marketplace/actions/checkout" rel="noopener noreferrer"&gt;GitHub Checkout Action&lt;/a&gt;, is by default &lt;a href="https://github.com/codecov/codecov-action/issues/155" rel="noopener noreferrer"&gt;pointed to the PR's Base&lt;/a&gt; and needs to be overridden. This can be achieved by:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Codecov&lt;/span&gt;
  &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;codecov/codecov-action@v1&lt;/span&gt;
  &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;token&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.CODECOV_TOKEN }}&lt;/span&gt;
    &lt;span class="na"&gt;override_pr&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ github.event.number }}&lt;/span&gt;
    &lt;span class="na"&gt;override_commit&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ github.event.pull_request.head.sha }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To get rid of the following warning message:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Issue detecting commit SHA. Please run actions/checkout with fetch-depth &amp;gt; 1 or set to 0&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;make sure to also set &lt;code&gt;fetch-depth&lt;/code&gt; of the Checkout action to 2.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v2&lt;/span&gt;      
  &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;fetch-depth&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Note: I found an alternative approach using a conditional &lt;a href="https://github.com/Cookie-AutoDelete/Cookie-AutoDelete/actions/runs/274505391/workflow" rel="noopener noreferrer"&gt;workflow step&lt;/a&gt; and a &lt;a href="https://github.com/Cookie-AutoDelete/Cookie-AutoDelete/blob/3.X.X-Branch/.github/codecov_alt.sh" rel="noopener noreferrer"&gt;shell script&lt;/a&gt;. But overriding the commit SHA is far easier.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;The advantage of this approach is that you can assign a group of reviewers who'll receive an email notification about the pending workflow and can review and approve it in a single click.&lt;br&gt;
The process is, in my opinion, more transparent thanks to all events being logged and semantically more correct than using labels.&lt;/p&gt;

&lt;p&gt;If you want to explore the whole workflow, feel free to check out my project &lt;a href="https://github.com/petrsvihlik/WopiHost/blob/master/.github/workflows/pull_request.yml" rel="noopener noreferrer"&gt;WopiHost&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;To learn more about the specifics of &lt;code&gt;pull_request_target&lt;/code&gt; head to the &lt;a href="https://docs.github.com/en/actions/reference/events-that-trigger-workflows#pull_request_target" rel="noopener noreferrer"&gt;documentation&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>githubactions</category>
      <category>pullrequest</category>
      <category>security</category>
      <category>ci</category>
    </item>
    <item>
      <title>Reporting .NET 5 XUnit Code Coverage in Codecov via GitHub Actions and Coverlet</title>
      <dc:creator>Petr Švihlík</dc:creator>
      <pubDate>Fri, 05 Mar 2021 14:56:10 +0000</pubDate>
      <link>https://forem.com/kontent_ai/reporting-net-5-xunit-code-coverage-in-codecov-via-github-actions-and-coverlet-4h5i</link>
      <guid>https://forem.com/kontent_ai/reporting-net-5-xunit-code-coverage-in-codecov-via-github-actions-and-coverlet-4h5i</guid>
      <description>&lt;p&gt;You might remember my &lt;a href="https://dev.to/petrsvihlik/running-net-core-3-xunit-code-coverage-in-appveyor-using-opencover-and-codecov-1n7p"&gt;earlier post&lt;/a&gt; where I described how to set up code coverage reporting for .NET Core 3 using AppVeyor + OpenCover + Codecov.&lt;/p&gt;

&lt;p&gt;Time has moved on, .NET 5 has arrived, and the best practices have changed, so let's see how to collect code coverage for .NET projects in 2021.&lt;/p&gt;

&lt;h2&gt;
  
  
  The stack
&lt;/h2&gt;

&lt;p&gt;For some time, I have been intrigued by the &lt;code&gt;coverlet.collector&lt;/code&gt; that you can find in any new XUnit &lt;code&gt;.csproj&lt;/code&gt;. What it is? How can I use it? Can I replace OpenCover with it?&lt;/p&gt;

&lt;p&gt;Also, I started noticing people migrating to GitHub Actions and I didn't have any experience with it and kinda felt that I'm missing the train. This observation has been confirmed recently in the &lt;a href="https://about.codecov.io/resource/2020-state-of-open-source-code-coverage/" rel="noopener noreferrer"&gt;2020 State of Open Source Code Coverage&lt;/a&gt; report where GitHub Actions scored the #1 fastest growing CI used with Codecov (and also the #1 in absolute numbers).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://about.codecov.io/resource/2020-state-of-open-source-code-coverage/#sos-23" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fahh5qegex307x5pf11c0.jpg" alt="2020 State of Open Source Code Coverage"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I was thinking I could modernize the stack that I use for .NET projects both at work and for my personal projects. Of the three tools that I've been using previously, I'll be keeping just Codecov (because it's awesome ❤). Let's get to it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Generating coverage
&lt;/h2&gt;

&lt;p&gt;I like to use the &lt;code&gt;dotnet&lt;/code&gt; CLI for as many tasks as possible. The good news is that &lt;code&gt;coverlet&lt;/code&gt; offers deep integration with &lt;code&gt;msbuild&lt;/code&gt;. &lt;br&gt;
This is how you can generate code coverage for the whole solution:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;dotnet&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;/p:CollectCoverage&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;true&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;/p:CoverletOutputFormat&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;opencover&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The command will generate &lt;code&gt;coverage.opencover.xml&lt;/code&gt; files in all your test projects (if you run &lt;code&gt;dotnet test&lt;/code&gt; on the solution level).&lt;/p&gt;

&lt;p&gt;All you need to do to make the code coverage-related &lt;code&gt;/p:&lt;/code&gt; switches work is to install also the &lt;a href="https://www.nuget.org/packages/coverlet.msbuild/" rel="noopener noreferrer"&gt;&lt;code&gt;coverlet.msbuild&lt;/code&gt;&lt;/a&gt; NuGet package. So your &lt;code&gt;.csproj&lt;/code&gt;s will look something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;PackageReference&lt;/span&gt; &lt;span class="na"&gt;Include=&lt;/span&gt;&lt;span class="s"&gt;"coverlet.collector"&lt;/span&gt; &lt;span class="na"&gt;Version=&lt;/span&gt;&lt;span class="s"&gt;"3.0.3"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;IncludeAssets&amp;gt;&lt;/span&gt;runtime; build; native; contentfiles; analyzers; buildtransitive&lt;span class="nt"&gt;&amp;lt;/IncludeAssets&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;PrivateAssets&amp;gt;&lt;/span&gt;all&lt;span class="nt"&gt;&amp;lt;/PrivateAssets&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/PackageReference&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;PackageReference&lt;/span&gt; &lt;span class="na"&gt;Include=&lt;/span&gt;&lt;span class="s"&gt;"coverlet.msbuild"&lt;/span&gt; &lt;span class="na"&gt;Version=&lt;/span&gt;&lt;span class="s"&gt;"3.0.3"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;PrivateAssets&amp;gt;&lt;/span&gt;all&lt;span class="nt"&gt;&amp;lt;/PrivateAssets&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;IncludeAssets&amp;gt;&lt;/span&gt;runtime; build; native; contentfiles; analyzers; buildtransitive&lt;span class="nt"&gt;&amp;lt;/IncludeAssets&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/PackageReference&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Make sure to leave in the &lt;code&gt;/p:CoverletOutputFormat=opencover&lt;/code&gt; switch as this is the format that's accepted by Codecov. &lt;/p&gt;

&lt;p&gt;For more advanced configuration, I recommend reading the documentation on &lt;a href="https://github.com/coverlet-coverage/coverlet/blob/master/Documentation/MSBuildIntegration.md" rel="noopener noreferrer"&gt;Coverlet's integration with MSBuild&lt;/a&gt;. This is especially useful when your solution or projects don't stick to naming conventions and you need to do some extra filtering, etc.&lt;/p&gt;

&lt;h2&gt;
  
  
  Integrating into GitHub Actions
&lt;/h2&gt;

&lt;p&gt;Plugging the &lt;code&gt;dotnet test&lt;/code&gt; into the CI pipeline is just a matter of creating a new workflow step. No matter if you use the &lt;code&gt;ubuntu-latest&lt;/code&gt; or &lt;code&gt;windows-latest&lt;/code&gt; runner, it works flawlessly.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Restore dependencies&lt;/span&gt;
  &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dotnet restore&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Build&lt;/span&gt;
  &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dotnet build --no-restore /p:ContinuousIntegrationBuild=true&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Test&lt;/span&gt;
  &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dotnet test --no-build --verbosity normal /p:CollectCoverage=true /p:CoverletOutputFormat=opencover&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Collecting coverage by Codecov
&lt;/h2&gt;

&lt;p&gt;The last step is to upload the test coverage to Codecov. You can find an official &lt;a href="https://github.com/marketplace/actions/codecov" rel="noopener noreferrer"&gt;Codecov GitHub Action&lt;/a&gt; on the marketplace which makes the process super easy - no need to install the Codecov CLI, etc.&lt;/p&gt;

&lt;p&gt;Just add the following lines to your workflow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Codecov&lt;/span&gt;
  &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;codecov/codecov-action@v1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Codecov will search through the folder structure of your project and discover your coverage reports based on conventions. You don't even have to include the &lt;code&gt;CODECOV_TOKEN&lt;/code&gt; for public repos.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;As you can see, this approach is much more convenient than &lt;a href="https://dev.to/petrsvihlik/running-net-core-3-xunit-code-coverage-in-appveyor-using-opencover-and-codecov-1n7p"&gt;the one I was using before&lt;/a&gt; - downloading and installing tools, running PowerShell scripts, etc.&lt;/p&gt;

&lt;p&gt;If you stick to standard .NET naming and folder structure conventions, setting up code coverage is actually very easy nowadays.&lt;/p&gt;

&lt;p&gt;To see the whole workflow in action, head to some of our &lt;a href="https://github.com/Kentico/kontent-delivery-sdk-net/actions/workflows/integrate.yml" rel="noopener noreferrer"&gt;SDK repos&lt;/a&gt;. You can also explore how we do releases using GitHub Actions. All code is in the &lt;a href="https://github.com/Kentico/kontent-delivery-sdk-net/tree/master/.github/workflows" rel="noopener noreferrer"&gt;&lt;code&gt;workflows folder&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In my next article, I'll cover how to &lt;a href="https://dev.to/petrsvihlik/using-environment-protection-rules-to-secure-secrets-when-building-external-forks-with-pullrequesttarget-hci"&gt;secure secrets when building pull requests from forks&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>net5</category>
      <category>coverlet</category>
      <category>codecov</category>
      <category>githubactions</category>
    </item>
    <item>
      <title>Running .NET Core 3 XUnit Code Coverage in AppVeyor Using OpenCover and Codecov</title>
      <dc:creator>Petr Švihlík</dc:creator>
      <pubDate>Sun, 01 Dec 2019 21:25:45 +0000</pubDate>
      <link>https://forem.com/petrsvihlik/running-net-core-3-xunit-code-coverage-in-appveyor-using-opencover-and-codecov-1n7p</link>
      <guid>https://forem.com/petrsvihlik/running-net-core-3-xunit-code-coverage-in-appveyor-using-opencover-and-codecov-1n7p</guid>
      <description>&lt;p&gt;I love AppVeyor. It's my go-to CI/CD service for .NET projects. I use it for both &lt;a href="https://github.com/petrsvihlik/WopiHost/"&gt;personal&lt;/a&gt; and &lt;a href="https://github.com/Kentico/kontent-delivery-sdk-net"&gt;work&lt;/a&gt; open-source projects. I also love automation of all kinds so, among other things, I want my test coverage reports to be automatically generated with every build.&lt;/p&gt;

&lt;p&gt;Recently, I started upgrading my project portfolio to .NET Core 3.0. With that, I also wanted to level-up my unit tests which are typically XUnit. I didn't want to introduce too much change to my code so, after some research, I decided to stick with the following setup: AppVeyor (which I've already been using) + Codecov (which we use for another project at work) + OpenCover.&lt;/p&gt;

&lt;h3&gt;
  
  
  Custom PowerShell coverage script 📜
&lt;/h3&gt;

&lt;p&gt;The goal was to create a minimalistic, reusable piece of code (independent of environment settings, tool versions, etc.) that I could use across multiple projects. Here it goes:&lt;/p&gt;

&lt;h4&gt;
  
  
  coverage.ps1
&lt;/h4&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;The script can run locally and in AppVeyor with slightly different configurations. The most tricky part is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$register = if ($ENV:APPVEYOR -eq $true ) { '-register' } else { '-register:user' }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;When I finally made the script run on my local machine, it started failing in AppVeyor. It took me a few hours of digging and debugging to figure out that I can't use &lt;code&gt;-register:user&lt;/code&gt; in AppVeyor. Fortunately, debugging is quite easy in AppVeyor as you can &lt;a href="https://www.appveyor.com/docs/how-to/rdp-to-build-worker/"&gt;RDP to the build worker&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;I highly recommend reading &lt;a href="https://github.com/opencover/opencover/wiki/Usage"&gt;OpenCover's documentation&lt;/a&gt;. It'll help you understand the syntax of &lt;a href="https://github.com/opencover/opencover/wiki/Usage#notes-on-spaces-in-arguments"&gt;&lt;code&gt;targetArgs&lt;/code&gt;&lt;/a&gt;, &lt;a href="https://github.com/opencover/opencover/wiki/Usage#optional-arguments"&gt;&lt;code&gt;filter&lt;/code&gt; and &lt;code&gt;register&lt;/code&gt;&lt;/a&gt; parameters.&lt;/p&gt;
&lt;h3&gt;
  
  
  Hooking the script into the CI pipeline 🔗
&lt;/h3&gt;

&lt;p&gt;AppVeyor supports &lt;a href="https://chocolatey.org/"&gt;Chocolatey&lt;/a&gt; so I'm using &lt;code&gt;cinst&lt;/code&gt; to install OpenCover and Codecov CLIs. Then I'm turning off the default test script and replacing it with my own which runs OpenCover to generate the coverage files, and finally, I'm calling Codecov CLI to upload the results.&lt;/p&gt;

&lt;p&gt;Cool thing is that if you run &lt;code&gt;codecov -f coverage.xml&lt;/code&gt; from AppVeyor you don't need an API key. It just works automagically ✨.&lt;/p&gt;
&lt;h4&gt;
  
  
  appveyor.yml
&lt;/h4&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;



&lt;h3&gt;
  
  
  The result
&lt;/h3&gt;

&lt;p&gt;I've got this nice sunburst chart indicating which files need attention:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--zqkM20kU--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/eclrv6jr8d76kd0wr72u.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--zqkM20kU--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/eclrv6jr8d76kd0wr72u.png" alt="Codecov sunburst"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I've also got the Codecov badge with % coverage:&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--i3JOwpme--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/github-logo-ba8488d21cd8ee1fee097b8410db9deaa41d0ca30b004c0c63de0a479114156f.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/Kentico"&gt;
        Kentico
      &lt;/a&gt; / &lt;a href="https://github.com/Kentico/kontent-delivery-sdk-net"&gt;
        kontent-delivery-sdk-net
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Kentico Kontent Delivery .NET SDK
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;h1&gt;
Kentico Kontent Delivery .NET SDK&lt;/h1&gt;
&lt;p&gt;&lt;a href="https://github.com/Kentico/kontent-delivery-sdk-net/actions/workflows/integrate.yml"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--9c7DbLwC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://github.com/Kentico/kontent-delivery-sdk-net/actions/workflows/integrate.yml/badge.svg" alt="Build &amp;amp; Test"&gt;&lt;/a&gt;
&lt;a href="https://codecov.io/gh/Kentico/kontent-delivery-sdk-net" rel="nofollow"&gt;&lt;img src="https://camo.githubusercontent.com/55475904a89a032dfb3db93f491b94ba01999fdf4d3b11e1932edf918d564280/68747470733a2f2f636f6465636f762e696f2f67682f4b656e7469636f2f6b6f6e74656e742d64656c69766572792d73646b2d6e65742f6272616e63682f6d61737465722f67726170682f62616467652e737667" alt="codecov"&gt;&lt;/a&gt;
&lt;a href="https://stackoverflow.com/tags/kentico-kontent" rel="nofollow"&gt;&lt;img src="https://camo.githubusercontent.com/a6eaa6d1834d0c535ac7564a1653e2ecd832888f6d6727c031e4c6d801901cb9/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f537461636b2532304f766572666c6f772d41534b2532304e4f572d4645374131362e7376673f6c6f676f3d737461636b6f766572666c6f77266c6f676f436f6c6f723d7768697465" alt="Stack Overflow"&gt;&lt;/a&gt;
&lt;a href="https://discord.gg/SKCxwPtevJ" rel="nofollow"&gt;&lt;img src="https://camo.githubusercontent.com/c5fc1ef053e1b7eaf33a0b5bc47258c25b6cc239dba2dcec109e655326772f73/68747470733a2f2f696d672e736869656c64732e696f2f646973636f72642f3832313838353137313938343839313931343f6c6162656c3d446973636f7264266c6f676f3d446973636f7264266c6f676f436f6c6f723d7768697465" alt="Discord"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Paradigm&lt;/th&gt;
&lt;th&gt;Package&lt;/th&gt;
&lt;th&gt;Downloads&lt;/th&gt;
&lt;th&gt;Compatibility&lt;/th&gt;
&lt;th&gt;Documentation&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Async&lt;/td&gt;
&lt;td&gt;&lt;a href="https://www.nuget.org/packages/Kentico.Kontent.Delivery" rel="nofollow"&gt;&lt;img src="https://camo.githubusercontent.com/2ed9d656d102e00de9d7409261221e6d8c2445d02477985cda58780a14a03df3/68747470733a2f2f696d672e736869656c64732e696f2f6e756765742f762f4b656e7469636f2e4b6f6e74656e742e44656c69766572792e737667" alt="NuGet"&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://www.nuget.org/packages/Kentico.Kontent.Delivery" rel="nofollow"&gt;&lt;img src="https://camo.githubusercontent.com/05695f02c75a9eaf1783aafecfda965470fe0cc3c0231ea71fd4e642ea77be17/68747470733a2f2f696d672e736869656c64732e696f2f6e756765742f64742f4b656e7469636f2e4b6f6e74656e742e64656c69766572792e737667" alt="NuGet"&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://docs.microsoft.com/en-us/dotnet/standard/net-standard" rel="nofollow"&gt;&lt;code&gt;netstandard2.0&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/Kentico/kontent-delivery-sdk-net#using-the-deliveryclient"&gt;📖&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reactive&lt;/td&gt;
&lt;td&gt;&lt;a href="https://www.nuget.org/packages/Kentico.Kontent.Delivery.Rx" rel="nofollow"&gt;&lt;img src="https://camo.githubusercontent.com/42b28476dfab950dec45e8553567e927a66f4a630deef9ae27dc538b80d1d7ed/68747470733a2f2f696d672e736869656c64732e696f2f6e756765742f762f4b656e7469636f2e4b6f6e74656e742e44656c69766572792e52782e737667" alt="NuGet"&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://www.nuget.org/packages/Kentico.Kontent.Delivery.Rx" rel="nofollow"&gt;&lt;img src="https://camo.githubusercontent.com/046f005ef024e5251ed86f8fa634ae26de244ba3f7f94163a5fe13d19b2ee799/68747470733a2f2f696d672e736869656c64732e696f2f6e756765742f64742f4b656e7469636f2e4b6f6e74656e742e64656c69766572792e52782e737667" alt="NuGet"&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://docs.microsoft.com/en-us/dotnet/standard/net-standard" rel="nofollow"&gt;&lt;code&gt;netstandard2.0&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/Kentico/kontent-delivery-sdk-net../../wiki/Using-the-Kentico.Kontent.Delivery.Rx-reactive-library"&gt;📖&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
&lt;h2&gt;
Summary&lt;/h2&gt;
&lt;p&gt;The Kentico Kontent Delivery .NET SDK is a client library that lets you easily retrieve content from Kentico Kontent.&lt;/p&gt;
&lt;h3&gt;
Getting started&lt;/h3&gt;
&lt;p&gt;Installation via Package Manager Console in Visual Studio:&lt;/p&gt;
&lt;div class="highlight highlight-source-powershell position-relative js-code-highlight"&gt;
&lt;pre&gt;PM&lt;span class="pl-k"&gt;&amp;gt;&lt;/span&gt; &lt;span class="pl-c1"&gt;Install-Package&lt;/span&gt; Kentico.Kontent.Delivery &lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Installation via .NET CLI:&lt;/p&gt;
&lt;div class="highlight highlight-text-shell-session position-relative js-code-highlight"&gt;
&lt;pre&gt;&amp;gt; &lt;span class="pl-s1"&gt;dotnet add package Kentico.Kontent.Delivery &lt;/span&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
Usage&lt;/h2&gt;
&lt;p&gt;To retrieve content from your Kentico Kontent projects, you'll be using an implementation of the &lt;code&gt;IDeliveryClient&lt;/code&gt; interface. This is the main interface of the SDK. Here's how you can instantiate and use the Delivery client either &lt;a href="https://github.com/Kentico/kontent-delivery-sdk-net#use-dependency-injection-ideal-for-aspnet-core-web-apps" title="Usage with dependency injection"&gt;with DI/IoC&lt;/a&gt; or &lt;a href="https://github.com/Kentico/kontent-delivery-sdk-net#usage-without-iocdi-containers-ideal-for-console-apps-unit-tests" title="Usage without dependency injection"&gt;without DI/IoC&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;
Use dependency injection (ideal for ASP.NET Core web apps)&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Startup.cs&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight highlight-source-cs position-relative js-code-highlight"&gt;
&lt;pre&gt;&lt;span class="pl-k"&gt;public&lt;/span&gt; &lt;span class="pl-k"&gt;void&lt;/span&gt; &lt;span class="pl-en"&gt;ConfigureServices&lt;/span&gt;(&lt;span class="pl-k"&gt;IServiceCollection&lt;/span&gt; &lt;span class="pl-en"&gt;services&lt;/span&gt;)
{
    &lt;span class="pl-smi"&gt;services&lt;/span&gt;.&lt;span class="pl-en"&gt;AddDeliveryClient&lt;/span&gt;(&lt;span class="pl-smi"&gt;Configuration&lt;/span&gt;);
}&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;HomeController.cs&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight highlight-source-cs position-relative js-code-highlight"&gt;
&lt;pre&gt;&lt;span class="pl-k"&gt;public&lt;/span&gt; &lt;span class="pl-k"&gt;class&lt;/span&gt; &lt;span class="pl-en"&gt;HomeController&lt;/span&gt;
{
    &lt;span class="pl-k"&gt;private&lt;/span&gt; &lt;span class="pl-k"&gt;IDeliveryClient&lt;/span&gt; &lt;span class="pl-en"&gt;_client&lt;/span&gt;;

    &lt;span class="pl-k"&gt;public&lt;/span&gt; &lt;span class="pl-en"&gt;HomeController&lt;/span&gt;(&lt;span class="pl-k"&gt;IDeliveryClient&lt;/span&gt; &lt;span class="pl-en"&gt;deliveryClient&lt;/span&gt;)
    {
        &lt;span class="pl-smi"&gt;_client&lt;/span&gt; &lt;span class="pl-k"&gt;=&lt;/span&gt; &lt;span class="pl-smi"&gt;deliveryClient&lt;/span&gt;;
    }
}&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;In…&lt;/p&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/Kentico/kontent-delivery-sdk-net"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;And last but not least, a nice ASCII art in the AppVeyor log here: &lt;br&gt;
&lt;a href="https://ci.appveyor.com/project/kentico/deliver-net-sdk/branch/master#L310"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--BGhDhmSy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/zs9oh02twi8tvg8at415.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So far, I have successfully tested this approach in two repos so I hope it'll work for you too. Any improvements are welcome!&lt;/p&gt;

</description>
      <category>netcore</category>
      <category>appveyor</category>
      <category>codecov</category>
      <category>opencover</category>
    </item>
    <item>
      <title>Why I Won't Hire You - 5 Honest Tips to a Better CV</title>
      <dc:creator>Petr Švihlík</dc:creator>
      <pubDate>Sun, 25 Aug 2019 22:10:06 +0000</pubDate>
      <link>https://forem.com/petrsvihlik/why-i-won-t-hire-you-5-honest-tips-to-a-better-cv-540g</link>
      <guid>https://forem.com/petrsvihlik/why-i-won-t-hire-you-5-honest-tips-to-a-better-cv-540g</guid>
      <description>&lt;p&gt;Recently, I had the immense pleasure to go through several hundreds of CVs. I threw 95% of them to trash. Are you wondering how to get in the lucky 5%? Read on and follow the simple tips:&lt;/p&gt;

&lt;h1&gt;
  
  
  1. Keep it short
&lt;/h1&gt;

&lt;p&gt;Your resume should fit on one page; two pages at maximum. If it's longer, it's not a sign of your overqualification but rather of the lack of communication skills.&lt;/p&gt;

&lt;h1&gt;
  
  
  2. Leave out irrelevant and obvious experience
&lt;/h1&gt;

&lt;p&gt;Spare your future employer the details about your part-time jobs at school, unless, of course, it's the only experience you have. It really doesn't help if you tell me how delicious burgers you were preparing at McDonald's or how many graves you dug (true story btw).&lt;br&gt;
If you really feel the urge to share such things somewhere, do it on LinkedIn.&lt;/p&gt;

&lt;p&gt;If you are applying for a job in IT and include Word or Excel in your resume, congratulations, you've just ruled out yourself… Unless you are an Excel guru who can craft a multi-sheet tax calculation form. &lt;/p&gt;

&lt;p&gt;Are you tempted to add "10 years experience in XML"? Perish the thought!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fci99e23o1sk5lrvi2shl.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fci99e23o1sk5lrvi2shl.jpg" alt="Grave Digging"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  3. Don't try to fool the recipient
&lt;/h1&gt;

&lt;p&gt;Do not try to name every technology you've ever come across…list things that you specialize in. I've seen elaborate tables and charts indicating how many years and months of experience a candidate has in each programming language.&lt;/p&gt;

&lt;p&gt;Frankly, the numbers don't tell me much. I need to see you think and code. &lt;/p&gt;

&lt;p&gt;Write honestly what kind of projects you've worked on, what was your contribution and please, be brief.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fewer graphics, the better
&lt;/h2&gt;

&lt;p&gt;Using pie charts, progress bars, or other sophisticated visuals to indicate the level of your skills is a big NO-NO. With the exception of people applying for a graphic designer or BI analyst jobs. Remember, it's a resume - not an infographic.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Ftv3d72canjkt9b2mpet5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Ftv3d72canjkt9b2mpet5.png" alt="Intelligence -10"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  4. Make it personal
&lt;/h1&gt;

&lt;p&gt;Do not broadcast your CV. By broadcasting a CV you are basically saying "I don't care where I will work. I don't want to excel in anything, I just want your money for whatever I'll be doing."&lt;/p&gt;

&lt;p&gt;It shows a lack of motivation and disrespect to people reading your CV. You are wasting everybody's time.&lt;/p&gt;

&lt;p&gt;Do proper research and craft a resume to fit the few carefully chosen potential employers.&lt;/p&gt;

&lt;h1&gt;
  
  
  5. Attach a cover letter when it makes sense
&lt;/h1&gt;

&lt;p&gt;Are you moving to a different tech stack? Are you changing roles or field of expertise completely? Attach a cover letter and explain your motivation.&lt;/p&gt;




&lt;p&gt;It's that simple. The less information you include and the fewer companies you address, the higher is the chance of you getting hired! Isn't that great? :D&lt;/p&gt;

</description>
      <category>career</category>
      <category>cv</category>
      <category>resume</category>
      <category>coverletter</category>
    </item>
  </channel>
</rss>
