diff --git a/rest/ibm-iam/README.md b/rest/ibm-iam/README.md new file mode 100644 index 0000000..a19140c --- /dev/null +++ b/rest/ibm-iam/README.md @@ -0,0 +1,25 @@ +# IBM IAM Token + +## Overview + +Defines a field `Query.ibm_iam_token` that obtains an IBM Cloud IAM token. + +This can be used in as the first step of a sequence to obtain a token for a subsequent execution +of an `@rest` field in the sequence. + +An example (`Query.usage`) is provided of accessing the IBM Cloud billing usage endpoint using +a `@sequence` to fetch a token and then make the REST request. + +The same technique can be used with any IAM system. + +## Try it out + +The schema must be deployed with the environment variable `STEPZEN_IBM_IAM_APIKEY` set +to an IBM Cloud API key, typically by a CI/CD workflow. + +Then a request such as this can be executed, replacing `<>` with an IBM Cloud account ID +that matches the IBM Cloud API key: + +``` +stepzen request '{usage(account:"<>" month:"2025-01") }' +``` diff --git a/rest/ibm-iam/config.yaml b/rest/ibm-iam/config.yaml new file mode 100644 index 0000000..827dc28 --- /dev/null +++ b/rest/ibm-iam/config.yaml @@ -0,0 +1,5 @@ +configurationset: +- configuration: + endpoint: https://iam.cloud.ibm.com/identity/token + apikey: STEPZEN_IBM_IAM_APIKEY + name: ibm-iam diff --git a/rest/ibm-iam/ibm-iam.graphql b/rest/ibm-iam/ibm-iam.graphql new file mode 100644 index 0000000..3de1d0c --- /dev/null +++ b/rest/ibm-iam/ibm-iam.graphql @@ -0,0 +1,33 @@ +extend type Query { + """ + Obtain an IBM Cloud bearer token. + + Uses the [IBM Cloud API Key](https://cloud.ibm.com/docs/account?topic=account-userapikey&interface=ui#userapikey) + or [service ID's API Key](https://cloud.ibm.com/docs/account?topic=account-serviceidapikeys&interface=ui) + to [generate an IAM Token](https://cloud.ibm.com/docs/account?topic=account-iamtoken_from_apikey#iamtoken_from_apikey) + """ + ibm_iam_token: Secret + @rest( + endpoint: "$endpoint" + method: POST + contenttype: "x-www-form-urlencoded" + postbody: """ + grant_type=urn:ibm:params:oauth:grant-type:apikey&apikey={{ .Get "apikey"}} + """ + ecmascript: """ + function transformREST(body) { + switch (status) { + case 200: + return body + case 401: + case 400: // returned for apikey not found + throw new Error('unauthorized'); + default: + throw new Error('unknown error'); + } + } + """ + setters: { path: "access_token" } + configuration: "ibm-iam" + ) +} diff --git a/rest/ibm-iam/index.graphql b/rest/ibm-iam/index.graphql new file mode 100644 index 0000000..cc6c8a3 --- /dev/null +++ b/rest/ibm-iam/index.graphql @@ -0,0 +1,48 @@ +schema @sdl(files: ["ibm-iam.graphql"]) { + query: Query +} + +""" +Example use of `Query.ibm_iam_token`. + +First fetches a token and then executes a REST request using the token. +""" +type Query { + """ + IBM Cloud account usage. + """ + usage( + """ + IBM Cloud account ID. + """ + account: String! + """ + Billing month with format `yyyy-mm` + """ + month: String! + ): JSON + @sequence( + steps: [ + { query: "ibm_iam_token" } + { + query: "_usage" + arguments: [ + { name: "token", field: "ยง0" } + { name: "account", argument: "account" } + { name: "month", argument: "month" } + ] + } + ] + ) + + """ + Fetch requesst against the usage endpoint. + + https://cloud.ibm.com/apidocs/metering-reporting#get-account-usage + """ + _usage(token: Secret!, account: String!, month: String!): JSON + @rest( + endpoint: "https://billing.cloud.ibm.com/v4/accounts/$account/usage/$month" + headers: { name: "Authorization", value: "Bearer $token" } + ) +} diff --git a/rest/ibm-iam/stepzen.config.json b/rest/ibm-iam/stepzen.config.json new file mode 100644 index 0000000..af1c0ea --- /dev/null +++ b/rest/ibm-iam/stepzen.config.json @@ -0,0 +1,3 @@ +{ + "endpoint": "api/miscellaneous" +} diff --git a/rest/ibm-iam/tests/Test.js b/rest/ibm-iam/tests/Test.js new file mode 100644 index 0000000..1596c11 --- /dev/null +++ b/rest/ibm-iam/tests/Test.js @@ -0,0 +1,11 @@ +const { + deployAndRun, + getTestDescription, +} = require("../../../tests/gqltest.js"); + +testDescription = getTestDescription("snippets", __dirname); + +describe(testDescription, function () { + const tests = []; + return deployAndRun(__dirname, tests); +});