Melbourne, Australia
24 November 2018
I am working on Flymark.dance which is used by many people and its hosted on their local IIS where I am not able to push new releases. For each new release I have to send email to everyone with notes what was changed and where to get the latest version. It works, but not always, because my team members forgot to check their emails and one day most of them instead of using latest version I send them, they used the old version and it caused me a lot of problems. Then I decided to search what solutions are out there to solve my problems and I found quite a few project, but they are not exactly I was looking for.
I wanted my solution to be able to:
Azure DevOps Services + Azure Function + Table Storage. Why? Because Azure DevOps Services is complete solution for manage backlog, builds, releases, source control. Its free (you have free private git repositories, free 240 minutes of builds, free Azure function calls).
Managing backlog is easy with board, and since it’s my pet project we dont have time for much planing so we are using Kanban board and update user stories, bugs as soon as we go.
Its very important to write understandable titles for work items because they will go to change log. To link them with release there are two options:
#task_id
then it will be linked automaticallyIn our project we are using both options :)
Now each release will have history of work items included in this release:
Depends on a project it can be easy as configure release name or a bit more specific as mine and can be set from script].
For changing Azure DevOps Release name I am using following powershell script
Write-Output "##vso[release.updatereleasename]1.10.12"
We don’t have any api yet but service hooks is where I started. They allow to use webhooks to send full information about release including artifacts, work items. So I create my service hook to dummy non existing API and triggered release, then extracted from logs my request details.
So far solution seems to be simple, Create work items in Azure Devops Backlog, Link them to task, Do release, using Service hook will use webhook to send details of release to newly created api.
As a result I’ve build 3 endpoints:
Parameters:
To save each release I am using azure table storage which is saved using:
public class FlymarkRelease : TableEntity
{
public List<ReleaseNoteItem> ReleaseNotes { get; set; }
public bool Latest { get; set; }
public bool Unstable { get; set; }
public bool ShowInLog { get; set; }
public string VersionStamp { get; set; }
public override IDictionary<string, EntityProperty> WriteEntity(OperationContext operationContext)
{
var items = base.WriteEntity(operationContext);
items[nameof(ReleaseNotes)] = new EntityProperty(JsonConvert.SerializeObject(ReleaseNotes));
return items;
}
public override void ReadEntity(IDictionary<string, EntityProperty> properties,
OperationContext operationContext)
{
base.ReadEntity(properties, operationContext);
if (properties.ContainsKey(nameof(ReleaseNotes)))
{
ReleaseNotes =
JsonConvert.DeserializeObject<List<ReleaseNoteItem>>(properties[nameof(ReleaseNotes)].StringValue);
}
}
}
There is one important thing to note when designing table storage its very important to define proper RowKey. Since table storage does not support OrderBy, Skip data will be sorted by RowKey. I need to invert my version number and store it as long so I wrote this method to do it.
public static string ConvertVersionToRowKey(string version)
{
int.TryParse(version.Replace(".", ""), out var numbericVersion);
return (int.MaxValue - numbericVersion).ToString();
}
All together validation of user version will look like
[FunctionName("release-validate-version")]
public static async Task<HttpResponseMessage> RunValidateVersion(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "release-validate-version/{program}/{version}")]
HttpRequestMessage req,
string program,
string version,
[Table(TableName)] CloudTable table,
[Blob(Container, FileAccess.Read, Connection = StorageConnectionString)]
CloudBlobContainer container)
{
var stable = await LatestStableAsync(table, program);
if (stable.VersionStamp == version)
{
return req.CreateResponse(HttpStatusCode.OK, new FlymarkVersionCheck
{
IsLatest = true
});
}
var userVersion = await UserVersionAsync(table, program, version);
var urlToDownload = UrlToDownload(program, container, version);
return req.CreateResponse(HttpStatusCode.OK, new FlymarkVersionCheck
{
IsLatest = false,
IsUnstable = userVersion?.Unstable ?? false,
LatestVersion = stable.RowKey,
Download = urlToDownload
});
}
private static async Task<FlymarkRelease> LatestStableAsync(CloudTable table, string key)
{
var query = new TableQuery<FlymarkRelease>()
.Where(TableQuery.CombineFilters(
TableQuery.GenerateFilterCondition(nameof(FlymarkRelease.PartitionKey), QueryComparisons.Equal, key.ToLower()),
TableOperators.And,
TableQuery.GenerateFilterConditionForBool(nameof(FlymarkRelease.Latest), QueryComparisons.Equal, true)));
var result = await table.ExecuteQuerySegmentedAsync(query, null);
return result.First();
}
private static async Task<FlymarkRelease> UserVersionAsync(CloudTable table, string key, string version)
{
var rowKey = ConvertVersionToRowKey(version);
var query = new TableQuery<FlymarkRelease>()
.Where(TableQuery.CombineFilters(
TableQuery.GenerateFilterCondition(nameof(FlymarkRelease.PartitionKey), QueryComparisons.Equal, key.ToLower()),
TableOperators.And,
TableQuery.GenerateFilterCondition(nameof(FlymarkRelease.RowKey), QueryComparisons.Equal, rowKey)));
var result = await table.ExecuteQuerySegmentedAsync(query, null);
return result.FirstOrDefault();
}
As you can see users version is now read by RowKey and getting release notes are now automatically sorted in right order.
Now using previously build endpoints I can compare users version and return details and notify if user needs to update their version
or just let them know that they are running unstable/beta version
or all good and its latest version
And automatic release notes:
In addition, this solution supports multiple release notes simply provide different software name as parameter program and there will be new release notes.
Its not yet finished, but there is going to be an api which will change unstable version to be latest stable. In my software there is only one latest version supported, yes there can be multiple unstable but always one stable.
Also for my mobile app I am thinking to return one more property how many versions users are behind the latest version.
A full source code can be found on github