Access azure billing API - node.js

I would like to create a dashboard with graphs about costs of my azure resources (as detailed as possible). Meaning, a list of monthly invoices is not enough (but I would already be very happy if I a could achieve that!!)
Anyway, the first thing I noticed is that if you find an example the endpoint urls look like this
https://management.azure.com/subscriptions/${subscriptionId}/resourcegroups?api-version=2016-09-01
Check the end of the url 2016-09-01, doesn't look very up2date. This medium post was the best article I could find, but it also uses these urls.
Furthermore, I was not able to follow the steps described, first it uses postman to retrieve an access_token (not very useful for me because I need it automated) and second, somewhere in the middle an access_token is retrieved but never used.
So, I found a npm packages like [azure-arm-billing][2] from which I was able to write the following program (mostly copy-paste):
const msRestAzure = require('ms-rest-azure');
const BillingManagement = require('azure-arm-billing')
const clientId = process.env['CLIENT_ID'];
const secret = process.env['APPLICATION_SECRET'];
const domain = process.env['DOMAIN'];
const subscriptionId = process.env['AZURE_SUBSCRIPTION_ID'];
// Retrieve access_token
const app = new msRestAzure.ApplicationTokenCredentials(clientId, domain, secret);
app.getToken((err, token) => {
console.log(token.accessToken);
});
// =======
msRestAzure
.interactiveLogin( { domain }) // The argument here is nowhere documented
.then(credentials => {
console.log(credentials);
let client = new BillingManagement(credentials, subscriptionId);
return client.invoices.list();
})
.then(invoices => {
console.log('List of invoices:');
console.dir(invoices, { depth: null, colors: true });
});
Running this shows a nice access_token and invoices
...
List of invoices:
[
{
id: '/subscriptions/../providers/Microsoft.Billing/invoices/....',
name: '...',
type: 'Microsoft.Billing/invoices',
invoicePeriodStartDate: 2019-08-25T00:00:00.000Z,
invoicePeriodEndDate: 2019-09-24T00:00:00.000Z,
billingPeriodIds: [
'/subscriptions/.../pr..s/Micro..ing/bill..ods/201910-1'
]
},
{
id: '/subscriptions/9ea...3d/providers/Microsoft.Billing/invoices/201909-...',
name: '....',
type: 'Microsoft.Billing/invoices',
invoicePeriodStartDate: 2019-07-25T00:00:00.000Z,
invoicePeriodEndDate: 2019-08-24T00:00:00.000Z,
billingPeriodIds: [
'/subscriptions/..../providers/Microsoft.Billing/billingPeriods/201909-1...'
]
}
]
Although I have my invoices, there are no numbers. And I would like to retrieve costs for every resources.
So the documentation seems to be outdated up to not existing for what I want (as it seems). My question is if someone was able to retrieve information like this? I would really like to know how!!
UPDATE
It seems to be a permission issue. So, below I share some screenshots showing what I have right now. Maybe from these it is clear what I miss or have setup incorrectly. So first, here is my latest nodejs app:
const msRestAzure = require("ms-rest-azure");
const ConsumptionManagementClient = require("azure-arm-consumption");
const clientId = '76d79....'; // App registration ID
const secret = '****...'; // App registration secret
const domain = 'dc36...'; // tenantId
const subscriptionId = '9ea2d...'; // subscription ID
const AzureServiceClient = msRestAzure.AzureServiceClient;
//an example to list resource groups in a subscription
msRestAzure.loginWithServicePrincipalSecret(clientId, secret, domain).then((creds) => {
const client = new ConsumptionManagementClient(creds, subscriptionId);
const expand = '';
const filter = '';
const skiptoken = '';
const top = 1000;
const apply = '';
return client.usageDetails.list(expand, filter, skiptoken, top, apply).then(result => {
console.log('The result is:', result);
});
}).catch((err) => {
console.log('An error occurred:');
console.dir(err, { depth: null, colors: true });
});
Which outputs a statusCode 401
Error: Unauthorized. Request ID: e6b127...
...
So, I have in AD an App registration
Its API permissions are
Finally, I have just one subscription
With the following IAM settings
Any suspicious?

