azure-cli/doc/microsoft_graph_client.md

4.9 KiB

GraphClient - Microsoft Graph API client

Azure CLI has been migrated to Microsoft Graph API for Azure Active Directory operations. A lightweight client azure.cli.command_modules.role._msgrpah.GraphClient is developed for calling Microsoft Graph API.

Create a GraphClient instance

GraphClient should NEVER be instantiated directly, but always through the client factory azure.cli.command_modules.role.graph_client_factory.

# Correct!
from azure.cli.command_modules.role import graph_client_factory
graph_client = graph_client_factory(cli_ctx)

# Wrong!
from azure.cli.command_modules.role._msgrpah import GraphClient
graph_client = GraphClient(cli_ctx)

Request

The underlying Microsoft Graph API is exposed as GraphClient methods. For example, Create application API corresponds to application_create.

The order of the verb ("create") and the noun ("application") is inverted to keep alignment with Azure CLI commands like az ad app create and old Python SDK azure-graphrbac methods like graph_client.applications.create. This makes it easier to find a method/API from the new GraphClient and migrate to it:

- graph_client.applications.create(app_create_param)
+ graph_client.application_create(body)

The body argument for the request should be a dict object as defined by the underlying API. For example, Create application takes a dict object defined by application resource type.

For example, to create an application with certain displayName:

body = {"displayName": display_name}
app = graph_client.application_create(body)

Response

Like body, the response is also a dict object, returned by the underlying API. The dict object is deserialized from the JSON response, unchanged, meaning any client-side manipulation defined by REST API spec (such as flattening) doesn't take place.

For example, to get an application's object ID:

app_object_id = app['id']

Error

All GraphClient methods raise an azure.cli.command_modules.role.GraphError exception if the underlying APIs return a status code >= 400.

Say we catch the exception as ex. str(ex) gives the error.message field of the response JSON. ex.response gives the raw response, as a requests.models.Response object.

For example, to retrieve the error message and status code:

message = str(ex)
status_code = ex.response.status_code

A full example

Here is a full example graph_demo.py. It does several tasks:

  1. Create an application and a service principal for this application.
  2. Resolve service principal's object ID from its service principal name.
  3. Delete the application.
from azure.cli.core import get_default_cli

from knack.log import get_logger

logger = get_logger(__name__)


def create_application_and_service_principal(cli_ctx, display_name):
    """Create an application with display_name. Then create a service principal for this application."""
    from azure.cli.command_modules.role import graph_client_factory, GraphError
    graph_client = graph_client_factory(cli_ctx)
    try:
        body = {"displayName": display_name}
        app = graph_client.application_create(body)
        sp = graph_client.service_principal_create({"appId": app['appId']})
        return app, sp
    except GraphError as ex:
        logger.exception(ex)


def delete_application(cli_ctx, object_id):
    """Delete an application specified by its object ID."""
    from azure.cli.command_modules.role import graph_client_factory, GraphError
    graph_client = graph_client_factory(cli_ctx)
    try:
        graph_client.application_delete(object_id)
    except GraphError as ex:
        logger.exception(ex)


def resolve_service_principal_id(cli_ctx, service_principal_name):
    """Resolve service principal's object ID from its service principal name."""
    from azure.cli.command_modules.role import graph_client_factory, GraphError
    graph_client = graph_client_factory(cli_ctx)
    try:
        service_principals = graph_client.service_principal_list(
            filter="servicePrincipalNames/any(c:c eq '{}')".format(service_principal_name))
        return service_principals[0]['id'] if service_principals else None
    except GraphError as ex:
        logger.exception(ex)


def main():
    cli_ctx = get_default_cli()

    app, sp = create_application_and_service_principal(cli_ctx, 'azure-cli-test')
    print('Created application {} and service principal {}'.format(app['id'], sp['id']))

    resolved_sp_object_id = resolve_service_principal_id(cli_ctx, sp['appId'])
    print('appId {} is resolved to service principal {}'.format(app['appId'], resolved_sp_object_id))

    delete_application(cli_ctx, app['id'])


if __name__ == "__main__":
    main()