Categories
Software

Debugging Azure Devops Pipelines

When creating new pipelines I often need to inspect the contents of the workspace:

  - powershell: |
      tree "$(Pipeline.Workspace)" /F
    displayName: 'Treeview of Pipeline.Workspace'
Categories
Software

Delete git tags

To list all tags in your remote repository that include the word ‘Jenkins’ and end with a number:

git ls-remote --tags origin | awk '/Jenkins(.*)[0-9]$/{ print $2 }'

To delete all of those tags:

git ls-remote --tags origin | awk '/Jenkins(.*)[0-9]$/{ print ":" $2 }' | xargs git push origin

Categories
Software

Microsoft Tye

Tye’s promise:

  1. Making development of microservices easier by:
    • Running many services with one command
    • Using dependencies in containers
    • Discovering addresses of other services using simple conventions
  2. Automating deployment of .NET applications to Kubernetes by:
    • Automatically containerizing .NET applications
    • Generating Kubernetes manifests with minimal knowledge or configuration
    • Using a single configuration file

Installation

dotnet tool install --global Microsoft.Tye --version 0.10.0-alpha.21420.1

Running a single service

Let’s create a new front-end project, and start it via Tye.

mkdir microservices
cd microservices
dotnet new razor -n frontend
tye run frontend

Here we can see Tye has built the project, configured HTTP bindings, and started it running. We can navigate to 127.0.0.1:8000 to view the Tye dashboard.

We can stop Tye with Ctrl+C.

Running multiple services

Now suppose out front-end project needs to talk to a back-end API. We will create a solution file containing both projects

dotnet new webapi -n backend
dotnet new sln
dotnet sln add frontend backend
tye run

Now we have both services running.

We will wire up the two services using Tye’s service discovery.

  1. Stop Type with Ctrl+C.
  2. Add WeatherForecast.cs to the frontend project, to match the similar class in backend.
namespace frontend;

public class WeatherForecast
{
    public DateTime Date { get; set; }

    public int TemperatureC { get; set; }

    public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);

    public string? Summary { get; set; }
}
  1. Add WeatherClient.cs to the frontend project:
namespace frontend;

using System.Net.Http;
using System.Text.Json;
using System.Threading.Tasks;

public class WeatherClient
{
    private readonly JsonSerializerOptions options = new JsonSerializerOptions()
    {
        PropertyNameCaseInsensitive = true,
        PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
    };

    private readonly HttpClient client;

    public WeatherClient(HttpClient client)
    {
        this.client = client;
    }

    public async Task<WeatherForecast[]> GetWeatherAsync()
    {
        var responseMessage = await this.client.GetAsync("/weatherforecast");
        var stream = await responseMessage.Content.ReadAsStreamAsync();
        return await JsonSerializer.DeserializeAsync<WeatherForecast[]>(stream, options);
    }
}

4. Add a reference to the Microsoft.Tye.Extensions.Configuration package to the frontend project.

dotnet add frontend/frontend.csproj package Microsoft.Tye.Extensions.Configuration  --version "0.2.0-*"

  1. Now we register WeatherClient for dependency injection in out start-up code:
using frontend;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddHttpClient<WeatherClient>(client =>
{
    client.BaseAddress = builder.Configuration.GetServiceUri("backend");
});

builder.Services.AddRazorPages();

...
  1. Add a Forecasts property to the Index page model under Pages\Index.cshtml.cs in the frontend project.
public WeatherForecast[] Forecasts { get; set; }
  1. Change the OnGet method to take the WeatherClient to call the backend service and store the result in the Forecasts property:
public async Task OnGet([FromServices]WeatherClient client)
{
    Forecasts = await client.GetWeatherAsync();
}
  1. Render the Forecasts property in the Index.cshtml razor view:
@page
@model IndexModel
@{
   ViewData["Title"] = "Home page";
}

<div class="text-center">
   <h1 class="display-4">Welcome</h1>
   <p>Learn about <a href="https://docs.microsoft.com/aspnet/core">building Web apps with ASP.NET Core</a>.</p>
</div>

Weather Forecast:

