All posts by Jim

Software engineer from Crete living in Switzerland; C# & Azure paladin; economics hobbyist; firearm enthusiast; perpetually tormented by 3 beautiful women :-)

Design by contract Tutorial: Mock your interfaces using Swagger, Wiremock, Docker, Azure Devops, Terraform and Azure

Fake it till you make it: Introduction

As with many topics, this one came up due to a real need.

You see, at work we have in-house as well as outsourced development. The outsourcers’ locations literally span continents as well as time zones. And yes, the software each one develops must play nice with everyone else’s.

So in our situation, design-by-contract is imperative. It simply doesn’t work any other way. We need to let each one know what is expected of them and then give them relative freedom to develop using their tools and methods -as long as the input and output is defined.

Since we mostly use REST services, the way we usually do it is by giving them Swagger files. This does a decent job of explaining what they need to build, be it either the provider (a REST API) or the consumer (the caller of the REST API). But still, there are many cases where there are gaps and they need to test with “the real thing”, or at least something that’s close to being real.

So what I usually do is build and deploy a mock -a fake service that looks a lot like the real thing. That enables all of us to get most of the work done; any differences that arise during the integration testing will be (usually!) minor and (usually!) easily fixable.

My work (and this guide, walk-through, tutorial, nameitwhatyouwant) has the following steps:

  1. Write the Swagger, or OpenAPI, file. This is versioned; whenever there are changes, which is normal in every project, I issue a new version and notify everyone involved. The swagger files are kept in a git repository (we use Azure Devops, a.k.a. VSTS) so the changes are traceable.
  2. Create a mock (fake) service using Wiremock.
  3. Create a docker image for the mock service using Rodolphe Chaigneau’s wiremock-docker image.
  4. Using Terraform, I build an Azure resource group and host the docker image in an app service.
  5. In Azure Devops, I build a deployment pipeline that deploys all changes, be it in the Docker container or the Azure configuration, whenever a change is pushed in the git repository.
  6. Then everyone involved can test the service using the swagger editor, curl or whatever tool they like -SoapUI, Postman, Postwoman, younameit.

I could, of course, not bother with most of it and just run Wiremock locally. But remember, it’s not just for me. It has to be useful for many people, most of them outside my company’s network. They will use it to test the client they’re developing or verify the service they’re building.

Note that all of the steps explained in this guide are cross-platform. I’ve tested them with both Windows 10 and Ubuntu 19.04. In both OSes I’ve used just the simplest tools -a text editor and git command line- but normally I use VS Code (and occasionally vi for old time’s sake 😊). Whatever little scripting there is, it’s done in Powershell Core, also cross-platform.

Let’s start, shall we?

Step 1 – [OpenAPI] Define the specs and create the Swagger file

Step 2 – [Wiremock] Create the mock service

Step 3 – [Docker] Build the container for the mock service

Step 4 – [Terraform] Ship these containers

Step 5 – [Azure Devops] Create the auto-deploy pipeline

Step 6 – [Swagger] Test via Swagger UI and curl

How I fought off a Facebook hacker -and how to avoid getting hacked yourself

Last night I helped a close friend: I successfully fought off a Facebook account takeover. It wasn’t easy. I sweated for a couple of hours until I got it done. And I even had to face a rather unsophisticated, or maybe just lazy, enemy. Here I’ll recap what happened and give some easy but effective advice that you can easily use .

What happened?

As it happens with many IT professionals, I’m the go-to person for any computer related problems for family and friends.

My friend called me, frantically trying to explain that someone, using his Facebook account, was using Messenger to send personal messages to all his contacts. The message was in casual language, like you would talk to a friend, claiming that he had lost his wallet and asking if the friend has an account in a certain bank (obviously the bank had nothing to do with this). Most importantly, the message didn’t look obviously fake.

