Event Information
Annotated links: Episode 51 of Hands-on SAP dev with qmacro
This is a searchable description of the content of a live stream recording, specifically “Ep.51 – More fun with Business Rules API and OAuth” in the “Hands-on SAP dev with 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 31 Jan 2020 and is approximately 60 minutes in length. The stream recording is available on YouTube.
Brief synopsis: In the previous episode we got as far as staring at the OAuth Bearer token that we could use to authenticate calls to the API. In this episode we see how far we can get in using one of those tokens to do so. Perhaps even a bit of JavaScript with Axios might be nice to look at too – promises promises! (geddit)
00:01:55 Stream starts.
00:03:00 Talking briefly about “behind the scenes” of the HandsOnSAPDev “studio” (which I changed around a little bit recently so the video is showing me in a different angle).
We see my setup on the main desk, which includes the Blue Yeti microphone and my single 4K monitor which, when streaming, I switch to run at twice the resolution of 720p (1280×720), i.e. 2560×1440, so that I can use my version of the Tiling Window Manager for Chrome OS extension (which I’ve modified to give me the ability to adjust the gaps between windows) to run four 720p sized windows, one of which (the top left one) I capture and stream. This minimises scaling and the untidy visual artifacts that go with that sort of thing. That also means I’ve got three more windows to manage the streaming and other stuff.
00:05:40 A small warning that my typing may be louder than usual as I’ve switched back from my Anne Pro 2 (with Gateron Brown switches) to my Vortex Race 3 which has Cherry MX Blue switches – tactile AND clicky, nice!
00:06:00 Reminding folks about the very first conference on the SAP Cloud Application Programming Model, reCAP, which is taking place in Heidelberg, Germany, on Fri 15 May 2020. I’m looking forward to this very much and hope to be able to make it. The call for papers is open, so head over and submit a session proposal!
00:07:50 Talking about the recent Coffee Corner Radio podcast episode which has an interview with the one and only Tom Jung our new head of Developer Advocacy at SAP.
00:08:55 Looking at what we’re going to cover in the main part of this episode, which is to continue with the SAP Business Rules API. We hadn’t got as far as calling the runtime API last time, so this time that’s what we’ll aim to do, starting with some Bash script goodness in the terminal to get us warmed up.
00:11:30 Reminding ourselves of the different APIs that we have for the SAP Business Rules on the API Hub, which can be divided up into APIs for Neo and Cloud Foundry, for designtime (‘authoring’) and runtime (‘execution’), and for different versions.
Last time, we used the designtime API endpoint /v1/projects
to get a brief summary of our Business Rules project (airline discounts).
00:13:00 To get back into the swing of things, we switch to a terminal running tmux
and open a couple of panes to run simple scripts that will monitor the apps and services in my Cloud Foundry space. I wrote about these scripts, and related subjects, in a couple of recent blog posts: Scripting Cloud Foundry activities in trial and Mini adventures with MTAs and the Cloud Foundry CLI.
00:16:20 Having a look at the gettoken.bash
script, and where we get some of the information that it requires. Here’s the entirety of the script:
#!/bin/bash
# Run this script in the context of an eval $(...) to get a fresh
# access token set in TOKEN
curl \
--silent \
--netrc \
--header "Content-Type: application/x-www-form-urlencoded" \
--data "grant_type=password&response_type=token&username=$USERNAME&password=$(pass SAP/trial)" \
https://qmacrosubdomain.authentication.eu10.hana.ondemand.com/oauth/token \
| jq -r '"export TOKEN=\(.access_token)"'
The purpose of this script is to retrieve a fresh access token and present it in a way that can be eval
‘d into the environment. It does this by calling the authentication endpoint (qmacrosubdomain.authentication.eu10.hana.ondemand.com/oauth/token
in this case) and passing the appropriate credentials and request information.
Note that the authentication for this endpoint is Basic Authentication with the username and password provided by the ‘netrc’ facility which we’ve seen on recent previous live stream episodes – basically, credentials are managed on a per-host basis in a private .netrc
file and curl
will use those when told to with the --netrc
option.
00:17:10 On the subject of token requests in OAuth flows, there’s a really nice post by Carlos Roggan – SAP Cloud Platform Backend service: Tutorial [14]: about OAuth mechanism – which explains lots of things and is, along with its companion blog posts, well worth reading, not least to understand more about the different OAuth based authentication mechanisms, or ‘grant types’.
00:17:50 The grant type we’re going to use is the ‘resource owner password credentials grant type’, and is also described in the documentation for the NPM module client-oauth2 that we’re about to use in our JavaScript code.
This grant type, or flow, is when the client is trusted by the user, because the user gives the client their credentials to send to the OAuth endpoint. This is different to other flows where the client is not trusted, and in these cases the user is temporarily redirected to be asked for their credentials, and a short-lived token is returned to the client with which an access token can then be requested.
00:18:55 For managing my credentials securely in my terminal, I’m using the classic pass
password manager which you can see in action in the $(pass SAP/trial)
part of the script.
00:20:25 Having retrieved a token, we look briefly at the other data that is returned with it, including the value of the expires_in
property, which is 43199. If you weren’t on the live stream at the time, can you make a guess as to why that is a significant number? Put your thoughts in the comments to this post.
00:21:00 Once we have a token in the TOKEN
environment variable, we can make the actual API call to /v1/projects
(this is still warming ourselves up from last week) to retrieve information from the designtime API, about the projects we have. This is in the getprojectinfo.bash
script which looks like this:
#!/bin/bash
curl \
--silent \
--header "Authorization: Bearer ${TOKEN}" \
https://bpmrulerepository.cfapps.eu10.hana.ondemand.com/rules-service/rest/v1/projects
That all works fine. So far so good.
00:23:00 Thinking now about calls not to the designtime API but to the runtime API, we pause briefly to think about the version management that has been introduced to the Business Rules service, which is explained by Archana Shukla in the blog post Version Management in SAP Cloud Platform Business Rules, and means that our new airline discounts rule project that we’re currently authoring is accessible to call as a ‘working set’ rule, for which we need the /v2/workingset-rule-services
endpoint.
00:23:50 We try out this call in the SAP API Hub using my ‘EU10’ environment that I’ve already got set up, using a payload that I’d stored in payload.json
, that looks like this:
{
"RuleServiceId": "8ebbf08e24f84790a99f1e8f32be59a9",
"Vocabulary": [
{
"flightinfo": {
"carrier": "LH"
}
}
]
}
I think that’s a nice way of calling rules in runtime, nicer than the previous version (v1).
By the way, the value for the RuleServiceId
property is available in the URL when we’re looking at the business rule service within the project in the Business Rules Editor.
00:26:55 We copy the getprojectinfo.bash
script to a new one, callrule.bash
which we modify to look like this:
#!/bin/bash
curl \
--silent \
--header "Authorization: Bearer ${TOKEN}" \
-d@payload.json \
--header "Content-Type: application/json" \
https://bpmruleruntime.cfapps.eu10.hana.ondemand.com/rules-service/rest/v2/workingset-rule-services
As you can see, we’re calling the runtime API (bpmruleruntime.cfapps.eu10.hana.ondemand.com
in my case) with the /v2/workingset-rule-services
endpoint, passing the contents of payload.json
using the -d@
option to curl
. Of course, we pass the token we already have as a bearer token in the Authorization
header of the request.
00:30:00 Starting to move across to doing this now in JavaScript, and we initialise a new Node.js project callrule
… and while I do that Christian Drumm gently challenges me on my continued use of the Bash shell, rather than, say, the Z shell.
00:32:10 At this stage we go back and take a closer look at the client-oauth2 that we glanced at earlier, as this is what we’re going to use for the OAuth flow.
We also look at the request-promise package for making the actual HTTP requests, in a promise context (this package is essentially a wrapper around the request package). This is a deprecated package but serves our purpose for now.
00:33:50 First, we install the nodemon package as a development dependency – everyone likes a bit of nodemon, right? We start running it in a new tmux
pane and create the simplest thing that could possibly work to satisfy nodemon, in a sort of “nodemon driven development” style.
00:55:30 After lots of thinking and typing, we end up with something that looks like this in index.js
:
const
oauth2client = require('client-oauth2'),
request = require('request-promise'),
client = new oauth2client({
clientId: 'sb-clone-b9c9-e0ad-4f1d-b0be-b38f2da71f95!b34464|bpmrulebroker!b2466',
clientSecret: 'xs+ExbQ-eTZLBX857nQOGkbKj4=',
accessTokenUri: 'https://qmacrosubdomain.authentication.eu10.hana.ondemand.com/oauth/token'
})
async function main() {
const
token = await client.owner.getToken(process.env.USERNAME, process.env.PASSWORD)
console.log(token.accessToken)
const
req = request.defaults({
headers: { Authorization: 'Bearer ' + token.accessToken },
})
const result = await req.post('https://bpmruleruntime.cfapps.eu10.hana.ondemand.com/rules-service/rest/v2/workingset-rule-services', {
json: {
RuleServiceId: "8ebbf08e24f84790a99f1e8f32be59a9",
Vocabulary: [
{
flightinfo: {
carrier: "LH"
}
}
]
}
})
console.log(result)
}
main()
You can get a good idea of what each part of this does by watching this section of the recording, but here’s a brief rundown too:
- We use a set of
const
declarations to bring in the two modules we want to use (client-oauth2 and request-promise), and to initialise a new client-oauth2 object that has the client ID and secret set, along with the authorisation endpoint, ready for calls to be made (this set of ID and secret is now no longer in existence, in case you’re wondering). - As we’re using a promise-based request mechanism, we can wrap the actual calls inside a function
main
that’s declared as asynchronous, and then call it right at the bottom of the script (withmain()
) when everything is ready. This reminds me a little of the Python pattern involving__name__ == "__main__"
, but that’s a story for another time I guess. Anyway, that allows us to make the actual calls in a synchronous fashion, usingawait
. - Inside the
main
function we pass the user credentials (remember this is the ‘resource owner password credentials’ grant type flow) in the call to actually request a token (getToken
). We get the credential values from environment variables so you don’t see them in the script. - Assuming that a token is returned successfully (remember, this is just demo code and not robust!) we make an HTTP POST request with the request-promise module, having first set some defaults for requests with the
request.defaults
call, setting the bearer token in an Authorization header. - The request-promise’s
post
mechanism is very convenient, allowing us to send JSON directly via thejson
parameter, which we do, supplying the same payload as we’ve seen already to invoke the business rule.
Unfortunately (or fortunately, if you like live streams that end with some suspense!) I forget something small but fundamental in the code, which we don’t manage to catch before the hour is up. For those of you following this in the future, I’ll give you a clue – it’s in the call to request.defaults
, and I will cover it in Ep.52 which at the time of writing this post is tomorrow! 🙂
That wrapped it up for this episode. Tune in for the next one for some more (hopefully exciting) hands-on stuff with SAP development!