<table class="table">
   <thead>
      <tr>
         <th>Date</th>
         <th>Temp. (C)</th>
         <th>Temp. (F)</th>
         <th>Summary</th>
      </tr>
   </thead>
   <tbody>
      @foreach (var forecast in @Model.Forecasts)
      {
         <tr>
            <td>@forecast.Date.ToShortDateString()</td>
            <td>@forecast.TemperatureC</td>
            <td>@forecast.TemperatureF</td>
            <td>@forecast.Summary</td>
         </tr>
      }
   </tbody>
</table>

Now if we re-start the services with tye run we should see the front-end service is displaying random weather data generated by the back-end.

Tye configuration

Let’s generate Tye’s optional configuration file, tye.yaml, for our solution:

tye init

Here we can re-configure the services, e.g., we can set specific bindings.

(Changing the name of the backend service means we must also update the reference to it in the frontend Program.cs).

Adding external dependencies (Redis)

Let’s change the WeatherForecastController.Get() method in the backend to cache weather information using an IDistributedCache.

  1. Add these using statements
using Microsoft.Extensions.Caching.Distributed;
using System.Text.Json;
  1. Update Get()
    [HttpGet(Name = "GetWeatherForecast")]
    public async Task<string> Get([FromServices]IDistributedCache cache)
    {
        var weather = await cache.GetStringAsync("weather");

        if (weather == null)
        {
            var rng = new Random();
            var forecasts = Enumerable.Range(1, 5).Select(index => new WeatherForecast
            {
                Date = DateTime.Now.AddDays(index),
                TemperatureC = rng.Next(-20, 55),
                Summary = Summaries[rng.Next(Summaries.Length)]
            })
            .ToArray();

            weather = JsonSerializer.Serialize(forecasts);

            await cache.SetStringAsync("weather", weather, new DistributedCacheEntryOptions
            {
                AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(5)
            });
        }
        return weather;
    }
  1. Add a package reference to Microsoft.Extensions.Caching.StackExchangeRedis
cd backend/
dotnet add package Microsoft.Extensions.Caching.StackExchangeRedis
cd ..
  1. Register the IDistributedCache implementation in the backend startup code.
...
builder.Services.AddStackExchangeRedisCache(o =>
{
    o.Configuration = builder.Configuration.GetConnectionString("redis");
});

builder.Services.AddControllers();
...
  1. Modify tye.yaml to include redis as a dependency.
...
- name: redis
  image: redis
  bindings:
  - port: 6379
    connectionString: "${host}:${port}"
- name: redis-cli
  image: redis
  args: "redis-cli -h redis MONITOR"
Categories
Food

Seismic Waves

Categories
Food

Chicken Marsala

Marsala

Ingredients

  • 4 chicken breasts
  • 1tbsp plain flour
  • 2 tbsp butter
  • 350ml chicken stock
  • 2 large shallots (or a small onion), finely chopped
  • 200g mushrooms
  • 2 garlic cloves, crushed
  • 100ml Marsala wine

Method

  1. Butterfly the chicken breasts. (Cut horizontally almost right through and open flat). Dust the chicken with the flour, seasoned with salt and pepper.
  2. Fry the chicken in 1 tbsp of butter for 2 minutes each side until golden brown, then put aside. Fry in batches if necessary.
  3. Deglaze the pan with a little stock, scraping the tasty brown stuff from the bottom. Fry the shallots in a 1 tbsp of butter for 2 minutes, then add the mushrooms, garlic and Marsala. Scrape the good stuff from the bottom of the pan again, and add the rest of the stock.
  4. Bring to a simmer, add the chicken and cook for 15 minutes, until the chicken is cooked through and the sauce has reduced.

Categories
Blog

More garden visitors

Categories
Food

Braised Red Cabbage

This sweet, sloppy, chunky, sour, sticky mess is great with roasts, stews, cold meats, or just on toast. I could eat it for every meal.

Ingredients

  • 1 red cabbage, core removed and roughly cut into chunks
  • 1 pack (approx. 300g) streaky bacon, sliced finely.
  • 2 eating apples, peeled, cored and roughly diced
  • 1 onion, sliced
  • 1 or 2 tbsp fennel seeds
  • 150ml balsamic vinegar

Method

  1. Fry the bacon and fennel seeds with plenty of olive oil in a very large saucepan, for 5 minutes.
  2. Add the onion and fry for a few more minutes until the onion is softened.
  3. Add the apple, cabbage and balsamic vinegar. Season well with salt, pepper, and anything else you fancy. Stir well.
  4. Cover and cook on a low heat for an hour, stirring occasionally.