“Good evening.. how are you?!! I’m here and there. I lost my wallet 😦 Can I ask you something.. do you maybe have an account in Piraeus Bank? I need a big favor, I lost the bank card as well :(“

The response

Mitigation: informing people

To avoid people actually sending money, I logged into Messenger with my friend’s credentials and started sending messages to people that were replying, concerned about what had might have happened to their friend. I opted for something short, clear and alarming: “I’VE BEEN HACKED PLEASE IGNORE IT’S A VIRUS” (yes, I know that technically speaking that’s not especially accurate)

But the enemy was active and chatting with 2-3 of the contacts. In these cases, I saw my message being deleted.

I noticed that all his messages were more or less the same; he had some kind of playbook and was copy-pasting text, maybe slightly changing the text to fit the conversation.

And in one case he came close to being victorious: before I could sent the “please ignore” message, one of the contacts sent him some bank details -not sure what exactly as the message was deleted by the enemy, presumably after copying it. The contact then saw my message and replied alarmed “I sent him, what do I do now???” to which I replied “Call your bank NOW and lock your account and credit card”. I hope that helped; I’ll definitely follow up on that.

Taking back control of the account

The enemy hadn’t changed any password, so I was able to log in. Remember that Messenger accounts are controlled in Facebook (unless you have a Messenger-only account, which was not the case here). So the first thing I checked was the active sessions in Facebook (Settings > Security and Login Settings > Where You’re Logged In). That was what I got:

Unfortunately I didn’t know at the time that you can hover over the session with the mouse and get more info, like the session’s IP address. Had I done that, we could have a chance to retaliate -like going to the police.

My friend uses an Ubuntu laptop (which I set up for him), a Windows PC at work and a Samsung mobile where he uses Facebook and Messenger through the apps. So the first 3 sessions were almost certainly the enemy. I immediately disconnected him. Then I changed the password.

But we were not out of the woods yet.

The Empire Strikes Back

After changing the password and believing that I had locked him out for good, I continuing notifying people in Messenger. But after a few minutes, I suddenly saw a fresh batch of the same message being sent. My friend has around 500 contacts (“friends”) and I suppose there’s some limitation from Messenger so the enemy wasn’t able to send his message to everyone at once.

How was this possible? I had changed the password and disconnected his sessions. I glanced at Facebook Settings (“Where You’re Logged In”) and, sure enough, new sessions of the Huawei Mate 8 were there. He couldn’t have guessed the new 18-character completely random password I had set. I tried logging into Facebook from a private browser window and I got “Wrong password”. Hmmm… the options I had from Facebook for changing a forgotten password was 1) SMS 2) email 3) recognize people in pictures. Until that point, I had used SMS. So how did he do that?

I called my friend:

