cli-microsoft365 icon indicating copy to clipboard operation
cli-microsoft365 copied to clipboard

New command: `request`

Open martinlingstuyl opened this issue 2 years ago • 16 comments

PnP Powershell has this nice wrapper function around the SharePoint api, Invoke-PnPSPRestMethod.

Would it be nice if we had that too in the CLI? For example for the SharePoint and Graph API's.

Why would a user use that?

  • It is useful if you have specific wishes around (for example) data retrieval. The user might be able to optimize scripts by just running one command instead of a few.
  • It is useful if there's some functionality not (yet) covered in the CLI.

Why would we add it?

Currently you would need to get a token using m365 util accesstoken get and construct the call using a tool like curl or Invoke-WebRequest. That works fine, but it would also be nice if we take away at least some of the plumbing for the user. This means less code to write for the user. An still a lot of flexibility. We might also be able to add extra's like retrieving all items (aka fix paging for the user)

It's a more advanced scenario for more advanced users. But I believe it could add a lot of value. Any ideas/opinions on this?

Specs below

request

Invoke a custom request at a Microsoft 365 API.

Usage

m365 request [options]

Options

Options Description
-u, --url <url> The request URL.
-m, --method [method] The HTTP request method. Accepted values: get, post, put, patch, delete, head. The default value is get.
-r, --resource [resource] The resource url for which the CLI should acquire a token from AAD in order to access the service. The token will be placed in the Authorization header. By default, the CLI can figure this out based on the --url argument.
-b, --body [body] The request body. Use @example.json to load the body from a file.
-h, --headers [headers] A JSON string containing optional header values. Use @example.json to load from a file.
-a, --accept [accept] A convenience option to set the accept header of the request.
-c, --contentType [contentType] A convenience option to set the Content-Type header of the request.

Remarks

This command will send a raw request to the targeted API. It will not make any assumptions on the type of content that's sent or returned.

Examples

Call the SharePoint Rest API using a GET request with a constructed URL containing expands, filters and selects.

m365 request --url "http://contoso.sharepoint.com/sites/project-x/_api/web/siteusers?$filter=IsShareByEmailGuestUser eq true&$expand=Groups&$select=Title,LoginName,Email,Groups/LoginName" --accept "application/json"

Call the Microsoft Graph beta endpoint using a GET request.

m365 request --url "https://graph.microsoft.com/beta/me"

Call the SharePoint API to retrieve a form digest.

m365 request --method post --url "http://contoso.sharepoint.com/sites/project-x/_api/contextinfo" --accept "application/json"

Call the SharePoint API to update a site title.

m365 request --method post --url "http://contoso.sharepoint.com/sites/project-x/_api/web" --headers '{"X-RequestDigest": "somedigest", "X-HTTP-Method": "PATCH"}' --body '{ "Title": "New title" }' --contentType "application/json"

martinlingstuyl avatar Jul 13 '22 11:07 martinlingstuyl

Great suggestion @martinlingstuyl

Azure CLI has something similar with the az rest command, it might be worth aligning with the implementation they have defined to be consistent.

https://docs.microsoft.com/en-us/cli/azure/reference-index?view=azure-cli-latest#az-rest

I wonder whether it would be better to have a unified REST command though rather than separating into two commands, we could inspect the hostname in the URL and determine which resource to get an access token for.

m365 rest --method get --url https://tenant.sharepoint.com/_api/web
m365 rest --method get --url https://graph.microsoft.com/beta/auditLogs/directoryAudits

garrytrinder avatar Jul 13 '22 20:07 garrytrinder

I wonder whether it would be better to have a unified REST command though rather than separating into two commands, we could inspect the hostname in the URL and determine which resource to get an access token for.

Great idea @garrytrinder!

martinlingstuyl avatar Jul 14 '22 05:07 martinlingstuyl

+1 to @garrytrinder's comment about having one command across all workloads. I'm not sure about the rest name though, as you can use it to call any URL, including CSOM, so perhaps something even more generic like call or request would it clearer.

Before we proceed with implementation, let's spec out the functionality some more. We should pay extra attention to headers: the format used by az cli (key=value pairs separated by spaces) might not work for us so let's double check that with the different scenarios.

waldekmastykarz avatar Jul 14 '22 07:07 waldekmastykarz

+1 for m365 request

garrytrinder avatar Jul 14 '22 09:07 garrytrinder

Good idea.. I'll create a better spec, and then you guys can respond to that again.

By the way: what do you think about having an option to force the command to get all items, e.g. that it follows odata next page links?

martinlingstuyl avatar Jul 14 '22 09:07 martinlingstuyl

By the way: what do you think about having an option to force the command to get all items, e.g. that it follows odata next page links?

Good question. On one hand, it would be a nice convenience option. On the other, it's additional complexity and moving away from the raw-request purpose of this command. I'd say, let's leave it out at the start and perhaps revisit later after we had some usage and feedback.

waldekmastykarz avatar Jul 14 '22 12:07 waldekmastykarz

Agree with @waldekmastykarz lets get the basic functionality covered in the command first, get it shipped and make folks aware and iterate based on feedback and real usage 🚀

garrytrinder avatar Jul 16 '22 10:07 garrytrinder

OK, I've added specs. Very interested to hear your ideas! @pnp/cli-for-microsoft-365-maintainers and other contributors!

martinlingstuyl avatar Jul 16 '22 22:07 martinlingstuyl

I love the idea. Also since it will provide more functionality/freedom than the PnP equivalent 😉 I was only wondering about the resource option if this could not be a free text but maybe give some possible options like: spo, graph, powerapps. etc. 🤔