This keeps well in the fridge, and is even better reheated and served with a bit of butter.

Categories
Blog

Workshop Tour (#1?)

This is a response to your man @petedotnu, who asked me for a photo tour of my workshop. It’s a space that is still evolving, so maybe this post will be the first in a series.

I was inspired to take up woodworking six months ago, and then became a little obsessed. My friends have been very patient on our weekly Zoom calls as I bang on about wood and very little else. It’s good of Pete to take an interest, but if you know him you’ll know he’s nice like that, and a practical guy himself, and his general curiosity about everything is why we get on so well.

Together with huge stock of wood, my workspace takes up nearly half of our double garage. In the next few weeks I plan to build some shelves down the middle of the garage, floor to ceiling, as far as the up-and-over door allows.

Design for shelf units, made from 2×3 construction timber and 11mm OSB. Shelves will be prevented from sagging by aluminium rails salvaged from an old conservatory.

These shelves will hold most of the stuff that is currently stored against the back wall, freeing up that space for more efficient storage of the bikes. The shelves will also help partition my workspace off from the rest of the garage, hopefully limiting my tendency to leave wood and tools in everybody’s way.

I see people on YouTube doing some fantastic precision joinery with table saws and for a while I wanted to emulate that, but I soon realised that’s not going to happen without spending more money than I can justify on a high-end saw. Instead I settled for this Titan, probably the cheapest model on the market that isn’t utter shite, and I got this particular one half-price on ebay. The fence wobbles, and the mitre guide is almost useless, but it’s good enough for rough work. I plan to build a cross-cut sled for this soon, but even without that I can manage some reasonably consistent cuts.

The workbench serves as an outfeed table to the table saw, as it is almost exactly the same height. It’s good and heavy, the top is reasonably flat, and at just £100 for a ready-assembled, six-foot bench it was a good option to get me started. But even though I’m quite tall, I find it a few inches too high for comfort when planing and sawing.

I’m making do without a vice, just using F-clamps to fix my work on or against the edge of the bench. When I’ve built my low roman workbench then this one will be demoted, perhaps to serve as a station for my mitre saw and the drill press I hope to get next year. I’d love to build a traditional English joiner’s bench with a couple of big vices, but that project is a long way off yet.

In front of the bench is this 2.4m oak sleeper. Small, handily sized bits of wood, especially hardwood, are bloody pricey, so it’s better to buy timber in whopping great lumps like this that you can’t lift on your own and that you can spend months tripping over until it’s dry enough to be usable.

Under the bench there is space for six 35-litre Really Useful Boxes. These are good but are usually pretty expensive; I was lucky to get these ones heavily discounted. I have boxes for my circular saw and jigsaw, drill and router bits, planes, clamps, and all of my glues and sanding and finishing gear. I don’t know why the axe and the oak maul are there, I usually keep those in the shed.

I have a couple of crates of other tools, including my lovely new chisels, my antique screw clamp, and one of the mallets I made. This is not a good way to store tools, so I’m working on a design for a dutch tool chest that will hold everything I commonly use, and which I can move around to keep it within reach.

I hung brackets from the joists so I could get some of my stock up and out of the way. Here I have some planed redwood boards that will become my tool chest. On the floor I have a stock of cheap construction timber: treated 2x3s for the garage shelving, untreated 2x4s for my low workbench.

And here is a shot of my bench in use. I clamped a piece across the bench as a planing stop, and I’m using this knotty offcut from a rafter to check the action of my new wooden jointer plane, comparing it with my cheap No.4 smoothing plane. It’s looking good but a bit more fettling is required to get it perfect. I aim to use this plane to joint the edges of the some reclaimed pine scaffolding boards, and glue them edgewise to make a table top that Susan can use on trestles when re-enacting.

Mess is a problem. Mostly my hobby seems to be sweeping up. With this set of hose adapters I can hook up Hetty to the dust ports on almost all of my power tools, and that helps a lot. She’ll catch about 95% of the dust from this mitre saw. On a dry day I can lift my workbench onto dollies and wheel it out onto the drive, and give the space a proper sweep out.

Categories
Food

Kimchi

This should fill 2 half-litre Korken jars. I have made this with carrots cut into matchsticks, and with grated carrot, but it is more satisfying when the carrot is closer in consistency to the cabbage.

Ingredients

  • 1 Chinese cabbage
  • 2 carrots, cut into ribbons with a peeler
  • 60g salt
  • 8 radishes, coarsely grated
  • 3cm piece of ginger, grated
  • 3 cloves of garlic, crushed
  • 2 tbsp Thai fish sauce
  • 3 tbsp sriracha sauce
  • 1 tbsp caster sugar
  • 3 tbsp rice vinegar
  • 4 spring onions, finely sliced

Method

  1. Quarter the cabbage and remove the core.
  2. In a large bowl, coat the cabbage and carrot with the salt, then cover with water and leave for 2 hours.
  3. In a small bowl, mix the ginger, garlic, fish sauce, sriracha, vinegar and sugar.
  4. Drain the brined vegetables and quickly rinse with cold water to get rid of any excess salt.
  5. In the large bowl, mix the cabbage and carrot with the radish, spring onions and the sauce. Then pack tightly into sterilised jars, wipe the rims clean and seal.
  6. Leave to ferment at room temperature for a few days. Periodically poke it to release any bubbles, and pack down again with a fork. Top up with vinegar and sriracha to add extra punch.
  7. Move to the fridge.

I have kept sealed jars in the fridge for 6 months without spoiling, and even after opening it seems to keep for 8 weeks or more. Your mileage might vary.

Categories
Blog

Woodwork for Humans

Inspired by YouTuber Rex Krueger’s terrific Woodwork for Humans series, I set myself a challenge: using just three simple hand tools, make a fourth. I need a good heavy mallet for some upcoming projects. Can I make one with just an axe, a saw and a hand drill?

tl;dr: Yes, kind of, but I didn’t like the way it turned out. My second attempt was much better, when I also used a chisel and a block plane.

This summer a storm brought down most of the oak tree behind our house, so before the council could organise a team to clear away the branches I nipped out with my saw and helped myself to a few logs. My only axe is rather small, but by pounding on it with an arm-thick branch I was able to split a nice big log, and once the first cut is made a lot of the tension in the wood is released and subsequent cuts are much easier.

The very core of a log, the pith, is too prone to splitting to be used for anything but firewood, so I chose a log that had the core off-centre, with a broad hunk of heartwood on one side. From this I split of a couple of roughly rectangular blocks, and spent an hour chipping one into shape with the axe. Then I drilled a mortise part-way through with an 25mm auger bit.

For the shaft I wanted some old well-seasoned hardwood, so I had to wait a few weeks for the Chiltern Wood Recycling Project to re-open after the lockdown, and I picked up a bit of oak beam, probably cut from door lintel.

In Rex’s video he shows how to shape a cylindrical tenon on the end of the shaft by splitting along the grain and then paring with the axe. Unfortunately the grain in my piece ran a little diagonally, making it really hard to keep the tenon straight. When I tested the fit the handle was quite wonky, and at this point still far too thick to hold comfortably.

Here I decided I had proved the concept but completing the job with just an axe was going to take too long. I sanded the mallet head smooth, just to check the look of it, decided I didn’t like it, and I started afresh.

I cut a new head, bigger, rougher and more rectangular than the first, which I think looks better. I cut a fresh tenon on the shaft using a chisel, then with a block plane I reduced the thickness and gave the handle a roughly hexagonal shape.

The fitting of the head was the most fun bit, and its a very simple but ingenious technique. I ripped down the middle of the tenon with a saw, and inserted a thin wedge cut from the same oak beam, as in the photo. Then I slathered it in wood glue and pounded it into the hole in the mallet head. The bottom of the hole forces the wedge into the slot, pushing the two halves of the tenon apart and locking it in place.

It is several months now since the tree was felled but the wood of the mallet head is still a quite green, so it is still slowly losing moisture and shrinking as it does so. But the wood of the handle is old and bone dry, so that joint should only get stronger.

Here’s the finished tool. At 1.4kg it is probably too heavy for use with chisels, unless maybe if I’m chopping out big mortices, but I think it will be perfect for hammering in wedges to hold my work pieces on the low Roman workbench that I plan to build this year.