Text
BC's Vaccine Card
On September 7th, 2021, the Government of BC revealed the BC Vaccine Card, which uses the SMART Health Card QR code format. This page describes what I found by examining BC's card.
Getting Your Card
To get your BC Vaccine Card, you need to visit https://gov.bc.ca/vaccinecard, provide some identifying information, then you're brought to a page that provides a website where you can get your card, and presumably show it at your gym or at a restaurant. Downloading the card to your phone so you don't need to log in is a little awkward. There isn't a convenient way to save it to your phone, so I just took multiple screen shots and stitched them together as a PDF, which I saved in the Files app in iOS. I added a Shortcut to add an icon on my homescreen to make it convenient to use. It would be a big improvement if you could add this to your Apple Wallet or Health app, or the WalletPasses app on Android. Even just a mobile formatted PDF would be better.
Examining the Card
I took a screenshot of my card, and used a simple Python script I cobbled together based on an example online by marcan2020. Here is the (anonymized) data I got out of my card:
JWS Header: { "alg": "ES256", "zip": "DEF", "kid": "XCqxdhhS7SWlPqihaUXovM_FjU65WeoBFGc_ppent0Q" } SHC Data: { "iss": "https://smarthealthcard.phsa.ca/v1/issuer", "nbf": 1631039838.0, "vc": { "type": [ "https://smarthealth.cards#covid19", "https://smarthealth.cards#immunization", "https://smarthealth.cards#health-card" ], "credentialSubject": { "fhirVersion": "4.0.1", "fhirBundle": { "resourceType": "Bundle", "type": "collection", "entry": [ { "fullUrl": "resource:0", "resource": { "resourceType": "Patient", "name": [ { "family": "ARMSTRONG", "given": [ "PATRICK" ] } ], "birthDate": "1970-01-01" } }, { "fullUrl": "resource:1", "resource": { "resourceType": "Immunization", "status": "completed", "vaccineCode": { "coding": [ { "system": "http://hl7.org/fhir/sid/cvx", "code": "208" }, { "system": "http://snomed.info/sct", "code": "28581000087106" } ] }, "patient": { "reference": "resource:0" }, "occurrenceDateTime": "2020-01-01", "lotNumber": "XX1234", "performer": [ { "actor": { "display": "Vancouver Convention Centre" } } ] } }, { "fullUrl": "resource:2", "resource": { "resourceType": "Immunization", "status": "completed", "vaccineCode": { "coding": [ { "system": "http://hl7.org/fhir/sid/cvx", "code": "208" }, { "system": "http://snomed.info/sct", "code": "28581000087106" } ] }, "patient": { "reference": "resource:0" }, "occurrenceDateTime": "2020-01-01", "lotNumber": "YY1234", "performer": [ { "actor": { "display": "Vancouver Convention Centre" } } ] } } ] } } } }
The contents of the card are straightforward, and Dr. Vishnu Ravi's post, How do Verifiable Vaccination Records with SMART Health Cards Work? covers the details. I won't repeat them since they're well covered in that post.
In case you're wondering, this says my name, and that I got two shots of the Pfizer BioNTech MRNA vaccine at the Vancouver Convention Centre (I changed the DOB, Lot number, and dates that I got the shots).
0 notes
Text
A Handy Introduction to Cloud Optimized GeoTIFFs
I wrote about GeoTIFF metadata over on the Planet Blog.
0 notes
Text
Using JDBI with Spring Boot
JDBI is a nice little ORM for Java applications. It offers a nice level of abstraction to make it simple to map SQL to Java objects without a ton of confusing overhead. Spring Boot is a nice framework to make Spring applications without a lot of configuration. That said, I found it quite confusing the first time I tried to wire up JDBI classes to Spring Boot, and only figured it out eventually by reading the JDBI source, and scouring github for projects that use JDBI. Dropwizard, on the other hand, makes it easy to integrate JDBI, but if you like Spring Boot, you can still use JDBI.
Here's a quick guide to getting started with JDBI in Spring Boot. I created a GitHub repository to go along with this code that you can follow along with if you like.
Set Up Your Spring Boot Project
There's plenty of good documentation to explain how to set up a Spring Boot project, so I'll skip explaining that step here. We'll start with a project that followed the quick start guide. Open up your Application.java file, then we'll add the necessary parts in turn to wire things up.
First, we add a datasource to our project in Application.java:
@Bean @ConfigurationProperties(prefix = "spring.datasource") public DataSource dataSource() { return DataSourceBuilder.create().build(); }
This is a pretty standard spring datasource. You'll also want to add a configuration for this datasource to your application.properties file:
spring.datasource.url = jdbc:h2:mem:test spring.datasource.driver-class-name = org.h2.Driver spring.datasource.initialize = true
And create an h2 schema.sql in your resources that will be initialized when we start our app:
CREATE TABLE example ( id BIGINT NOT NULL AUTO_INCREMENT, name VARCHAR(255) );
This is the same as any other ORM so far.
Wiring Things Up
Now for the tricky bits in your Application.java:
@Autowired @Bean public DBI dbi(DataSource dataSource) { synchronized (DBI.class) { return new DBI(dataSource); } }
This bean creates a new DBI instance that without spring you would be creating with new. Next, we wire a JDBI DAO object that uses the JDBI Object API:
@Bean @Autowired public ExampleDao exampleDao(DBI dbi) { return dbi.onDemand(ExampleDao.class); }
(The source to this DAO object is available on github)
That's it. You can now use your ExampleDao anywhere in your springified objects. There's a use of the DAO in the controller in the example code. However, it kind of feels like a lot of work to manually create a bean for every DAO.
Wiring Things Up Without Manually Creating Beans
One solution to simplify things is to wrap each DAO object in another higher level object, with the inner class only containing abstract JDBI methods, and if you need extra Java code to fill in JDBI's gaps (such as constricting WHERE clauses) you can put this in the outer Java class. Here's an example:
package hellojdbi; import org.skife.jdbi.v2.DBI; import org.skife.jdbi.v2.sqlobject.Bind; import org.skife.jdbi.v2.sqlobject.BindBean; import org.skife.jdbi.v2.sqlobject.GetGeneratedKeys; import org.skife.jdbi.v2.sqlobject.SqlQuery; import org.skife.jdbi.v2.sqlobject.SqlUpdate; import org.skife.jdbi.v2.sqlobject.customizers.Define; import org.skife.jdbi.v2.sqlobject.customizers.RegisterMapper; import org.skife.jdbi.v2.sqlobject.stringtemplate.UseStringTemplate3StatementLocator; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Repository; import java.util.List; @Repository public class ExampleDao {
private ExampleDb db;
@Autowired
public void setDbi(DBI dbi) {
this.db = dbi.onDemand(ExampleDb.class);
}
public long create(Example example) {
return db.create(example);
}
public Example get(long id) {
return db.get(id);
}
public List<example> getWhere(String name, long minimumId) {
String where = String.format("name = '%s' AND id > %s", name, minimumId);
return db.getWhere(where);
}
@UseStringTemplate3StatementLocator
@RegisterMapper(ExampleMapper.class)
public static abstract class ExampleDb {
@SqlUpdate("INSERT INTO example (name) values (:name)")
@GetGeneratedKeys
abstract long create(@BindBean Example example);
@SqlQuery("SELECT * FROM example WHERE id = :id")
abstract Example get(@Bind("id") long id);
@SqlQuery("SELECT * FROM example WHERE <where>")
abstract List<example> getWhere(@Define("where") String where);
}
}
Now you can remove the bean you added earlier in your Application.java, and things will still work, no need to manually wire up the DAO. This has a nice side effect of separating the abstract class that JDBI consumes from DAO code that is written in Java. You can see the code wired up like this in the github repository.
Now you can easily plug your DAO code into your controllers or business logic classes, without needing to manually wire them up.
Using your DAO
Here is an example controller that uses our ExampleDAO:
package hellojdbi; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.List; import java.util.stream.Collectors; @RestController public class HelloController { private ExampleDao exampleDao; @Autowired public void setExampleDao(ExampleDao exampleDao) { this.exampleDao = exampleDao; } @RequestMapping("/") public String index() { Example example = new Example(); example.setName("My Example"); long exampleId = exampleDao.create(example); Example createdExample = exampleDao.get(exampleId); List<example> wheres = exampleDao.getWhere("My Example", 0); String wheresJoined = wheres.stream() .map(Example::getId) .map(i -> i.toString()) .collect(Collectors.joining(", ")); return String.format("Created example '%s' with id '%d'\nFound %s", createdExample.getName(), createdExample.getId(), wheresJoined); } }
That's it! Pretty straightforward once you understand what needs to be done, but not so simple to figure out without some documentation.
Feel free to get at me on twitter if something is wrong or confusing.
0 notes
Text
Running a Powershell Script on Boot on Windows Azure
Recently while working on Dell Cloud Manager we wanted to be able to install our agent by providing a custom Powershell script to run at launch on a Windows VM instance on Azure.
In the Linux cloud world, it's common to send a custom script in the custom or user data that's available to the VM, and launch it automatically like an init script. On Amazon EC2 this is called User Data, on Azure its called Custom Data and GCE supports it as well.
These scripts can be used to do something like kickstart an Ansible or Chef install, install an app, or ping some coordination service.
On EC2, its pretty easy to get this working with Windows. You simply install the Windows EC2 Config Service, which comes installed on EC2 Windows images by default.
As near as I can tell (and I did a lot of Googling), I couldn't find an equivalent for Windows images on Azure. This seemed crazy to me since their blog even explains how to do this on Linux on Azure.
The process is simple enough. You need to create a script that launches on startup, and then executes the powershell script in CustomData.bin. I've created a script called cloudinit.ps1 that does just that. Here's how to create an image with it preinstalled:
1: Launch a Windows Image on Azure with DCM, the Azure console, or command line, and log in:
$ azure vm create cloudinit-test a699494373c04fc0bc8f2bb1389d6106__Windows-Server-2012-R2-20150916-en.us-127GB.vhd oldpatricka supersecretpassword --vm-size Basic_A2 --location 'Central US'
Now log into your Windows VM with RDP.
2: Open Powershell
3: Get cloudinit.ps1:
PS C:\> cd \ PS C:\> Invoke-Webrequest https://gist.githubusercontent.com/oldpatricka/e57a43ea4ce27accbddf/raw/d79ba7398aed399c165d7360c29863b146fa37ca/cloudinit.ps1 -Outfile cloudinit.ps1
4: Run Local Group Policy Editor to make the script run on startup:
PS C:\> gpedit.msc
Select Local Computer Policy, then Computer Configuration, then Windows Settings, then Scripts (Startup/Shutdown)
Next, double click Startup in the right pane:
Then pick the Powershell Scripts tab:
Choose Add..., then browse to C:\cloudinit.ps1, and click OK, then OK again, then close the Local Group Policy Editor.
5: Run sysprep to prepare to save the image:
PS C:\> C:\Windows\system32\sysprep\sysprep.exe /oobe /generalize /shutdown
6: When the VM is stopped, make an image in DCM, the Azure console, or the cli:
$ azure vm capture cloudinit-test cloudinit-img --delete
7: You can now launch a new instance that will run a Powershell script specified on the command line automatically:
$ azure vm create cloudinit-test $your_new_image oldpatricka supersecretpassword --vm-size Basic_A2 --location 'Central US' --custom-data your-powershell-script.ps1
It would be great if this was a pre-installed, first class feature of Azure Windows VMs, but this script seems to work for now.
0 notes