Event Information
Annotated links: Episode 52 of Hands-on SAP dev with qmacro
This is a searchable description of the content of a live stream recording, specifically “Ep.52 – Tidying up the JS script for Business Rules OAuth flow” in the “Hands-on SAP devwith qmacro” series. There are links directly to specific highlights in the video recording. For links to annotations of other episodes, please see the “Catch the replays” section of the series blog post.
This episode was streamed live on Fri 07 Feb 2020 and is approximately 60 minutes in length. The stream recording is available on YouTube.
Brief synopsis: In Ep.51 we finished on a cliffhanger – having retrieved a bearer token by authenticating using the resource owner password credentials grant flow, we made the call to the runtime API endpoint, but got a 401. Find out why in this episode, and work with me on making the script neater and more reusable.
00:01:55 Stream starts.
00:04:55 Highlighting a series of blog posts from Witalij Rudnicki on the subject of containers in general, and Docker in particular. I’m enjoying these posts, which Witalij has tagged with the user tag UnderstandContainers so you can find them easily. Thanks Witalij!
00:06:25 Highlighted this morning by Jakob Kjaer is a post on deploying an ABAP stack server on Kubernetes, by Richard Treu – Proof of Concept: Deploying ABAP in Kubernetes. A fascinating experiment, even more so because of my recent interest in containers and Kubernetes.
00:08:15 This post reminded me that I wanted to show a lovely example of a Terminal User Interface (TUI), in the form of a frontend for managing Kubernetes resources – k9s. It’s an implementation of a wonderful (and well established) idea – the ability to have a user interface that’s efficient, attractive to power users, with great key bindings, no requirement to use the mouse, and can run pretty much anywhere.
00:08:50 Showing off my recent “functor” tshirt, which I like very much 🙂
00:10:00 Connecting via ssh
to my host ‘gargantubrain’ (an old MBP on the shelves behind me), to demonstrate k9s
which I’d already installed and connected to the Kubernetes installation provided by Minikube.
I mentioned that I found it useful that while I was the user ‘qmacro’ on the local host ‘penguin’ I didn’t have to explicitly specify the user ‘dj’ that I needed to be over on ‘gargantubrain’ – normally it would have attempted to connect as ‘qmacro@gargantubrain’ which would have failed. And that’s because of the richness of ssh
which I’ve been looking into as part of my learning list for 2020.
I didn’t actually show how I’d set that up, so I’ll explain now; I maintain ssh
configuration in ~/.ssh/config
and it looks like this:
Host pixel
Port 8022
Host github.com
ForwardX11 no
Host c02w4e8ff9zq
User i347491
Host zino gargantubrain
User dj
Host *
ForwardX11 yes
So you can see that part of this says that when connecting to ‘gargantubrain’, connect as the user ‘dj’. Nice!
For those wondering, I like to forward X11, another one of my 2020 topics to dig into more, when I’m connecting to any host, apart from ‘github.com’ of course, which is just to use ssh to authenticate to interact with repositories with git
.
(And yes, some of you guessed that the name ‘gargantubrain’ comes from The Hitch Hiker’s Guide To The Galaxy of course).
00:11:20 Talking of Kubernetes, I mention a great book that I’m reading, by Jeff Geerling, called Ansible for Kubernetes, which is teaching me not only about Kubernetes but about Ansible too, and also Docker. Recommended.
Moreover, talking of Docker, I bring up another TUI in the form of dry – a Docker manager for the terminal. We have a look at this too, having paused briefly to marvel at the power of running tools without having to install them – as Docker containers … which in turn reminds me of npx
. We are in the future, right?
00:16:00 The final part of today’s preamble is about GitHub Packages which gives us citizen developers the ability to have our own Node.js modules (or Docker images, and other packages) hosted in a registry. The democratisation of NPM registries! (I couldn’t remember this word on the stream and “made up” a word “peopleisation” which of course is the same thing (δῆμος / demos = people).
00:19:30 Oh yes and I got to use my rules.bash
script to recreate the Business Rules setup in my freshly minted (post-expired and destroyed) trial subaccount related Cloud Foundry environment. I wrote about this script in a recent blog post Scripting Cloud Foundry activities in trial.
00:24:30 Moving now to the task at hand for this episode, which was to fix and enhance the Node.js script that we wrote in the previous episode (<ahref=”https://bit.ly/handsonsapdev#ep51″>Ep.51) to call the business rule service we’d created.
I’d already modified the OAuth token request credentials to match the new Business Rules service setup, so we first ran the script to remind ourselves of what the problem was – a 401 ‘unauthorized’ error.
The problem had been that I hadn’t properly set the defaults on the request object I’d set up. I’d written this:
req = request.defaults({
Authorization: 'Bearer ' + token.accessToken,
}),
but of course I wanted to set default headers (of which Authorization
is one), so it should have been like this:
req = request.defaults({
headers: {
Authorization: 'Bearer ' + token.accessToken,
}
}),
What a fool.
00:27:50 Explaining what parts of the script I was not particularly enamoured about – specifically the hard-coding of the OAuth credential information (and, worse, the repetition of it, seeing that it’s already stored in my ~/.netrc
file), and the lazy way of specifying the URLs for the authorisation and runtime API endpoints.
00:29:55 To address the first of these issues, there’s a nice netrc package that we can use. Thisallows us to get rid of the hard coded section that looks like this:
client = new oauth2client({
clientId: 'sb-clone-b9cdd759-e0ad-4f1d-b0be-b38f2da71f95!b34464|bpmrulebroker!b2466',
clientSecret: 'xs+ExbQiieTZLBX857nQOGkbKj4=',
accessTokenUri: 'https://qmacrosubdomain.authentication.eu10.hana.ondemand.com/oauth/token'
})
and replace it with this:
const
netrc = require('netrc'),
...
uaaHost = 'i347491trial.authentication.eu10.hana.ondemand.com',
authInfo = new netrc()[uaaHost],
client = new oauth2client({
clientId: authInfo.login,
clientSecret: authInfo.password,
...
})
Much neater!
00:37:00 After a brief bit of confusion where I mistook ‘uri’ for ‘uri-js’, we light upon the uri-jspackage which will help us manage our URLs more cleanly, to address the second of the issues.
00:37:40 While riffing on the fact that the ‘http(s)’ part of a URL is called the ‘scheme’, I digress slightly into other schemes,and reflecting on the gopher protocol, which John Murray points out is still in use today. Who knew?! I also mention a couple of old browsers that I used to use, Cello and Viola. Gosh.
00:38:20 Using this ‘uri-js’ module, we can rewrite the opaque URL strings and construct them, instead, like this for the OAuth token request endpoint:
const
uri = require('uri-js'),
...
uaaHost = 'i347491trial.authentication.eu10.hana.ondemand.com',
...
client = new oauth2client({
...
accessTokenUri: uri.serialize({
scheme: 'https',
host: uaaHost,
path: '/oauth/token'
})
})
and like this, for the Business Rules runtime API base endpoint:
runtimeApiEndpoint = uri.serialize({
scheme: 'https',
host: 'bpmruleruntime.cfapps.eu10.hana.ondemand.com',
path: '/rules-service/rest'
})
(remember that all of the Cloud Foundry Rule Execution API resources have a base path of /rules-service/rest
– we double-check this by looking into the environment configuration for my ‘EU10’ environment.)
00:51:10 Thinking about a further possible enhancement, specifically how we’d go about caching the OAuth token once received, instead of requesting a new one each time, which is what we’re doing right now. This leads into a small investigation as to whether requesting a new token returns a new one each time, which (after some confusion based on foolishness on my part) it appears it does, sending a fresh expiration (43199 seconds) each time (this is what Huseyin Dereli had already guessed).
Here’s what the entire contents of the retrieved token object look like – there’s plenty for us to be able to use to cache and calculate remaining lifetime:
ClientOAuth2Token {
client:
ClientOAuth2 {
options:
{ clientId:
'sb-clone-fe154511-fa4d-4b59-9865-92404f5ae664!b35963|bpmrulebroker!b2466',
clientSecret: 'KreSs824GXFFPJy8WaAVWOPmI/w=',
accessTokenUri:
'https://i347491trial.authentication.eu10.hana.ondemand.com/oauth/token' },
request: [Function: request],
code: CodeFlow { client: [Circular] },
token: TokenFlow { client: [Circular] },
owner: OwnerFlow { client: [Circular] },
credentials: CredentialsFlow { client: [Circular] },
jwt: JwtBearerFlow { client: [Circular] } },
data:
{ access_token:
'eyJhbGciO...',
token_type: 'bearer',
id_token:
'eyJhbGciO...',
refresh_token: '9e0de6aec13a4788871b6b3ce33c5e32-r',
expires_in: 43199,
scope:
'bpmrulecompiler!t2466.RuleCompiler.JavaCompile bpmrulecompiler!t2466.RuleCompiler.SqlCompile openid bpmrulesqlcompiler!t2466.RuleSqlCompiler.SqlCompile bpmrulerepository!t2466.RuleRepository.SuperUser bpmruleruntime!t2466.RuleRuntime.SuperUser',
jti: '2acab2bd037a429dbe805d9f90155cdd' },
tokenType: 'bearer',
accessToken:
'eyJhbGciO...',
refreshToken: '9e0de6aec13a4788871b6b3ce33c5e32-r',
expires: 2020-02-07T23:50:54.001Z }
(This, by the way, also explains why I was almost certain I’d seen an actual timestamp in the expiry information before now, whereas today I didn’t. It’s because the date – in this case 2020-02-07T23:50:54.001Z
– is part of the object properties, and not returned in the access_token
property itself.)
This is a nice place to bring the stream to an end, and in fact that is where it does end! I hope you enjoyed it, and that it inspires you to think a bit more about OAuth, tokens and related topics over the weekend. Happyruminating!
Thanks DJ for another instructive and fun episode 🙂 I wish I had time to join all the episodes 🙁
Thanks Huseyin, it was great to see you online, and I hope to see you again in an episode soon! I'm really happy you liked it, and also had fun 🙂