Me: please tell me that you don’t have the same password in your email as in Facebook
(note: his password was something like “oldman53#”)
Friend: no I don’t
Me: so what’s your email password?
Friend: The same but without the # at the end

NICE. Well that’s really damn secure I thought to myself, though I didn’t say anything -didn’t want to castigate my beleaguered friend, I’m saving that for the weekend 🙂

So first thing, as people were already replying in Messenger and there was real danger of someone sending money, I had to stop him getting in. So I went to Facebook settings to change the email.

The thing is, with the password already changed and unknown to me, I had to reset the password first. And Facebook wouldn’t send an SMS anymore, after having used it a few times already.

Return of the Jedi

So I had to resort to face recognition. The process presented me with 3 photos at a time, for a total of 5 people, and a list of possible names from the friends list. There was the option “I don’t know”, but you could use it only twice -then you were out.

Obviously these people were unknown to me, so I had to send them through What’s App to my friend. It took us around 10 precious minutes but at the end it worked. I immediately changed the email to one that I own (and has a decent, unique password and multi-factor authentication!).

After that, I disconnected his sessions and that was the end of it, I didn’t see him again. I quickly headed over to outlook.com, where his email is hosted, changed the password there and added two factor authentication by SMS.

I anxiously kept monitoring Facebook’s sessions in case he somehow came back on one window and at the same time continued to notify the hundreds of people he had sent his message to. At the same time I tested, with a private browser window, that I even knowing the password I couldn’t login to Facebook or outlook.com without an SMS to my friend’s phone.

After around half an hour had passed, I felt the worst were behind us. I called my friend and told him to log in to Messenger and continue talking to people.

Some conclusions

To be clear, the reason this happened was because my friend, like many, many people, had bad password hygiene. He was using relatively easy (for a machine) to guess passwords but most importantly, he was reusing passwords between web sites. And web sites get passwords stolen. A lot.

What can you do to avoid this happening to you? Start from the low-hanging fruit. You get very decent security with very little effort.

So here’s a small TODO list:

  • Use random long (18 character or more) passwords. If it’s really random (e.g zGasd6t7a6tgQaERys6Ld5AoVF567) you don’t even need symbols. Don’t create them by hand, use a password generator (like this).
  • Use unique passwords. Every site or service you use needs to have its own. It will get stolen, eventually, but the damage will be contained to this site only. And no, oldman53 and oldman53# are NOT really different.
  • The two points above are basically impossible for a human to do. So you need to use a password manager. I use LastPass and I’m very happy with it. It costs around EUR 35 a year. If you want a free alternative use either Bitwarden or Firefox Lockwise.
  • When available, use two-factor authentication (2FA); you might also see it named as multi-factor authentication (MFA) or two-step validation (2SV, that’s what Amazon calls it). This is an absolute must. 2FA is when, in order to login to a service, you need a username, a password plus something more. Usually it’s an SMS, and that’s fine, but even better you can use an authenticator app. LastPass has its own, and its backed up in your LastPass account, but if you want a free alternative get either Authy or the one from Microsoft which is backed up in your Microsoft account. Obviously your authenticator backup needs to be well protected, so use two-factor for this as well -but a different one in case you lose access to it, so here SMS is better.
How much effort is this?

I did this with my friend so I got a taste. Note that I’m in Switzerland and he’s in Greece, so he did the whole process with me giving instructions on the phone -which slowed us down considerably. But on the other hand I knew what had to be done, while less experienced users might be not so comfortable when doing this for the first time. We used Bitwarden + Authy.

  • It took us around 90 min to set up Bitwarden and Authy, and then add all his passwords there. We set it up on his laptop and two mobile phones.
  • It took another hour to change the password for the most important services (Gmail, Outlook.com, Paypal and Facebook) and to set up two-factor authentication.
  • Add to that another 45 minutes of training, for him to learn to use a password generator, the password manager and 2FA. Basically how to use really long and random passwords when signing up to web sites, how to save the passwords in Bitwarden, how to log in from the laptop or phone without having to type the password and how to add 2FA (where available) in Authy.

So that was, what, almost three and a half hours in total. It’s not trivial. But trust me, if you find yourself in his shoes you’ll wish you had done it already. It’s time well spent 🙂

Find and kill processes that lock a file or directory

It’s an old problem when doing installations: some directory or file are locked by some process, and the installation fails.

As I’ve had this exact problem in the context of a web application, I’ve written this powershell script to take care of it. The idea was taken from the Octopus Deploy Blog.

So what the script does is:
downloads the Microsoft Handle utility (in %temp%)
– uses it to scan the $PathToCheck (can be a file or directory; if it’s a dir, it scans subdirs and all files as well)
parses the output
– if needed, kills all processes mentioned

#
# Source: DotJim blog (http://dandraka.com)
# Jim Andrakakis, August 2019
#
param([string]$PathToCheck = "c:\temp")

Clear-Host
$ErrorActionPreference = "Stop"

$url = "https://download.sysinternals.com/files/Handle.zip"

# === download handle.exe from microsoft ===
$tempDir = [System.IO.Path]::GetTempPath()
$handlePath = [System.IO.Path]::Combine($tempDir, "handle64.exe")
if (-not (Test-Path $handlePath)) {    
    $output = [System.IO.Path]::Combine($tempDir, "handle.zip")
    Invoke-WebRequest -Uri $url -OutFile $output
    Expand-Archive -LiteralPath $output -OutputPath $tempDir
}

# === run handle.exe ===
# see https://octopus.com/blog/how-to-handle-locked-files to see why the reg entry is needed
& reg.exe ADD "HKCU\Software\Sysinternals\Handle" /v EulaAccepted /t REG_DWORD /d 1 /f | Out-Null
$handleOutput = & $handlePath -a $PathToCheck

# === do we have to kill anything? ===
if ($handleOutput -match "no matching handles found") {
    Write-Host "Nothing to kill, exiting"
    exit
}

# === get process ids from handle.exe output ===
$pidList = New-Object System.Collections.ArrayList
$lines = $handleOutput | Split-String -RemoveEmptyStrings -separator "`n" 
foreach($line in $lines) {
  # sample line: 
  # chrome.exe         pid: 11392  type: File           5BC: C:\Windows\Fonts\timesbd.ttf
  # regex to get pid and process name: (.*)\b(?:.*)(?:pid: )(\d*)
  $matches = $null
  $line -match "(.*)\b(?:.*)(?:pid: )(\d*)" | Out-Null
  if (-not $matches) { continue }
  if ($matches.Count -eq 0) { continue }
  $pidName = $matches[1]
  $pidStr = $matches[2]
  if ($pidList -notcontains $pidStr) { 
    Write-Host "Will kill process $pidStr $pidName"
    $pidList.Add($pidStr) | Out-Null
  }
}

# === DIE PROCESS DIE ===
foreach($pidStr in $pidList) {
    $pidInt = [int]::Parse($pidStr)    
    Stop-Process -Id $pidInt -Force
    Write-Host "Killed process $pidInt"
}

Write-Host "Finished"

Hope that helps!

Σκεψεις για την “Τελευταια Μπλοφα”

Είμαι σε διακοπές, και όπου βρεθώ μια αγαπημένη συνήθεια είναι η επίσκεψη στο τοπικό βιβλιοπωλείο. Πάνω πάνω στα ευπώλητα ήταν το “Η τελευταία μπλόφα” της Ε. Βαρβιτσιώτη και Β. Δενδρινού. Το ειχα δει και τρέντινγκ στα σόσιαλ, ε το πήρα.

Το ρούφηξα σε λιγότερο από 24 ώρες. Και δεν είναι τόσο καλό όσο λένε, είναι ακόμη καλύτερο.

Παρότι ήξερα την ιστορία, σε ικανοποιητικό βαθμό, ως οικονομική και πολιτική, το βιβλίο την εξιστορεί από την προσωπική ματιά των πρωταγωνιστών. Αποτυπώνονται στιγμές που, για προφανείς λόγους, δεν είδαν το φως της δημοσιότητας όπως σύμβουλοι να απελπίζονται, πρωθυπουργοί να κοιμούνται σε καναπέδες, ο Ρέντσι να βάζει τις φωνές στη Μέρκελ και ο Ολάντ να πηδάει μπαλκόνια (!)

Αλλά δεν είναι κουτσομπολίστικο βιβλίο. Ίσα ίσα που δίνει την πίεση που είχαν όλοι -ή σχεδόν όλοι- να βρουν μια λύση σε μια κατάσταση που χάρη στην καταστροφική ανωριμότητα της τότε ελληνικής κυβέρνησης πήγε από το κακό στο χειρότερο σε χρόνο μηδέν.

Καταγραφω 2-3 σκέψεις, ιδέες και απορίες που έχω, τόσο για την ιστορία όσο και για το βιβλίο:

Η πρώτη και σίγουρα η σημαντικότερη: φαίνεται στο βιβλίο, αλλα ήταν και προφανές σε όσους παρακολουθήσαμε(*) αυτή την ιστορία, από τις αρχές μέχρι το φθινόπωρο του 2015, ότι η συμπεριφορά των εταίρων της Ελλάδας (χώρες ΕΕ, ΕΚΤ, ΔΝΤ κλπ.) ήταν πολύ ελαστικότερη προς την νέα τότε κυβέρνηση (ΣΥΡΙΖΑ-ΑΝΕΛ) παρά προς τις προηγούμενες (ΠΑΣΟΚ & ΝΔ-ΠΑΣΟΚ-ΔΗΜΑΡ). Κι αυτό όχι μόνο στην αρχή αλλά ακόμα και αργότερα, όταν είχε ήδη φανεί καθαρά ότι οι περισσότεροι άνθρωποι της νέας κυβέρνησης ήταν ιδεοληπτικοί, ανίκανοι για οποιαδήποτε σοβαρή εργασία τέτοιου μεγέθους και πολυπλοκότητας.

Γιατί;

Το βιβλίο δεν δίνει κάποια απάντηση. Σε κάποια σημεία μόνο διαφαίνεται, αν το ερμηνεύω σωστά, ότι με τις προηγούμενες κυβερνήσεις οι εταίροι είχαν αφενός μπουχτίσει με την απροθυμία τους να κάνουν τις επώδυνες αλλά αναγκαίες μεταρρυθμίσεις και αφετέρου τις θεωρούσαν -σωστά- υπεύθυνες για το χάλι της χώρας. Αν ισχύει αυτό, είναι στην ουσία παρόμοια λογική με το “τους είδαμε τους παλιούς, ας δοκιμάσουμε κάτι καινούριο” που έλεγαν αρκετοί πολίτες πριν τις εκλογές του 2015(**).

Η δεύτερη είναι οτι πρέπει να θυμόμαστε, όσοι δεν ασχολούμαστε ενεργά με την πολιτική, ότι η πολιτική κρίνεται από τα αποτελέσματά, όχι από τα λόγια. Είναι χαρακτηριστική η εικόνα της Μέρκελ που επισκέπτεται την Ελλάδα προ των εκλογών του 2015, βλέπει τα συνθήματα (“go back” κλπ) και, μετά τις εκλογές, δεν έχει κανένα πρόβλημα να συνεργαστεί με τους ίδιους που την καθύβριζαν.

Και η τρίτη, για το βιβλίο: θα ήταν χρήσιμο, σε κάποια επανέκδοση ίσως, να μπει ένα timeline, μια χρονική γραμμή που να δείχνει την αλληλουχία των γεγονότων. Ίσως ακόμα και δυο: μια από το 2009 ως το τέλος του 2015, που να δείχνει τα μείζονα γεγονότα (εκλογές 2009, Καστελόριζο, μνημόνιο 1, κυβέρνηση Παπαδήμου κλπ) και μια να κάνει ζουμ από την αρχή του 2015 ως το φθινόπωρο, με τα πάμπολλα Eurogroup, τα capital controls κλπ.

(*) την ελληνική κρίση την έζησα “άμεσα”, ζούσα και εργαζόμουν δηλαδή στην Ελλάδα, μέχρι την άνοιξη του 2012. Ήδη στα τέλη του 2011 είχα αποφασίσει ότι το ρίσκο για μένα, είτε λόγω grexit είτε πολύ απλά ότι μπορεί να έμενα άνεργος λόγω ύφεσης, ήταν πολύ υψηλό. Έτσι τον Φεβρουάριο του 2012 ξεκίνησα να ψάχνω για δουλειά στο εξωτερικό, και τέλη Απρίλη έμπαινα σε μια γκαρσονιέρα της Ζυρίχης. Από το φθινόπωρο 2012 και πέρα, παρότι φυσικά παρακολουθούσα τα πάντα με λεπτομέρεια, οι εξελίξεις δεν είχαν πάνω μου τις άμεσες συνέπειες που είχαν για τους φίλους μου -όσους δεν είχαν ήδη βγαλει εισητήριο χωρίς επιστροφή.

(**) το κυριότερο επιχείρημα όλων όσων τους έλεγα ότι οι ισχυρισμοί ΣΥΡΙΖΑ ήταν ανεδαφικοί (“θα καταργήσω τα μνημόνια μ’ένα νόμο κι ένα άρθρο” και άλλα ανεκδιήγητα), παρότι το καταλάβαιναν και το δεχόταν, ήταν “έστω και τα μισά από αυτά που υπόσχεται να κάνει, πάλι καλά θα είναι”. Αυτό… όπως είδαμε όλοι, δεν πήγε και πολύ καλά.

Powershell – file system operations within a transaction

Anyone who’s ever developed anything connected to a database knows about transactions. Using a transaction we can group data operations so that they happen “all or nothing”, i.e. either all of them succeed or no one does. One example is a transfer from one bank account to another: the complete transaction requires subtracting the amount to be transferred from one account and adding that same amount to the other. We wouldn’t want one to happen without the other, would we?

(yes, I know that’s not a complete description of what transactions are or do; that’s not my purpose here)

But what happens when we need the same from our filesystem?

Let’s take this scenario (which is a simplified version of a true story): we are receiving CSV files from solar panels (via SFTP) and we want to do some preprocessing and then store the data to a database. When processing them we have to generate a lot of intermediate files. After that, we want to move them to a different dir. But if something happens, say if the database is down, we want the whole thing to be cancelled so that, when we retry, we can start over.

Obviously a simple solution would be as follows:

try {
  # do a lot of hard work
  # store the data in the db
  # clean up intermediate files
  # move the CSV file to an "archive" dir
}
catch {
  # clean up intermediate files, potentially clean up any db records etc
}

That’s… well it can work but it’s not great. For example, the cleanup process itself -inside the catch block- might fail.

A much better process would be like that:

try {   
  # start a transaction
    # do a lot of hard work   
    # store the data in the db   
    # clean up intermediate files   
    # move the CSV file to an "archive" dir 
  # commit the transaction
} 
catch {   
  # rollback everything: files, db changes, the whole thing
}

That’s much cleaner! But is it possible to group db changes and filesystem changes (file create, move, delete & append, dir create & delete etc) all in one transaction?

Unix geeks are allowed to feel smug here: some flavors like HP-UX (though not Linux as far as I know) have this baked in from the get go. Your code doesn’t have to do anything special; the OS takes care of this on user request.

But as a matter of fact yes, it is also available on Windows, and it has been for some time now. The requirement is that you’re working on a file system that supports this, like NTFS.

But there’s a problem for the average .NET/Powershell coder: the standard methods, the ones inside System.IO, do not support any of this. So you have to go on a lower level, using the Windows API. Which for .NET coders, there’s no other way to put this, sucks. That’s also the reason why the Powershell implementation of file transactions (e.g. New-Item -ItemType File -UseTransaction) doesn’t work -it relies on System.IO.

I’m pretty sure that this is what crossed the minds of the developers that wrote AlphaFS which is just wonderful. It’s exactly what you’d expect but never got from Microsoft: a .NET implementation of most of System.IO classes that support NTFS’s advanced features that are not available in, say, ye olde FAT32. Chief among them is support for file system transactions.

So the example below shows how to do exactly that. I tried to keep it as simple as possible to highlight the interesting bits, but of course a real world example would be much more complicated, e.g. there would be a combined file system and database transaction, which would commit (or rollback) everything at the same time.

Note that there’s no need for an explicit rollback. As soon as the transaction scope object is disposed without calling Complete(), all changes are rolled back.

#
# Source: DotJim blog (http://dandraka.com)
# Jim Andrakakis, July 2019
#
# Prerequisite: 
#   1) internet connectivity
#   2) nuget command line must be installed 
#      (see https://www.nuget.org/downloads).
# If nuget is not in %path%, you need to change 
#   the installation (see below) to call it with 
#   its full path.
 
# Stop on error
$ErrorActionPreference = "Stop"

if ($psISE)
{
    $binPath = Split-Path -Path $PSISE.CurrentFile.FullPath        
}
else
{
    $binPath = $PSScriptRoot
}
$alphaFSver = "2.2.6"
$libPath = "$binPath\AlphaFS.$alphaFSver\lib\net40\AlphaFS.dll"
$basePath = "$binPath\..\alphatest"

# ====== installation ======
if (-not [System.IO.File]::Exists($libPath)) {
    Out-File -FilePath "$binPath\packages.config" `
        -Force `
        -Encoding utf8 `
        -InputObject ("<?xml version=`"1.0`" encoding=`"utf-8`"?><packages>" + `
          "<package id=`"AlphaFS`" version=`"$alphaFSver`" targetFramework=`"net46`" />" + `
          "</packages>")
    cd $binPath
    & nuget.exe restore -PackagesDirectory "$binPath"
}
# ==========================
 
