Tags

, , , ,

Problem statement

Your codebase is growing. Every week brings handful of new microservices. Each one uses (potentially) different languages, libraries, frameworks, data stores etc. The freedom is in the air and everyone is happy but… There is always “but” :).  For solving cross-cutting concerns (e.g. logging, monitoring, authorization…) you have small list of common libraries which need to be in sync. You want at least to know if there are any outdated dependencies.

Solution

Automate checking for outdated references! For a .NET developer dependency is roughly equivalent to NuGet package. The best tool for managing NuGet (as we will see later, not only!) references is Paket. And there is a command for that! All we need is to run it in a build script and potentially break the build if there are some important upgrades.

Details

Output from the paket outdated command looks like this:

Paket version 5.0.0
Resolving packages for group Main:
 - Castle.Core 4.1.0
 - Castle.Windsor 3.3.0
Outdated packages found:
  Group: Main
    * Castle.Core 2.0.0 -> 4.1.0
    * Castle.Windsor 2.0.0 -> 3.3.0
Performance:
 - Resolver: 3 seconds (1 runs)
    - Runtime: 199 milliseconds
    - Blocked (retrieving package details): 1 second (3 times)
    - Blocked (retrieving package versions): 1 second (1 times)
    - Not Blocked (retrieving package versions): 1 times
 - Average Request Time: 884 milliseconds
 - Number of Requests: 12
 - Runtime: 4 seconds

We need to parse it and extract list of outdated packages:

let parseOutput (lines: string seq) =
    lines
    |> Seq.fold (fun state line ->
        match state, line.Trim() with
        | (false, list), "Outdated packages found:" -> true, list
        | (true, list), "Performance:" -> false, list
        | (true, list), line when line.StartsWith "* " -> true, line :: list
        | state, _ -> state) (false, [])
    |> snd
    |> List.rev

Yep, that’s F# code! I use FAKE for my build scripts, so you should because it’s great! FAKE gives me easy domain-specific language (DSL) for build tasks together with power of F#.

Using FAKE’s ProcessHelper I can run paket outdated command and feed output into my function:

let runPaket () =
    ProcessHelper.ExecProcessAndReturnMessages
        (fun psi ->
            psi.FileName <- ".paket/paket.exe"
            psi.Arguments <- "outdated")
        (TimeSpan.FromMinutes 5.) // timeout
    |> fun r -> r.Messages

let outdated = runPaket () |> parseOutput

Given list of outdated packages I want to decide which are important (i.e. break the build). For example, all packages with the name starting with “MyCompany.” should pull the trigger:

let filterByPrefix prefix (msg: string) =
    if msg.StartsWith("* " + prefix) then true else FAKE.trace msg; false

let breakIfAny = function
| [] -> ()  // do nothing
| any -> failwithf "Outdated packages found: %A" any  // break the build

outdated |> List.filter (filterByPrefix "MyCompany.") |> breakIfAny

Now it’s easy to create build step (called Target in FAKE DSL):

Target "Outdated" <| fun _ ->
    runPaket ()
    |> parseOutput
    |> List.filter (filterByPrefix "MyCompany.")
    |> breakIfAny

...

"Clean"
    =?> ("Outdated", hasBuildParam "FindOutdatedPackages") // optional step
    ==> "Restore"
    ==> "Build"
    ==> "Test"
    ==> "Publish"
    ==> "Package"

Postscriptum

Do you remember that Paket manages not only NuGet references? We can use it to share our target with other developers. Just save it as a Gist. Now you can add it as a reference in paket.depencencies file:

...
gist orient-man/c29c299ed970fd097f80124ffde734ce FindOutdatedPackages.fsx
nuget FAKE
...

And in your FAKE script:

#load "paket-files/orient-man/c29c299ed970fd097f80124ffde734ce/FindOutdatedPackages.fsx"

Target "Outdated" <| fun _ -> OutdatedPackages.target "MyCompany."

That’s all folks.

Advertisements