If you're looking for resource costs, I would suggest that you take a look at Consumption API - List Usage Details. That will give you the consumption for all the resources.
You will need to install azure-arm-consumption package.
Here's the sample code:
const msRestAzure = require("ms-rest-azure");
const ConsumptionManagementClient = require("azure-arm-consumption");
msRestAzure.interactiveLogin().then((creds) => {
const subscriptionId = "<your subscription id>";
const client = new ConsumptionManagementClient(creds, subscriptionId);
const expand = "";
const filter = "";
const skiptoken = "";
const top = 1000;
const apply = "";
return client.usageDetails.list(expand, filter, skiptoken, top, apply).then((result) => {
console.log("The result is:");
console.log(result);
});
}).catch((err) => {
console.log('An error occurred:');
console.dir(err, {depth: null, colors: true});
});
This is taken from here: https://github.com/Azure/azure-sdk-for-node/tree/master/lib/services/consumptionManagement.

Related

Unable to update an item in CosmosDB using the replace method with JavaScript

I am trying to create a basic REST API using Azure functions and the cosmosDB client for JavaScript. I have been successful with all the actions except the UPDATE. The cosmosDB client uses conainter.item(id,category).replace(newObject) I am unable to get the container.item().replace method to work. When I test the function in the portal or using Postman, I get a 500 error and in the portal, I get the error: Result: Failure Exception: Error: invalid input: input is not string Stack: Error: invalid input: input is not string at trimSlashFromLeftAndRight.
Example of my basic document/item properties
{
id:002,
project:"Skip rope",
category:"task",
completed: false
}
const config = require("../sharedCode/config");
const { CosmosClient } = require("#azure/cosmos");
module.exports = async function (context, req) {
const endpoint = config.endpoint;
const key = config.key;
const client = new CosmosClient({ endpoint, key });
const database = client.database(config.databaseId);
const container = database.container(config.containerId);
const theId = req.params.id;
// I am retrieving the document/item that I want to update
const { resource: docToUpdate } = await container.item(theId).read();
// I am pulling the id and category properties from the retrieved document/item
// they are used as part of the replace method
const { id, category } = docToUpdate;
// I am updating the project property of the docToUpdate document/item
docToUpdate.project = "Go fly a kite";
// I am replacing the item referred to with the ID with the updated docToUpdate object
const { resource: updatedItem } = await container
.item(id, category)
.replace(docToUpdate);
const responseMessage = {
status: 200,
message: res.message,
data: updatedItem,
};
context.res = {
// status: 200, /* Defaults to 200 */
body: responseMessage,
};
};
I Googled the heck out of this and been through the Microsoft Azure CosmosDB documents from top-to-bottom, but I can't figure out how to get this to work. I can get the other CRUD operations to work based on the examples Microsoft docs provide, but not this. Any help would be greatly appreciated.
I believe the reason you’re getting this error is because the data type of your “id” field is numeric. The data type of “id” field should be string.
UPDATE
So I tried your code and was able to run it successfully. There was one issue I noticed in your code though:
const { resource: docToUpdate } = await container.item(theId).read();
In the above line of code, you are not specifying the partition key value. If you don't specify the value, then your docToUpdate would come as undefined. In my code I used task as partition key value (I created a container with /category as the partition key).
This is the code I wrote:
const { CosmosClient } = require("#azure/cosmos");
const endpoint = 'https://account.documents.azure.com:443/';
const key = 'accountkey==';
const databaseId = 'database-name';
const containerId = 'container-name';
// const docToUpdate = {
// 'id':'e067cbae-1700-4016-bc56-eb609fa8189f',
// 'project':"Skip rope",
// 'category':"task",
// 'completed': false
// };
async function readAndUpdateDocument() {
const client = new CosmosClient({ endpoint, key });
const database = client.database(databaseId);
const container = database.container(containerId);
const theId = 'e067cbae-1700-4016-bc56-eb609fa8189f';
const { resource: docToUpdate } = await container.item(theId, 'task').read();
console.log(docToUpdate);
console.log('==============================');
const { id, category } = docToUpdate;
docToUpdate.project = "Go fly a kite";
console.log(docToUpdate);
console.log('==============================');
const { resource: updatedItem } = await container
.item(id, category)
.replace(docToUpdate);
console.log(updatedItem);
console.log('==============================');
}
readAndUpdateDocument();
Can you try by using this code?