# Make sure the path matches the version from step 2
Import-Module -Name $libPath
 
if (-not (Test-Path $basePath)) {
    New-Item -ItemType Directory -Path $basePath
}
 
# Check if the filesystem we're writing to supports transactions.
# On a FAT32 drive you're out of luck.
$driveRoot = [System.IO.Path]::GetPathRoot($basePath)
$driveInfo = [Alphaleonis.Win32.Filesystem.DriveInfo]($driveRoot)
if (-not $driveInfo.VolumeInfo.SupportsTransactions) {
    Write-Error ("Your $driveRoot volume $($driveInfo.DosDeviceName) " + `
      "[$($driveInfo.VolumeLabel)] does not support transactions, exiting")
}
 
# That's some example data to play with.
# In reality you'll probably get data from a DB, a REST service etc.
$list = @{1="Jim"; 2="Stef"; 3="Elena"; 4="Eva"}
 
try {
    # Transaction starts here
    $transactionScope = [System.Transactions.TransactionScope]::new([System.Transactions.TransactionScopeOption]::RequiresNew)
    $fileTransaction = [Alphaleonis.Win32.Filesystem.KernelTransaction]::new([System.Transactions.Transaction]::Current)
 
    # Here we're doing random stuff with our files and dirs, 
    #   just to show how this works.
    # The important thing to remember is that for the transaction 
    #   to work correctly, ALL methods you use have to be -transacted.
    # I.e. you must not use AppendText() but AppendTextTransacted(), 
    #   not CreateDirectory() but CreateDirectoryTransacted() etc etc.
    $logfileStream = [Alphaleonis.Win32.Filesystem.File]::AppendTextTransacted($fileTransaction, "$basePath\list.txt")
    foreach($key in $list.Keys) {
        $value = $list.$key
        $filename = "$([guid]::NewGuid()).txt"
        $dir = "$basePath\$key"
 
        Write-Host "Processing item $key $value"
 
        if (-not [Alphaleonis.Win32.Filesystem.Directory]::ExistsTransacted($fileTransaction, $dir)) {
            [Alphaleonis.Win32.Filesystem.Directory]::CreateDirectoryTransacted($fileTransaction, $dir)
        }
        [Alphaleonis.Win32.Filesystem.File]::WriteAllTextTransacted($fileTransaction, "$basePath\$key\$filename", $value)        
        $logfileStream.WriteLine("$filename;$key;$value")
    }
    $logfileStream.Close()
     
    # to simulate an error and subsequent rollback:
    # Write-Error "Something not great, not terrible happened"
     
    # Commit transaction
    $transactionScope.Complete()
    Write-Host "Transaction committed, all modifications written to disk"
}
catch {
    Write-Host "An error occured and the transaction was rolled back: '$($_.Exception.Message)'"
    throw $_.Exception
}
finally {
    if ($null -ne $logfileStream -and $logfileStream -is [System.IDisposable]) {
        $logfileStream.Dispose()
        $logfileStream = $null
    }    
    if ($null -ne $transactionScope -and $transactionScope -is [System.IDisposable]) {
        $transactionScope.Dispose()
        $transactionScope = $null
    }    
}

Have fun coding!

Lefkogeia – a REST API test server

Lefkogeia is a server to test your REST APIs with. When it runs, it accepts every request made on the configured IP/port and returns an HTTP 200 “Thank you for your {method name, GET/POST etc}”. As you can imagine, I developed it for my own needs and then thought it would be handy for others, so I published it on Github.

You can download its first release here. The project’s intro page is here.

It logs all requests in a directory imaginatively called logs. It creates an access.txt file where all requests are written, and one file per request (000001.txt, 000002.txt etc) in which the request’s payload (e.g. an xml or a json) is written.

Usage

The primary use of Lefkogeia is to test/debug/troubleshoot REST API and web services clients. You run it (see release notes on that) and get your client to call it. It will log whatever was sent, allowing you to troubleshoot whatever problem you might have.

Configuration

To configure Lefkogeia, edit the appsettings.json file with a text editor.

An example appsettings.json file to serve multiple addresses & ports would be:

{
	"Logging": {
		"LogLevel": {
			"Default": "Debug",
			"System": "Information",
			"Microsoft": "Information"
		}
	},
	"Host": {
		"Url": [
			"http://localhost:6800",
			"http://server1:7777",
			"http://147.102.43.3:4545"
		]
	}
}

Paths are not yet supported in URLs, so if you change http://server1:7777 to http://server1:7777/testapi you will get an error. This is planned for the next release.

Also note that in order to use https:// you need to generate a certificate by running

dotnet dev-certs https --trust

For more info see https://go.microsoft.com/fwlink/?linkid=848054.

But why “Lefkogeia”, what does this even mean?

Because it’s such a beautiful place! Lefkogeia is a small village in southern Crete, Greece, with amazing beaches like Ammoudi, Shinaria, Klisidi and more. You can read more in Tripadvisor.