{"id":28523139,"url":"https://github.com/upbound/function-msgraph","last_synced_at":"2026-01-16T16:53:19.635Z","repository":{"id":289947623,"uuid":"972898002","full_name":"upbound/function-msgraph","owner":"upbound","description":"A Crossplane composition function for querying the Microsoft Graph API.","archived":false,"fork":false,"pushed_at":"2025-06-25T17:50:20.000Z","size":342,"stargazers_count":1,"open_issues_count":4,"forks_count":0,"subscribers_count":6,"default_branch":"main","last_synced_at":"2025-06-25T18:42:15.697Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/upbound.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2025-04-25T21:20:10.000Z","updated_at":"2025-06-13T00:08:34.000Z","dependencies_parsed_at":"2025-05-31T11:51:21.730Z","dependency_job_id":"1adfa681-e421-42d0-b406-11d5ffbd918f","html_url":"https://github.com/upbound/function-msgraph","commit_stats":null,"previous_names":["upbound/function-msgraph"],"tags_count":3,"template":false,"template_full_name":null,"purl":"pkg:github/upbound/function-msgraph","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/upbound%2Ffunction-msgraph","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/upbound%2Ffunction-msgraph/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/upbound%2Ffunction-msgraph/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/upbound%2Ffunction-msgraph/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/upbound","download_url":"https://codeload.github.com/upbound/function-msgraph/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/upbound%2Ffunction-msgraph/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":263853301,"owners_count":23520118,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-06-09T10:07:20.738Z","updated_at":"2026-01-16T16:53:19.617Z","avatar_url":"https://github.com/upbound.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# function-msgraph\n\nA Crossplane composition function for querying the Microsoft Graph API.\n\n## Overview\n\nThe `function-msgraph` provides read-only access to Microsoft Graph API endpoints, allowing Crossplane compositions to:\n\n1. Validate Azure AD User Existence\n2. Get Group Membership\n3. Get Group Object IDs\n4. Get Service Principal Details\n\nThe function supports throttling mitigation with the `skipQueryWhenTargetHasData` flag to avoid unnecessary API calls.\n\n## Usage\n\nAdd the function to your Crossplane installation:\n\n```yaml\napiVersion: pkg.crossplane.io/v1beta1\nkind: Function\nmetadata:\n  name: function-msgraph\nspec:\n  package: xpkg.upbound.io/upbound/function-msgraph:v0.1.0\n```\n\n### Azure Credentials\n\nThe service principal needs the following Microsoft Graph API permissions:\n- User.Read.All (for user validation)\n- Group.Read.All (for group operations)\n- Application.Read.All (for service principal details)\n\n#### Client Secret Credentials\nCreate an Azure service principal with appropriate permissions to access Microsoft Graph API:\n\n```yaml\napiVersion: v1\nkind: Secret\nmetadata:\n  name: azure-account-creds\n  namespace: crossplane-system\ntype: Opaque\nstringData:\n  credentials: |\n    {\n      \"clientId\": \"your-client-id\",\n      \"clientSecret\": \"your-client-secret\", \n      \"subscriptionId\": \"your-subscription-id\",\n      \"tenantId\": \"your-tenant-id\"\n    }\n```\n\n#### Workload Identity Credentials\nAKS cluster needs to have workload identity enabled.\nThe managed identity needs to have the Federated Identity Credential created: https://azure.github.io/azure-workload-identity/docs/topics/federated-identity-credential.html.\n\n##### Credentials secret:\n```yaml\napiVersion: v1\nkind: Secret\nmetadata:\n  name: azure-account-creds\n  namespace: crossplane-system\ntype: Opaque\nstringData:\n  credentials: |\n    {\n      \"clientId\": \"your-client-id\", # optional\n      \"tenantId\": \"your-tenant-id\", # optional\n      \"federatedTokenFile\": \"/var/run/secrets/azure/tokens/azure-identity-token\"\n    }\n```\n\n##### Function\n```yaml\napiVersion: pkg.crossplane.io/v1\nkind: Function\nmetadata:\n  name: upbound-function-msgraph\nspec:\n  package: xpkg.upbound.io/upbound/function-msgraph:v0.2.0\n  runtimeConfigRef:\n    apiVersion: pkg.crossplane.io/v1beta1\n    kind: DeploymentRuntimeConfig\n    name: upbound-function-msgraph\n```\n\n##### DeploymentRuntimeConfig\n```yaml\napiVersion: pkg.crossplane.io/v1beta1\nkind: DeploymentRuntimeConfig\nmetadata:\n  name: upbound-function-msgraph\nspec: \n  deploymentTemplate:\n    spec:\n      selector:\n        matchLabels:\n          azure.workload.identity/use: \"true\"\n          pkg.crossplane.io/function: \"upbound-function-msgraph\"\n      template:\n        metadata:\n          labels:\n            azure.workload.identity/use: \"true\"\n            pkg.crossplane.io/function: \"upbound-function-msgraph\"\n        spec:\n          containers:\n          - name: package-runtime\n            volumeMounts:\n            - mountPath: /var/run/secrets/azure/tokens\n              name: azure-identity-token\n              readOnly: true\n          serviceAccountName: \"upbound-function-msgraph\"\n          volumes:\n          - name: azure-identity-token\n            projected:\n              sources:\n              - serviceAccountToken:\n                  audience: api://AzureADTokenExchange\n                  expirationSeconds: 3600\n                  path: azure-identity-token\n  serviceAccountTemplate:\n    metadata:\n      annotations:\n        azure.workload.identity/client-id: \"your-client-id\"\n      name: \"upbound-function-msgraph\"\n```\n\n## Examples\n\n### Validate Azure AD Users\n\n```yaml\napiVersion: example.crossplane.io/v1\nkind: Composition\nmetadata:\n  name: user-validation-example\nspec:\n  compositeTypeRef:\n    apiVersion: example.crossplane.io/v1\n    kind: XR\n  pipeline:\n  - step: validate-user\n    functionRef:\n      name: function-msgraph\n    input:\n      apiVersion: msgraph.fn.crossplane.io/v1alpha1\n      kind: Input\n      queryType: UserValidation\n      users:\n        - \"user1@yourdomain.com\"\n        - \"user2@yourdomain.com\"\n      target: \"status.validatedUsers\"\n      skipQueryWhenTargetHasData: true\n    credentials:\n      - name: azure-creds\n        source: Secret\n        secretRef:\n          namespace: crossplane-system\n          name: azure-account-creds\n```\n\n### Get Group Membership\n\n```yaml\napiVersion: example.crossplane.io/v1\nkind: Composition\nmetadata:\n  name: group-membership-example\nspec:\n  compositeTypeRef:\n    apiVersion: example.crossplane.io/v1\n    kind: XR\n  pipeline:\n  - step: get-group-members\n    functionRef:\n      name: function-msgraph\n    input:\n      apiVersion: msgraph.fn.crossplane.io/v1alpha1\n      kind: Input\n      queryType: GroupMembership\n      group: \"Developers\"\n      # The function will automatically select standard fields:\n      # - id, displayName, mail, userPrincipalName, appId, description\n      target: \"status.groupMembers\"\n      skipQueryWhenTargetHasData: true\n    credentials:\n      - name: azure-creds\n        source: Secret\n        secretRef:\n          namespace: crossplane-system\n          name: azure-account-creds\n```\n\n### Get Group Object IDs\n\n```yaml\napiVersion: example.crossplane.io/v1\nkind: Composition\nmetadata:\n  name: group-objectids-example\nspec:\n  compositeTypeRef:\n    apiVersion: example.crossplane.io/v1\n    kind: XR\n  pipeline:\n  - step: get-group-objectids\n    functionRef:\n      name: function-msgraph\n    input:\n      apiVersion: msgraph.fn.crossplane.io/v1alpha1\n      kind: Input\n      queryType: GroupObjectIDs\n      groups:\n        - \"Developers\"\n        - \"Operations\"\n        - \"Security\"\n      target: \"status.groupObjectIDs\"\n      skipQueryWhenTargetHasData: true\n    credentials:\n      - name: azure-creds\n        source: Secret\n        secretRef:\n          namespace: crossplane-system\n          name: azure-account-creds\n```\n\n### Get Service Principal Details\n\n```yaml\napiVersion: example.crossplane.io/v1\nkind: Composition\nmetadata:\n  name: service-principal-example\nspec:\n  compositeTypeRef:\n    apiVersion: example.crossplane.io/v1\n    kind: XR\n  pipeline:\n  - step: get-service-principal-details\n    functionRef:\n      name: function-msgraph\n    input:\n      apiVersion: msgraph.fn.crossplane.io/v1alpha1\n      kind: Input\n      queryType: ServicePrincipalDetails\n      servicePrincipals:\n        - \"MyServiceApp\"\n        - \"ApiConnector\"\n      target: \"status.servicePrincipalDetails\"\n      skipQueryWhenTargetHasData: true\n    credentials:\n      - name: azure-creds\n        source: Secret\n        secretRef:\n          namespace: crossplane-system\n          name: azure-account-creds\n```\n\n## Input Configuration Options\n\n| Field | Type | Description |\n|-------|------|-------------|\n| `queryType` | string | Required. Type of query to perform. Valid values: `UserValidation`, `GroupMembership`, `GroupObjectIDs`, `ServicePrincipalDetails` |\n| `users` | []string | List of user principal names (email IDs) for user validation |\n| `usersRef` | string | Reference to resolve a list of user names from `spec`, `status` or `context` (e.g., `spec.userAccess.emails`) |\n| `group` | string | Single group name for group membership queries |\n| `groupRef` | string | Reference to resolve a single group name from `spec`, `status` or `context` (e.g., `spec.groupConfig.name`) |\n| `groups` | []string | List of group names for group object ID queries |\n| `groupsRef` | string | Reference to resolve a list of group names from `spec`, `status` or `context` (e.g., `spec.groupConfig.names`) |\n| `servicePrincipals` | []string | List of service principal names |\n| `servicePrincipalsRef` | string | Reference to resolve a list of service principal names from `spec`, `status` or `context` (e.g., `spec.servicePrincipalConfig.names`) |\n| `target` | string | Required. Where to store the query results. Can be `status.\u003cfield\u003e` or `context.\u003cfield\u003e` |\n| `skipQueryWhenTargetHasData` | bool | Optional. When true, will skip the query if the target already has data |\n| `FailOnEmpty` | bool | Optional. When true, the function will fail if the `users`, `groups`, or `servicePrincipals` lists are empty, or if their respective reference fields are empty lists. |\n| `identity.type` | string | Optional. Type of identity credentials to use. Valid values: `AzureServicePrincipalCredentials`, `AzureWorkloadIdentityCredentials`. Default is `AzureServicePrincipalCredentials` |\n\n## Result Targets\n\nResults can be stored in either XR Status or Composition Context:\n\n```yaml\n# Store in XR Status\ntarget: \"status.results\"\n\n# Store in nested XR Status\ntarget: \"status.nested.field.results\"\n\n# Store in Composition Context\ntarget: \"context.results\"\n\n# Store in Environment\ntarget: \"context.[apiextensions.crossplane.io/environment].results\"\n```\n\n## Using Reference Fields\n\nYou can reference values from XR spec, status, or context instead of hardcoding them:\n\n### Using groupRef from spec\n\n```yaml\napiVersion: msgraph.fn.crossplane.io/v1alpha1\nkind: Input\nqueryType: GroupMembership\ngroupRef: \"spec.groupConfig.name\"  # Get group name from XR spec\ntarget: \"status.groupMembers\"\n```\n\n### Using groupsRef from spec\n\n```yaml\napiVersion: msgraph.fn.crossplane.io/v1alpha1\nkind: Input\nqueryType: GroupObjectIDs\ngroupsRef: \"spec.groupConfig.names\"  # Get group names from XR spec\ntarget: \"status.groupObjectIDs\"\n```\n\n### Using usersRef from spec\n\n```yaml\napiVersion: msgraph.fn.crossplane.io/v1alpha1\nkind: Input\nqueryType: UserValidation\nusersRef: \"spec.userAccess.emails\"  # Get user emails from XR spec\ntarget: \"status.validatedUsers\"\n```\n\n### Using servicePrincipalsRef from spec\n\n```yaml\napiVersion: msgraph.fn.crossplane.io/v1alpha1\nkind: Input\nqueryType: ServicePrincipalDetails\nservicePrincipalsRef: \"spec.servicePrincipalConfig.names\"  # Get service principal names from XR spec\ntarget: \"status.servicePrincipals\"\n```\n\n## Using data from EnvironmentConfig\n\nTo use data from a Crossplane `EnvironmentConfig`, first load the data into the context using [function-environment-configs](https://github.com/crossplane-contrib/function-environment-configs). This function should be in the pipeline before you use function-msgraph.\n\nfunction-environment-configs loads a merged map of environment config data to `context[\"apiextensions.crossplane.io/environment\"]` but to refer to this correctly in function-msgraph the following syntax must be used: `context.[apiextensions.crossplane.io/environment]`.\n\n### Example:\n\n1. First, define your Environment Config:\n\n```yaml\napiVersion: apiextensions.crossplane.io/v1beta1\nkind: EnvironmentConfig\nmetadata:\n  name: example-config\ndata:\n  entraid:\n    users:\n      - user@example.com\n```\n\n2. Load the Environment Config in to your composition pipeline:\n\n```yaml\n  pipeline:\n    - step: load-environment-config\n      functionRef:\n        name: crossplane-contrib-function-environment-configs\n      input:\n        apiVersion: environmentconfigs.fn.crossplane.io/v1beta1\n        kind: Input\n        spec:\n          environmentConfigs:\n            - type: Reference\n              ref:\n                name: example-config\n```\n\n3. Reference the Environment Config data in your function input:\n\n```yaml\n    - step: load-environment-config\n      # ... omitted for brevity\n    - step: get-group-members\n      functionRef:\n        name: function-msgraph\n      input:\n        apiVersion: msgraph.fn.crossplane.io/v1alpha1\n        kind: Input\n        queryType: GroupMembership\n        groupRef: context.[apiextensions.crossplane.io/environment].entraid.users\n        target: \"status.users\"\n```\n\n## Using Different Credentials\n\n### Using ServicePrincipal credentials\n\n#### Explicitly\n```yaml\napiVersion: msgraph.fn.crossplane.io/v1alpha1\nkind: Input\nidentity:\n  type: AzureServicePrincipalCredentials\n```\n\n#### Default\n```yaml\napiVersion: msgraph.fn.crossplane.io/v1alpha1\nkind: Input\n```\n\n### Using Workload Identity Credentials\n```yaml\napiVersion: msgraph.fn.crossplane.io/v1alpha1\nkind: Input\nidentity:\n  type: AzureWorkloadIdentityCredentials\n```\n\n## Operations support\nfunction-msgraph support every kind of [operations](https://docs.crossplane.io/latest/operations/operation/) but it only allows targeting Composite Resources\nFunction omits the input.skipQueryWhenTargetHasData parameter when running in operation mode to enforce compability with Cron/Watch modes.\nCronOperations and WatchOperations are the most useful in context of graph queries, please check [examples](./example/operations/).\n### Operations and Compositions Working Together\n\n**Important**: Operations and Compositions work in conjunction to provide a self-healing mechanism:\n\n1. **Operations Role (Drift Detection)**:\n   - Query Microsoft Graph API on schedule/watch events\n   - Compare results with current XR status\n   - Set drift detection annotations (but don't update status directly)\n\n2. **Compositions Role (Drift Correction)**:\n   - Run when XR is reconciled (triggered by annotation changes)\n   - Check drift detection annotation\n   - If drift detected, ignore `skipQueryWhenTargetHasData` flag and update status\n   - Reset drift annotation to \"false\" after successful update\n\nThis creates a **two-phase self-healing system** where Operations monitor for changes and Compositions perform the actual data updates.\n### Operations results\nfunction-msgraph operations result in two annotations set on the XR:\n```yaml\napiVersion: \"example.org/v1\"\nkind: XR\nmetadata:\n  name: \"cool-xr\"\n  annotations:\n    \"function-msgraph/last-execution\": \"2025-01-01T00:00:00+01:00\"\n    \"function-msgraph/last-execution-query-drift-detected\": \"false\"\n```\nfunction-msgraph/last-execution sets RFC3339 timestamp informing about last succesful Operation run.\nfunction-msgraph/last-execution-query-drift-detected sets a boolean if there's a drift between input.target field's value and query result, which is used by function-msgraph in Composition context for self-healing. skipQueryWhenTargetHasData input parameter is ommited when drift detected annotation is set which leads to XR update and after that next Operation run sets the annotation back to \"false\".\n\n### CronOperation\nCronOperation may be used to forcefully update XR's status in a predefined interval.\nThat functionality may be especially useful for XRs that are business critical and should have the data refreshed without worrying about throttling.\nSupports only singular resource reference.\n\n```yaml\napiVersion: ops.crossplane.io/v1alpha1\nkind: CronOperation\nmetadata:\n  name: update-user-validation-for-critical-xr\nspec:\n  schedule: \"*/5 * * * *\" # Every 5 minutes\n  concurrencyPolicy: Forbid\n  successfulHistoryLimit: 5\n  failedHistoryLimit: 3\n  operationTemplate:\n    spec:\n      mode: Pipeline\n      pipeline:\n      - step: user-validation\n        functionRef:\n          name: function-msgraph\n        input:\n          apiVersion: msgraph.fn.crossplane.io/v1alpha1\n          kind: Input\n          queryType: UserValidation\n          # Replace these with actual users in your directory\n          users:\n            - \"admin@example.onmicrosoft.com\"\n            - \"user@example.onmicrosoft.com\"\n            - \"yury@upbound.io\"\n          target: \"status.validatedUsers\"\n        credentials:\n          - name: azure-creds\n            source: Secret\n            secretRef:\n              namespace: crossplane-system\n              name: azure-account-creds\n        requirements:\n          requiredResources:\n          - requirementName: ops.crossplane.io/watched-resource\n            apiVersion: example.crossplane.io/v1\n            kind: XR\n            name: business-critical-xr\n```\n### WatchOperation\nWatchOperation may be used to forcefully update XR's status based on match condition.\nFor example it may be useful to refresh status in business critical XR's that are labeled with label `always-update: \"true\"`.\n```yaml\napiVersion: ops.crossplane.io/v1alpha1\nkind: WatchOperation\nmetadata:\n  name: update-user-validation-for-critical-xrs\nspec:\n  watch:\n    apiVersion: example.crossplane.io/v1\n    kind: XR\n    matchLabels:\n      always-update: \"true\"\n  concurrencyPolicy: Allow\n  operationTemplate:\n    spec:\n      mode: Pipeline\n      pipeline:\n      - step: user-validation\n        functionRef:\n          name: function-msgraph\n        input:\n          apiVersion: msgraph.fn.crossplane.io/v1alpha1\n          kind: Input\n          queryType: UserValidation\n          # Replace these with actual users in your directory\n          users:\n            - \"admin@example.onmicrosoft.com\"\n            - \"user@example.onmicrosoft.com\"\n            - \"yury@upbound.io\"\n          target: \"status.validatedUsers\"\n        credentials:\n          - name: azure-creds\n            source: Secret\n            secretRef:\n              namespace: crossplane-system\n              name: azure-account-creds\n```\n\n## References\n\n- [Microsoft Graph API Overview](https://learn.microsoft.com/en-us/graph/api/overview?view=graph-rest-1.0)\n- [User validation](https://learn.microsoft.com/en-us/graph/api/user-list?view=graph-rest-1.0\u0026tabs=go)\n- [Group membership](https://learn.microsoft.com/en-us/graph/api/group-list-members?view=graph-rest-1.0\u0026tabs=go)\n- [Group listing](https://learn.microsoft.com/en-us/graph/api/group-list?view=graph-rest-1.0\u0026tabs=go)\n- [Service principal listing](https://learn.microsoft.com/en-us/graph/api/serviceprincipal-list?view=graph-rest-1.0\u0026tabs=http)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fupbound%2Ffunction-msgraph","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fupbound%2Ffunction-msgraph","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fupbound%2Ffunction-msgraph/lists"}