Firebase Functions extracting Token

Trying to grab my FCM token from my Cloud Firestore using Firebase Function
my function code:
const functions = require("firebase-functions");
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
exports.sendNotificationToFCMToken = functions.firestore.document('Posts/{likes}').onWrite(async (event) => {
const title = event.after.get('title');
const content = event.after.get('likes');
let userDoc = await admin.firestore().doc('Users').get();
let fcmToken = userDoc.get('{token}');
var message = {
notification: {
title: title,
body: "you have a new like",
},
token: fcmToken,
}
let response = await admin.messaging().send(message);
console.log(response);
});
My Firestore
Posts:
Users:
if I manually add the token everything works but just send every "like" to one device, my goal is to send a link to only the owner of the post
It's probably more alike this:
let userRef = event.after.get('ref'); // obviously the path is mandatory ...
let userDoc = await admin.firestore().doc(userRef).get(); // then this should match
let token = userDoc.get('token'); // and the token should be accessible
Add logging to see what you get: functions.logger.info('🞬🞬🞬 ' + JSON.stringify(event)); ...viewable at https://console.cloud.google.com/logs/query. When listening for Posts/{likes} you'd likely need an additional query - and when listening for Posts, you'd need to determine changes. Getting access to ref is required to make the subsequent query work.
Martin's answer is correct, but it seems that the ref field is of type Reference, see the slash at the beginning, plus the error you get.
So, if this assumption is correct, you should use the path property, as follows (adapting Martin's code):
let userRef = event.after.get('ref'); // obviously the path is mandatory ...
let userDoc = await admin.firestore().doc(userRef.path).get(); // then this should match
let token = userDoc.get('token'); // and the token should be accessible
In addition, in order to correctly manage the life cycle of your Cloud Function, you should do, at the end:
let response = await admin.messaging().send(message);
console.log(response);
return null;
or simply
return admin.messaging().send(message);

check MFA is enabled for a user using rest api

How do I check MFA is enabled for AD users using rest API loginWithServicePrincipalSecret
is there anyone who can help me out to do this....I want to do this using node sdk like this
require("isomorphic-fetch");
const { UserAgentApplication } = require("msal");
const { ImplicitMSALAuthenticationProvider } = require("#microsoft/microsoft-graph-client/lib/src/ImplicitMSALAuthenticationProvider");
const { MSALAuthenticationProviderOptions } = require("#microsoft/microsoft-graph-client/lib/src/MSALAuthenticationProviderOptions");
const msalConfig = {
auth: {
clientId: "bec52b71-dc94-4577-9f8d-b8536ed0e73d", // Client Id of the registered application
},
};
const graphScopes = ["user.read", "mail.send"]; // An array of graph scopes
const msalApplication = new UserAgentApplication(msalConfig);
const Options = new MSALAuthenticationProviderOptions(graphScopes);
const authProvider = new ImplicitMSALAuthenticationProvider(
msalApplication,
Options
);
const options = {
authProvider,
};
const Client = require("#microsoft/microsoft-graph-client");
const client = Client.init(options);
async function test() {
try {
let res = await client
.api("/reports/credentialUserRegistrationDetails")
.version("beta")
.get();
console.log("res: ", res);
} catch (error) {
throw error;
}
}
test();
This is possible with MS Graph API,
To Get information of users registered with MFA and hasn't, we can use isMfaRegistered property in credentialUserRegistrationDetails .
credentialUserRegistrationDetails help us to get the details of the
usage of self-service password reset and multi-factor authentication
(MFA) for all registered users. Details include user information,
status of registration, and the authentication method used.
This is possible programmatically with MS Graph where you will get a JSON reports an can be plugged into other reports or can be represented programmatically itself
Example:
GET https://graph.microsoft.com/beta/reports/credentialUserRegistrationDetails
sample output:
{
"id": "****************************",
"userPrincipalName": "NKS#nishantsingh.live",
"userDisplayName": "Nishant Singh",
"isRegistered": false,
"isEnabled": true,
"isCapable": false,
"isMfaRegistered": true,
"authMethods": [
"mobilePhone"
]
}
Sample code for your Node JS,
const options = {
authProvider,
};
const client = Client.init(options);
let res = await client.api('/reports/credentialUserRegistrationDetails')
.version('beta')
.get();
To implement your NodeJS code please go through step-by-step guide in MS Documentation

Unable to regenerate storage key with Azure Management API

I can't use /regenerateKey [1] to regenerate keys for a Storage Account with the Azure Management API.
I'm using the following code in JavaScript (the resource has the subscription removed)
const { ClientSecretCredential } = require('#azure/identity');
const { SecretClient } = require('#azure/keyvault-secrets');
const MSRestAzure = require('ms-rest-azure');
const keyVaultName = process.env.KEY_VAULT_NAME;
const KVUri = `https://${keyVaultName}.vault.azure.net`;
const credential = new ClientSecretCredential(
process.env.AZURE_TENANT_ID,
process.env.AZURE_CLIENT_ID,
process.env.AZURE_CLIENT_SECRET,
);
const vault = new SecretClient(KVUri, credential);
function getCreds() {
return new Promise((res, rej) => {
MSRestAzure.loginWithServicePrincipalSecret(
process.env.AZURE_CLIENT_ID,
process.env.AZURE_CLIENT_SECRET,
process.env.AZURE_TENANT_ID,
(err, creds) => {
if (err) {
rej(err);
return;
}
res(creds);
},
);
});
}
const getResourceUrl = (resource, action) => `https://management.azure.com${resource}/${action}?api-version=2019-04-01`;
const resource = "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/myRg/providers/Microsoft.Storage/storageAccounts/MyStore
const creds = await getCreds();
const client = new MSRestAzure.AzureServiceClient(creds);
const regenUrl = getResourceUrl(resource, 'regenerateKey');
await client.sendRequest({ method: 'POST', url: regenUrl }).then(console.log);
I'm getting an UnexpectedException response -
{
"error": {
"code": "UnexpectedException",
"message": "The server was unable to complete your request."
}
}
The Client ID/Secret belongs to an app registration that has access to the storage account, as well as Contributor and Storage Account Key Operator over that subscription.
I'm lead to think that I've not formed the request properly.
I am able to reproduce the error if I don't specify the request body.
Please provide the request body in the following format:
{
keyName: "key1 or key2 (basically which key you want to regenerate)"
}

How to get user by email?

I am using graph-service to access Microsoft Graph API. Following this document to get user by email. I have write a block of code:
var user = null;
const GraphService = require('graph-service');
const ClientCredentials = require('client-credentials');
const tenant = 'my-company.com';
const clientId = '0b13aa29-ca6b-42e8-a083-89e5bccdf141';
const clientSecret = 'lsl2isRe99Flsj32elwe89234ljhasd8239jsad2sl=';
const credentials = new ClientCredentials(tenant, clientId, clientSecret);
const service = new GraphService(credentials);
service.all('/users').then(response => {
console.log(response.data);
response.data.forEach(function(item) {
if (item.userPrincipalName == "lgomez#my-company.com"){
user = item;
break;
}
});
});
But response.data returns a list of users with cost much resource to get a single user object by email. Please help me to update the code which can return only a single object of user by email.
The operation /users you are using, is for retreiving a list of all users.
You can request as single user by calling:
/users/{id | userPrincipalName}
This operation either accepts an userId or an userPrincipleName
e.g.
/users/foo#bar.com
[more information is in the documentation]
In your case the code could be altered to:
//...
var myUPN = "lgomez#my-company.com"
service.get('/users/' + myUPN).then(response => {
console.log(response.data);
});

Resources