Taking Note of Straightforward Solutions
“Write it down.”
Back when I was in college, this advice was given to me by one of my English professors. To date, it has been some of the best advice I have ever received. Unfortunately, it is also advice that I often forget or disregard.
Recently I was struggling with a pipeline issue that kept failing because it could not find some of my published artifacts. This instance is not the first time I have seen failures like this, so I read logs, added logging, and hunted down the problem. After I tracked down the issue, it occurred to me that I had run into this issue about nine months earlier. Of course, I failed to write it down and clearly had not learned from my past mistakes. A simple example of the problem and how to fix it follows.
Imagine that you have a simple .NET console application you want to compile and publish as a pipeline build artifact using Azure DevOps. From a DevOps perspective, this should be pretty straightforward: compile the application, publish the application, and push the result as a build artifact.
Both the code and the pipeline are on Azure DevOps. At the time of writing, .NET 6.0.300 is the latest release. After installing the .NET SDK, the entire project structure can be recreated by simply running the following commands:
> dotnet new -c Console
> dotnet new editorconfig
> dotnet new gitignore
> dotnet new globaljson
The above commands create a simple “hello world” console application. This “hello world” console application is what we want to publish as a pipeline artifact. We can run it to confirm that everything is working:
> dotnet run
Finding the Right Commands
When building out a pipeline, it is nice first to run the commands on your local repository to verify that you understand what needs to occur on the pipeline. We want to compile the application; this happens with the dotnet build command. We also need to specify the configuration of “Release” to use the release configuration.
> dotnet build --configuration Release PublishExample.sln
Next, we want to use the dotnet publish command to produce a directory containing the application and its dependencies. Like the build command, we also need to specify the release configuration and the –no-build option indicating that we don’t want to rebuild the application and instead use the output of the previous build.
> dotnet publish --configuration Release --no-build PublishExample.sln
PublishExample -> C:\Dev\PublishExample\bin\Release\net6.0\publish\
Building a Pipeline
Now that we have the two commands to build and publish the application, it is time to put them into an Azure DevOps pipeline. If you are unfamiliar with the Azure DevOps pipeline syntax, there are some great tutorials on Microsoft Learn.
The first version of the azure-pipeline.yaml looks like this:
trigger: - master pool: vmImage: ubuntu-latest steps: - checkout: self fetchDepth: 1 - task: UseDotNet@2 displayName: 'SDK' inputs: packageType: 'sdk' useGlobalJson: true - task: DotNetCoreCLI@2 displayName: 'Dotnet Build' inputs: command: 'build' projects: 'PublishExample.sln' arguments: '--configuration Release' - task: DotNetCoreCLI@2 displayName: 'Dotnet Publish' inputs: command: 'publish' projects: 'PublishExample.sln' arguments: '--no-build --configuration Release' - publish: 'bin/Release/net6.0/publish/' displayName: 'Publish Project' artifact: PublishCode language: CSS (css)
The pipeline starts by checking out the latest code version in the repository. Then, the pipeline ensures we have the appropriate version of the .NET SDK installed by looking and the version specified in the global.json file. It then runs both of the commands to build and publish the application. Finally, it uses the publish task to push the built artifacts as a pipeline artifact.
Upon running this pipeline, the “Dotnet Publish” step fails with the following error:
##[error]No web project was found in the repository. Web projects are identified by presence of either a web.config file, wwwroot folder in the directory, or by the usage of Microsoft.Net.Web.Sdk in your project file. You can set Publish web projects property to false (publishWebProjects: false in yml) if your project doesn't follow this convention or if you want to publish projects other than web projects.
Conveniently, the error message tells us exactly how to fix the issue — because we are publishing something that is not a web project, we need to set publishWebProjects to false in our “Dotnet Publish” task.
- task: DotNetCoreCLI@2 displayName: 'Dotnet Publish' inputs: command: 'publish' projects: 'PublishExample.sln' arguments: '--no-build --configuration Release' publishWebProjects: falseCode language: CSS (css)
When rerunning the pipeline, the “Dotnet Publish” task now succeeds, but the “Publish Project” task now fails with the error:
Artifact name input: Publish
##[error]Path does not exist: /home/vsts/work/1/s/bin/Release/net6.0/publish/
From the error, we can see that it cannot locate the publish directory. Looking back at the previous “Dotnet Publish” task, we can see that it did publish to that exact directory.
PublishExample -> /home/vsts/work/1/s/bin/Release/net6.0/publish/
So why does the path not exist? We can start to investigate the state of the file system by adding in some Command Line script tasks to list the contents of that directory. We can insert the following task between the “Dotnet Publish” and “Publish Project” tasks.
- script: ls bin/Release/net6.0
Running the pipeline again, we can see the output from the script:
PublishExample PublishExample.deps.json PublishExample.dll PublishExample.pdb PublishExample.runtimeconfig.json publish.zipCode language: CSS (css)
The publish directory is gone, and in its place is a publish.zip file. Looking closer at the DotNetCoreCLI task, we can see that it has many arguments. Specifically, the zipAfterPublish argument is the one causing the issue. This argument defaults to true, and its description reads, “If true, folder created by the publish command will be zipped and deleted.”
The Missing Publish Directory Fix
In the end, the fix is simple. Set the zipAfterPublish argument to false on the Publish task. The final result looks like this:
- task: DotNetCoreCLI@2 displayName: 'Publish' inputs: command: 'publish' projects: 'PublishExample.sln' arguments: '--no-build --configuration Release' publishWebProjects: false zipAfterPublish: falseCode language: CSS (css)
Even though this fix is simple, I spent much time tracking it down. Hopefully, this post serves as a memento of my professor’s advice, “write it down.” Sometimes the most straightforward solutions are the first to be forgotten.
Are there any pieces of advice you have for people troubleshooting pipeline failures? Let me know in the comments below. Looking for more developer tools or advice? Check out our Developer Tools page and my blog on Shifting Left.