Adam-it avatar Jul 17 '22 20:07 Adam-it

Well, I think that would make it less flexible. The idea is that the CLI knows what the resource is, based on the URL. There are situations where the resource can not be easily guessed, in those cases you'll need to use the resource option. It's hard upfront to guess what these cases are, so you'll have the best freedom typing your own resource.

martinlingstuyl avatar Jul 17 '22 21:07 martinlingstuyl

Well, I think that would make it less flexible. The idea is that the CLI knows what the resource is, based on the URL. There are situations where the resource can not be easily guessed, in those cases you'll need to use the resource option. It's hard upfront to guess what these cases are, so you'll have the best freedom typing your own resource.

That's true. Freedom is the biggest advantage in this command 👍. I only thought that maybe we could also help out the user somehow by giving options and setting the proper url for to use, but then we will loose the freedom of this command. No other additional comment then. Great suggestion 🤩

Adam-it avatar Jul 17 '22 22:07 Adam-it

...added some extra examples.

martinlingstuyl avatar Jul 18 '22 19:07 martinlingstuyl

Anyone extra comments on this? @pnp/cli-for-microsoft-365-maintainers? Otherwise we can open this up for contribution.

martinlingstuyl avatar Jul 30 '22 16:07 martinlingstuyl

Anyone extra comments on this? @pnp/cli-for-microsoft-365-maintainers? Otherwise we can open this up for contribution.

For me looks ok and ready to go

Adam-it avatar Jul 30 '22 17:07 Adam-it

@garrytrinder or @waldekmastykarz also OK with this setup?

martinlingstuyl avatar Aug 02 '22 16:08 martinlingstuyl

Ship. It. 🚀

garrytrinder avatar Aug 03 '22 15:08 garrytrinder

Why does a resource need to be a separate option rather than something that we'd resolve using the existing logic?

waldekmastykarz avatar Aug 15 '22 14:08 waldekmastykarz

Also, for headers, have we researched the ability to use key-value pairs instead of a JSON string (which would be pretty inconvenient to type)?

waldekmastykarz avatar Aug 15 '22 14:08 waldekmastykarz

Why does a resource need to be a separate option rather than something that we'd resolve using the existing logic?

Hi Waldek, We do resolve it using the existing logic. We added the resource option as there are cases where the resource url differs from the api endpoint. We cover a few of those situations in the CLI (for example with the power platform urls and https://management.azure.com) but there might be cases that we have missed till now. The resource option offers a little extra flexibility in those cases, should anyone run into it.

Should I make the description clearer?

martinlingstuyl avatar Aug 15 '22 16:08 martinlingstuyl

Also, for headers, have we researched the ability to use key-value pairs instead of a JSON string (which would be pretty inconvenient to type)?

Good point, I did not... (making a mental note)

martinlingstuyl avatar Aug 15 '22 16:08 martinlingstuyl

We added the resource option as there are cases where the resource url differs from the api endpoint. We cover a few of those situations in the CLI (for example with the power platform urls and https://management.azure.com/) but there might be cases that we have missed till now. The resource option offers a little extra flexibility in those cases, should anyone run into it.

If we allow people to specify the resource, we'll need to find a way either to pass the retrieved access token into the request while suppressing the automatic token retrieval based on the detected resource. One way around it might be to pretend that the request is anonymous using the custom x-anonymous header that we strip before issuing the request but which skips the token retrieval part.

As for the headers, let's update the spec, so that later we know what the command is supposed to do and can properly validate the PR.

waldekmastykarz avatar Aug 15 '22 18:08 waldekmastykarz

If we allow people to specify the resource, we'll need to find a way either to pass the retrieved access token into the request while suppressing the automatic token retrieval based on the detected resource.

Check my PR 😇 @waldekmastykarz.

I considered x-anonymous, but seeing the use cases I chose another route: injecting the accesstoken as a part of the AxiosRequestConfig. A minimal change, but OK as far as I can see.

martinlingstuyl avatar Aug 15 '22 18:08 martinlingstuyl

I added my comments on the PR 👍

waldekmastykarz avatar Aug 16 '22 07:08 waldekmastykarz

Hi @waldekmastykarz,

I'm playing with the idea to use unknown options for the headers. This would greatly simplify writing them down.

m365 request --url "https://blimped.sharepoint.com/sites/learning/_api/web" --accept "application/json" --x-request-digest "some digest" -if-match "*"

Combined with allowUnknownOptions, I've tested it and this:

    const unknownOptions: any = this.getUnknownOptions(args.options);
    const unknownOptionsNames: string[] = Object.getOwnPropertyNames(unknownOptions);
    unknownOptionsNames.forEach(o => {
      logger.logToStderr("header: " + o + " / value: " + unknownOptions[o]);
    });

prints the following:

header: content-type / value: test
header: x-request-digest / value: some digest

What do you think about that? I think its better than string key value pairs. The only question is what we do with the contentType option. Keep it on the convenience list (and write contentType) or follow up writing in with a dash.

martinlingstuyl avatar Aug 18 '22 15:08 martinlingstuyl

Nice idea! If there's no chance for a conflict with any of the known options, then we can definitely use it. In such case, I'd drop contentType because it's very similar to content-type.

waldekmastykarz avatar Aug 18 '22 15:08 waldekmastykarz

Let's do it!

martinlingstuyl avatar Aug 18 '22 16:08 martinlingstuyl

I've updated the specs and fixed the PR.

martinlingstuyl avatar Aug 18 '22 21:08 martinlingstuyl

Very nice! 👏

waldekmastykarz avatar Aug 19 '22 06:08 waldekmastykarz