You've created resources with the btp CLI in the previous exercise, specifically a directory and a subaccount. In this exercise, to layer on a bit more experience and understanding, you're going to remove those resources using the appropriate API, and as a bonus, make another call to the jobs management API to see the status of the deletion operation, which is asynchronous.
It's worth double-checking what you're going to delete, and to get everything ready for action.
👉 Have a look at the hierarchy of directories and subaccounts:
btp get accounts/global-account --show-hierarchy | trunc
In the output that ensues, identify the directory and subaccount that you created in the previous exercise. The output should look similar to this, and you can see the parent/child relationship between the two resources (highlighted with arrows):
Showing details for global account fdce9323-d6e6-42e6-8df0-5e501c90a2be...
├─ 8fe7efd4trial (fdce9323-d6e6-42e6-8df0-5e501c90a2be - global account)
│ ├─ trial (f78e0bdb-c97c-4cbc-bb06-526695f44551 - subaccount)
│ ├─ codejam-directory (f4c7d60e-627c-4fab-8e67-603b20b84f72 - directory) <----
│ │ ├─ codejam-subaccount (be63bfda-070a-49a8-ab26-03153b16617e - subaccount) <----
type: id: display name: parent id: parent
global account fdce9323-d6e6-42e6-8df0-5e501c90a2be 8fe7efd4trial
subaccount cd76fdef-16f8-47a3-954b-cab6678cc24d testsubaccount fdce9323-d6e6-42e6-8df0-5e501c90a2be global
subaccount f78e0bdb-c97c-4cbc-bb06-526695f44551 trial fdce9323-d6e6-42e6-8df0-5e501c90a2be global
directory f4c7d60e-627c-4fab-8e67-603b20b84f72 codejam-directory fdce9323-d6e6-42e6-8df0-5e501c90a2be global
subaccount be63bfda-070a-49a8-ab26-03153b16617e codejam-subaccount f4c7d60e-627c-4fab-8e67-603b20b84f72 direct
👉 Check that you can still use the btpguid
script (that you created a symbolic link to in your $HOME/bin/
directory in the previous exercise) to get the GUIDs of these resources:
btpguid codejam-directory; btpguid codejam-subaccount
You should see output similar to this - i.e. the two GUIDs:
f4c7d60e-627c-4fab-8e67-603b20b84f72
be63bfda-070a-49a8-ab26-03153b16617e
In a previous exercise, we used the /entitlements/v1/globalAccountAllowedDataCenters
endpoint of the Entitlements Service API. This time, we need to look at endpoints in the Accounts Service API.
👉 Take a moment to look at the API reference for the Entitlements Service API, and you'll see that there are three groups of endpoints: "Manage Assigned Entitlements", "Regions for Global Account" and "Job Management":
The Job Management group contains a single generic endpoint that is common to many of the APIs in this package.
The endpoint we used was within the "Regions for Global Account" group.
This time, we need to find an endpoint that supports operations on directories and subaccounts, and in the Core Services APIs overview we can see that the description for the Accounts Service API sounds like what we're looking for: "Manage the directories and subaccounts in your global account's structure."
👉 Select the Accounts Service API in the SAP Business Accelerator Hub, go to the API Reference section, and take note of the endpoint groups, which are:
- Global Account Operations
- Directory Operations
- Subaccount Operations
- Job Management
Within the Directory Operations group, you'll see that there's a specific combination of HTTP method and endpoint thus:
DELETE /accounts/v1/directories/{directoryGUID}
👉 Take a look at this endpoint, the required scope, and the parameters:
Back in Exercise 05 - Preparing to call a Core Services API, specifically in the section Understanding what's required for a token request, we noted that the central
plan for the SAP Cloud Management service for SAP BTP (cis
) provided greater access than the other plan(s).
👉 Go back to the SAP Cloud Management - Service Plans and check through the scopes offered with the central
plan. Make sure you can see that it offers this scope:
global-account.account-directory.delete
This is the scope that we see as a requirement to be able to make a DELETE request to this /accounts/v1/directories/{directoryGUID}
endpoint.
So with our current access token, we should be all set. Right?
Well, yes, but "we should be all set" is a bit vague, don't you think? Let's spend a couple of minutes verifying this more precisely.
Back when we requested the token in Exercise 06 we got a whole blob of data, first to the terminal, and then captured into a file tokendata.json
.
We'll want to take a closer look at this data, so let's make the file available in the directory related to this exercise.
👉 Move to the appropriate directory and create a symbolic link to the file:
cd $HOME/projects/cloud-btp-cli-api-codejam/exercises/09-deleting-resources-with-api/
ln -s ../06-core-services-api-creds/tokendata.json .
👉 Check you can explore the contents from this same directory, like this:
jq keys tokendata.json
jq .access_token tokendata.json
This should show you the keys (properties) in the JSON contained in tokendata.json
, along with the big lump that is the access token.
Unless you're taking this CodeJam very slowly, over days, the access token should still be good - it lasts for 12 hours (you can use
jq -r '.expires_in / 60 / 60 | round' tokendata.json
to check).
Let's examine that "big lump" that is the access token. There doesn't seem to be much (if any) structure to it, but if we know what it is, we can find the tools to help us. There's an open Web standard RFC7519, otherwise known as "JSON Web Token". This is shortened to "JWT" and is commonly pronounced as "jot" (make of that what you will). You can find a link to an introduction to JWTs in the Further reading section.
The access token is a JWT.
There are many tools that can help you with JWTs. One is jwt
, which will parse and display the contents of a JWT. Let's use it to look inside the access token.
In your working environment, you already have a Node.js runtime, and along with that, the npm
package manager. There's a handy jwt-cli
package that contains the jwt
command line interface. Let's install that. We'll install it "globally" so it's independent of any NPM project. But that "global" location will be just local to us.
Installing the package globally to a local location is not standard, but it will allow us to have the same experience whether we use a Dev Space or a container. In the Dev Space environment, the user has write access to the real global locations in the UNIX (Linux) file system. But in the container, based on the standard Debian distribution of Linux, this is not the case. So we'll specify a different prefix for the
npm
installer to use.
👉 First, set a custom prefix for npm
in the configuration, and make sure the location that the prefix points to exists as a directory:
npm config set prefix $HOME/npm/
mkdir $HOME/npm/
👉 Now install the package, using the --global
option (which can be shortened to -g
):
npm install -g jwt-cli
👉 To satisfy your curiosity, have a look at what has been installed, like this:
find $HOME/npm/ -maxdepth 5
This should produce output that looks something like this:
/home/user/npm
/home/user/npm/lib
/home/user/npm/lib/node_modules
/home/user/npm/lib/node_modules/jwt-cli
/home/user/npm/lib/node_modules/jwt-cli/.prettierrc.json
/home/user/npm/lib/node_modules/jwt-cli/.prettierignore
/home/user/npm/lib/node_modules/jwt-cli/src
/home/user/npm/lib/node_modules/jwt-cli/src/input.js
/home/user/npm/lib/node_modules/jwt-cli/src/jwt.js
/home/user/npm/lib/node_modules/jwt-cli/src/output.js
/home/user/npm/lib/node_modules/jwt-cli/index.js
/home/user/npm/lib/node_modules/jwt-cli/LICENSE.txt
/home/user/npm/lib/node_modules/jwt-cli/CONTRIBUTING.md
/home/user/npm/lib/node_modules/jwt-cli/README.md
/home/user/npm/lib/node_modules/jwt-cli/package.json
/home/user/npm/lib/node_modules/jwt-cli/node_modules
/home/user/npm/lib/node_modules/jwt-cli/node_modules/minimist
/home/user/npm/lib/node_modules/jwt-cli/node_modules/ecdsa-sig-formatter
/home/user/npm/lib/node_modules/jwt-cli/node_modules/fast-jwt
/home/user/npm/lib/node_modules/jwt-cli/node_modules/safer-buffer
/home/user/npm/lib/node_modules/jwt-cli/node_modules/obliterator
/home/user/npm/lib/node_modules/jwt-cli/node_modules/safe-buffer
/home/user/npm/lib/node_modules/jwt-cli/node_modules/mnemonist
/home/user/npm/lib/node_modules/jwt-cli/node_modules/bn.js
/home/user/npm/lib/node_modules/jwt-cli/node_modules/inherits
/home/user/npm/lib/node_modules/jwt-cli/node_modules/color-convert
/home/user/npm/lib/node_modules/jwt-cli/node_modules/color-name
/home/user/npm/lib/node_modules/jwt-cli/node_modules/has-flag
/home/user/npm/lib/node_modules/jwt-cli/node_modules/minimalistic-assert
/home/user/npm/lib/node_modules/jwt-cli/node_modules/asn1.js
/home/user/npm/lib/node_modules/jwt-cli/node_modules/ansi-styles
/home/user/npm/lib/node_modules/jwt-cli/node_modules/chalk
/home/user/npm/lib/node_modules/jwt-cli/node_modules/supports-color
/home/user/npm/bin
/home/user/npm/bin/jwt
👉 Take note of the item /home/user/npm/bin/jwt
- that's the executable that we need. Let's create a symbolic link to it in our $HOME/bin/
directory where the rest of our custom executables are, and then check to see what it looks like:
ln -s $HOME/npm/bin/jwt $HOME/bin/
ls -l $HOME/bin/jwt
This should show the link, something like this:
lrwxrwxrwx 1 user user 22 Sep 8 13:36 /home/user/bin/jwt -> /home/user/npm/bin/jwt
Now we have a jwt
executable that we can use from anywhere.
👉 Try it now, sending the value of the access token, and it will parse it and display the contents.
jq --raw-output .access_token tokendata.json | jwt
Along with other output, you should see various sections displayed, something like this:
✻ Header
{
"alg": "RS256",
"jku": "https://8fe7efd4trial-ga.authentication.eu10.hana.ondemand.com/token_keys",
"kid": "default-jwt-key--57cafe828",
"typ": "JWT"
}
✻ Payload
{
"jti": "e7858cafea9344f392f0dc8b64bdc014",
"ext_attr": {
"enhancer": "XSUAA",
"globalaccountid": "fdce9323-d6e6-42e6-8df0-5e501c90a2be",
"zdn": "8fe7efd4trial-ga",
"serviceinstanceid": "4a66cafe-6978-4c05-9fb4-95dac2a625b3"
},
"xs.system.attributes": {
"xs.rolecollections": [
"Global Account Administrator"
]
},
"given_name": "Blue",
"xs.user.attributes": {},
"family_name": "Adams",
"sub": "0fe5eeef-60aa-4607-9bbe-2aacafe89347",
"scope": [
"cis-central!b14.global-account.subaccount.update",
"cis-central!b14.global-account.update",
"cis-central!b14.global-account.subaccount.delete",
"cis-central!b14.global-account.subaccount.read",
"cis-central!b14.job.read",
"cis-central!b14.catalog.product.update",
"cis-central!b14.catalog.product.delete",
"cis-central!b14.global-account.account-directory.create",
"cis-central!b14.directory.entitlement.update",
"cis-central!b14.global-account.entitlement.read",
"xs_account.access",
"cis-central!b14.event.read",
"cis-central!b14.directory.entitlement.read",
"cis-central!b14.global-account.account-directory.read",
"openid",
"cis-central!b14.global-account.entitlement.subaccount.update",
"cis-central!b14.global-account.account-directory.update",
"uaa.user",
"cis-central!b14.global-account.read",
"cis-central!b14.global-account.account-directory.delete",
"cis-central!b14.global-account.region.read",
"cis-central!b14.global-account.subaccount.create"
],
"client_id": "sb-ut-a71edd26-eb55-4443-adff-402fe561cafe-clone!b123443|cis-central!b14",
"cid": "sb-ut-a71edd26-eb55-4443-adff-402fe561cafe-clone!b123443|cis-central!b14",
"azp": "sb-ut-a71edd26-eb55-4443-adff-402fe561cafe-clone!b123443|cis-central!b14",
"grant_type": "password",
"user_id": "0fe5eeef-60aa-4607-9bbe-2aa2a5cafe47",
"origin": "sap.default",
"user_name": "[email protected]",
"email": "[email protected]",
"auth_time": 1660912670,
"rev_sig": "be1d11ba",
"iat": 1660912670,
"exp": 1660955870,
"iss": "https://8fe7efd4trial-ga.authentication.eu10.hana.ondemand.com/oauth/token",
"zid": "fdcafe23-d6e6-42e6-8df0-5e501c90a2be",
"aud": [
"cis-central!b14.global-account.subaccount",
"openid",
"xs_account",
"cis-central!b14.global-account.entitlement.subaccount",
"cis-central!b14.global-account.region",
"sb-ut-a71edd26-eb55-4443-adff-402fe5612a9b-clone!b123443|cis-central!b14",
"cis-central!b14.global-account.entitlement",
"cis-central!b14.event",
"cis-central!b14.global-account.account-directory",
"cis-central!b14.directory.entitlement",
"cis-central!b14.global-account",
"uaa",
"cis-central!b14.catalog.product",
"cis-central!b14.job"
]
}
iat: 1660912670 8/19/2022, 12:37:50 PM
exp: 1660955870 8/20/2022, 12:37:50 AM
✻ Signature wQOTOYR61Gz_H0nvM8Dyiv4qIPnHDtJSbBFRuA4HF4ExyVKQBxbGzVXG5qr6mtaWthVLms4X_CwsXV1uLVhtVQJdq1SChFnpDHDJVlRvygIQOnkyZuZhXc4ssIsBJT2rgv95fWY9ICERWCZtbjyIqtZ21fxbdSUhlizr3bcJsvpLloX7clwe2JUANK5eAoh6Zsiy3f_qpgUC2TWf0rjimz8TEN19mxormy3RGCtO7pHAUiU-2hPIjOAsAzm4p742URlsS1xlvnWatmHix--VduiBpHs-QRt1pCgZkqWQDdSKWgaSPGguThCTy7Zn2jcWLtQDb2jLWFb-zWASPvcGOA
That's wonderful! We can see within the "scope" section of the payload that the scope we need is indeed there:
"cis-central!b14.global-account.account-directory.delete"
In fact, with our lights-out hats on, we would want to go one step further to be able to automate the checking of this, if we needed to. Rather than have to visually identify the appropriate section in the output, we can use the --output=json
option with jwt
to get a predictable and parseable output in JSON format, that we can then feed into jq
to pick out what we want.
👉 Try this now:
jq -r .access_token tokendata.json \
| jwt --output=json \
| jq .payload.scope
This gives us something like this:
[
"cis-central!b14.global-account.subaccount.update"
"cis-central!b14.global-account.update"
"cis-central!b14.global-account.subaccount.delete"
"cis-central!b14.global-account.subaccount.read"
"cis-central!b14.job.read"
"cis-central!b14.catalog.product.update"
"cis-central!b14.catalog.product.delete"
"cis-central!b14.global-account.account-directory.create"
"cis-central!b14.directory.entitlement.update"
"cis-central!b14.global-account.entitlement.read"
"xs_account.access"
"cis-central!b14.event.read"
"cis-central!b14.directory.entitlement.read"
"cis-central!b14.global-account.account-directory.read"
"openid"
"cis-central!b14.global-account.entitlement.subaccount.update"
"cis-central!b14.global-account.account-directory.update"
"uaa.user"
"cis-central!b14.global-account.read"
"cis-central!b14.global-account.account-directory.delete"
"cis-central!b14.global-account.region.read"
"cis-central!b14.global-account.subaccount.create"
]
We can of course go one step further and look for the scopes that describe the delete level, like this:
jq -r .access_token tokendata.json \
| jwt --output=json \
| jq '
.payload.scope
| map(select(.|endswith(".delete")))
'
This will give us a smaller list to check:
[
"cis-central!b14.global-account.subaccount.delete",
"cis-central!b14.catalog.product.delete",
"cis-central!b14.global-account.account-directory.delete"
]
We've identified that the endpoint we want looks like this: /accounts/v1/directories/{directoryGUID}
. This is relative, of course, and must be suffixed to the Accounts Service API's service URL. Where's that? It's in the same place that we found the service URL for the Entitlements Service API, i.e. in the service key (binding) information. So let's look in there.
👉 Just like you did for the tokendata.json
file, create a symbolic link in this exercise's directory to the cis-central-sk.json
file we created back in Exercise 06:
ln -s ../06-core-services-api-creds/cis-central-sk.json .
We don't of course absolutely need references to these files in our current directory, but it's a bit more comfortable when referring to them.
👉 Let's now remind ourselves of the main properties in this service key information:
jq '.credentials | keys' cis-central-sk.json
You should see a list of keys like this:
[
"endpoints",
"grant_type",
"sap.cloud.service",
"uaa"
]
👉 Now dig in to the endpoints
key:
jq .credentials.endpoints cis-central-sk.json
You'll see output similar to this, which should include an accounts_service_url
key and value:
{
"accounts_service_url": "https://accounts-service.cfapps.eu10.hana.ondemand.com",
"cloud_automation_url": "https://cp-formations.cfapps.eu10.hana.ondemand.com",
"entitlements_service_url": "https://entitlements-service.cfapps.eu10.hana.ondemand.com",
"events_service_url": "https://events-service.cfapps.eu10.hana.ondemand.com",
"external_provider_registry_url": "https://external-provider-registry.cfapps.eu10.hana.ondemand.com",
"metadata_service_url": "https://metadata-service.cfapps.eu10.hana.ondemand.com",
"order_processing_url": "https://order-processing.cfapps.eu10.hana.ondemand.com",
"provisioning_service_url": "https://provisioning-service.cfapps.eu10.hana.ondemand.com",
"saas_registry_service_url": "https://saas-manager.cfapps.eu10.hana.ondemand.com"
}
So we can see that the service URL is https://accounts-service.cfapps.eu10.hana.ondemand.com
.
We now have everything we need to make the call, just like we did in an earlier exercise.
Let's do it, taking a cautious first step, then committing to the deletion.
We'll determine the dynamic values step by step and assign them to variables, rather than try to do everything in one go.
👉 Save the service URL in a variable, and also the GUID of the directory:
url=$(jq --raw-output .credentials.endpoints.accounts_service_url cis-central-sk.json)
guid=$(btpguid codejam-directory)
Starting cautiously, let's try first to read the directory information, using the HTTP GET method. If you check the Directory Operations group of endpoints for the Accounts Service API you'll see that there's a family of HTTP method + URL path combinations:
GET /accounts/v1/directories/{directoryGUID}
(read)DELETE /accounts/v1/directories/{directoryGUID}
(delete)PATCH /accounts/v1/directories/{directoryGUID}
(update)
👉 OK, try to read the directory information with this curl
invocation:
curl \
--url "$url/accounts/v1/directories/$guid" \
--header "Authorization: Bearer $(jq -r .access_token tokendata.json)" \
| jq .
This invocation also uses the access token from the
tokendata.json
file like we did in the previous API call. It also will default to the HTTP GET method.
After the progress bar that curl
displays (remember, you can suppress this with --silent
), you should see something like this:
{
"guid": "f4c7d60e-627c-4fab-8e67-603b20b84f72",
"parentType": "ROOT",
"globalAccountGUID": "fdce9323-d6e6-42e6-8df0-5e501c90a2be",
"displayName": "codejam-directory",
"createdDate": 1660820297434,
"createdBy": "[email protected]",
"modifiedDate": 1660820297434,
"entityState": "OK",
"stateMessage": "Directory created.",
"directoryType": "FOLDER",
"directoryFeatures": [
"DEFAULT"
],
"contractStatus": "ACTIVE",
"consumptionBased": false,
"parentGuid": "fdce9323-d6e6-42e6-8df0-5e501c90a2be",
"parentGUID": "fdce9323-d6e6-42e6-8df0-5e501c90a2be"
}
Great, that's the information for the directory that we're about to delete.
👉 Now make another call, this time explicitly specifying the DELETE method:
curl \
--request DELETE \
--url "$url/accounts/v1/directories/$guid" \
--header "Authorization: Bearer $(jq -r .access_token tokendata.json)" \
| jq .
This is the sort of response that you should see
{
"error": {
"code": 20013,
"message": "Could not delete entity with GUID f4c7d60e-627c-4fab-8e67-603b20b84f72. Entity is not empty.",
"target": "/accounts/v1/directories/f4c7d60e-627c-4fab-8e67-603b20b84f72",
"correlationID": "8ba8c975-6661-4e02-4fba-912455b6a36b"
}
}
The response makes sense, and is preventing us from doing something that we might not want to do.
👉 Just because we're curious, let's have a look to see what HTTP response code is returned when we try this; first, add the --verbose
option to curl
to ask it to tell us everything that's going on, and also remove the pipe to jq
, as the response will be a mix of text (the verbose output) and JSON (the response body) which of course jq
will not parse:
curl \
--request DELETE \
--verbose \
--url "$url/accounts/v1/directories/$guid" \
--header "Authorization: Bearer $(jq -r .access_token tokendata.json)"
In the response header output (the lines are denoted by the <
symbol) we should see a 409 status code, which will appear differently depending on the version of HTTP used:
< HTTP/1.1 409 Conflict
or
< HTTP/2 409
The 409 Conflict response status is appropriate, for two reasons:
- it's in the 4xx series, denoting it was the client (i.e. us) that is in error, not the server (see the Further reading section for a link to a 2 minute video on HTTP where this is covered)
- it indicates a request conflict with the current state of the target resource (in this case the directory), which is true ("entity is not empty")
In the scope and parameter detail of the /accounts/v1/directories/{directoryGUID}
endpoint that we looked at earlier in Check we have the right access, you might have noticed this parameter:
forceDelete
This is a boolean parameter that is false by default. Setting it to true will specify that we want the directory and everything it might contain to be deleted. Note that the parameter is specified with the word (query)
, signifying that it should be specified in the query string (i.e. as part of the URL), rather than as a name/value pair in any sort of request payload. Let's try that now.
👉 Use this curl
invocation, noting the addition of the query string, and the use of braces around the variable name to ensure that it doesn't get mixed up with any subsequent query string characters (this is shell parameter expansion, see Further reading for more details)):
curl \
--request DELETE \
--verbose \
--url "$url/accounts/v1/directories/${guid}?forceDelete=true" \
--header "Authorization: Bearer $(jq -r .access_token tokendata.json)" \
> >(jq .) \
2> >(tee curl.stderr >&2)
This should return fairly quickly, but successfully, and produce output like this:
{
"guid": "f4c7d60e-627c-4fab-8e67-603b20b84f72",
"parentType": "ROOT",
"globalAccountGUID": "fdce9323-d6e6-42e6-8df0-5e501c90a2be",
"displayName": "codejam-directory",
"createdDate": 1660820297434,
"createdBy": "[email protected]",
"modifiedDate": 1660921399071,
"entityState": "DELETING",
"directoryType": "FOLDER",
"directoryFeatures": [
"DEFAULT"
],
"contractStatus": "ACTIVE",
"consumptionBased": false,
"parentGuid": "fdce9323-d6e6-42e6-8df0-5e501c90a2be",
"parentGUID": "fdce9323-d6e6-42e6-8df0-5e501c90a2be"
}
It's the same output that we saw when we used the HTTP GET method. So what's going on?
Well, first of all, we can see that the entityState
now has a value of DELETING
. So that's a good sign. Moreover, because we used the --verbose
option, we can have a closer look at the details of what's returned; you should see some HTTP response headers in the output, similar to this:
< HTTP/1.1 200 OK
< cache-control: no-cache, no-store, max-age=0, must-revalidate
< content-type: application/json
< date: Fri, 19 Aug 2022 15:03:18 GMT
< expires: 0
< location: /jobs-management/v1/jobs/8926898/status
< pragma: no-cache
< vary: origin,access-control-request-method,access-control-request-headers,accept-encoding
< x-content-type-options: nosniff
< x-correlationid: f58ca769-44f6-41e4-7ab7-300d83c192b7
< x-frame-options: DENY
< x-vcap-request-id: f58ca769-44f6-41e4-7ab7-300d83c192b7
< x-xss-protection: 1; mode=block
< transfer-encoding: chunked
< strict-transport-security: max-age=31536000; includeSubDomains; preload;
Note the location
header in the response. It points to a relative URL that looks like this:
/jobs-management/v1/jobs/8926898/status
Guess what that relates to? Yes, the deletion activity is asynchronous, and is handled in a background job. And yes, the Job Management endpoint group that we saw briefly earlier is directly related to this.
Let's finish this exercise with a final API call, this time to the single endpoint in the Accounts Service Job Management endpoint group:
/jobs-management/v1/jobs/{jobInstanceIdOrUniqueId}/status
You'll notice that this endpoint reflects what was given to us in the location
HTTP response header above, which is really convenient.
From our foray into the scopes that we have in our access token, we might have noticed that we have the scope that we need to make this API call, too:
"cis-central!b14.job.read"
So let's get to it!
While curl
sends the response body to STDOUT (the JSON, in this case), it sends the verbose details of what went on, including the request and response headers, to a separate data stream, specifically STDERR. The eagle-eyed amongst you may have noticed that this STDERR output from this most recent call to curl
has been also captured to a file called curl.stderr
, as well as being output to the terminal (this is the 2> >(tee curl.stderr >&2)
bit in the invocation above).
This means that we can extract the value of that location
header from the file mechanically, and avoid any copy/paste action. As you'll see, we'll use grep
to do that, with tr
to remove LF and CR characters.
👉 Make another curl
invocation to the URL which is a combination of the Accounts Service base URL, in $url
, which looks something similar to this: https://accounts-service.cfapps.eu10.hana.ondemand.com
, plus this job-specific API endpoint, which we'll grab from the value of the location
header in the curl.stderr
file:
curl \
--url "$url$(grep -Po '(?<=^< location: )(.+)$' curl.stderr | tr -d '\n\r')" \
--header "Authorization: Bearer $(jq -r .access_token tokendata.json)" \
| jq .
Depending on how fast you are, and how slow the deletion is taking, you'll get some sort of response that tells you where the job is at. Here's an example of what you might get:
{
"status": "COMPLETED"
}
Great! Not only is the job complete, but this exercise is complete too. Well done!
At this point your confidence in discovering APIs, preparing what you need to call them, and making the calls, should have grown. Not only that, you've experienced all this at the lowest level, where you can see everything that is going on, and there's nothing hidden from you, nothing magic that you just have to make assumptions about.
If you finish earlier than your fellow participants, you might like to ponder these questions. There isn't always a single correct answer and there are no prizes - they're just to give you something else to think about.
- In the HTTP response to the DELETE request, what was the status code, and are there any others that might be appropriate?