def list_groups(scope='project', project=None, continuation_token=None, subject_types=None, organization=None, detect=None): """ List all the groups in a project or organization :param scope: List groups at project or organization level. :type scope: str :param continuation_token : If there are more results that can't be returned in a single page, the result set will contain a continuation token for retrieval of the next set of results. :type continuation_token: str :param subject_types: A comma separated list of user subject subtypes to reduce the retrieved results. You can give initial part of descriptor [before the dot] as a filter e.g. vssgp,aadgp :type subject_types: [str] :rtype: :class:`<PagedGraphGroups> <azure.devops.v5_0.graph.models.PagedGraphGroups>` """ if scope == 'project': organization, project = resolve_instance_and_project( detect=detect, organization=organization, project=project) else: organization = resolve_instance(detect=detect, organization=organization) client = get_graph_client(organization) scope_descriptor = None if project is not None: project_id = get_project_id_from_name(organization, project) scope_descriptor = get_descriptor_from_storage_key(project_id, client) if subject_types is not None: subject_types = subject_types.split(',') group_list_response = client.list_groups( scope_descriptor=scope_descriptor, continuation_token=continuation_token, subject_types=subject_types) return group_list_response
def add_membership(member_id, group_id, organization=None, detect=None): """Add membership. :param member_id: Descriptor of the group or Email Id of the user to be added. User should already be a part of the organization. Use `az devops user add` command to add an user to organization. :type member_id: str :param group_id: Descriptor of the group to which member needs to be added. :type group_id: str :rtype: :class:`<GraphMembership> <azure.devops.v5_0.graph.models.GraphMembership>` """ organization = resolve_instance(detect=detect, organization=organization) client = get_graph_client(organization) subject_descriptor = member_id if '@' in member_id or '.' not in member_id: member_id = resolve_identity_as_id(member_id, organization) subject_descriptor = get_descriptor_from_storage_key(member_id, client) membership_details = client.add_membership( subject_descriptor=subject_descriptor, container_descriptor=group_id) lookup_keys = [] container = GraphSubjectLookupKey(membership_details.container_descriptor) subject = GraphSubjectLookupKey(membership_details.member_descriptor) lookup_keys.append(container) lookup_keys.append(subject) subject_lookup = GraphSubjectLookup(lookup_keys=lookup_keys) membership_details = client.lookup_subjects(subject_lookup=subject_lookup) return membership_details
def list_memberships(id, relationship='members', organization=None, detect=None): # pylint: disable=redefined-builtin """List memberships for a group or user. :param id: Group descriptor or User Email whose membership details are required. :type id: str :rtype: [GraphMembership] """ organization = resolve_instance(detect=detect, organization=organization) subject_descriptor = id client = get_graph_client(organization) if '@' in id or '.' not in id: id = resolve_identity_as_id(id, organization) subject_descriptor = get_descriptor_from_storage_key(id, client) direction = 'down' if relationship == 'memberof': direction = 'up' membership_list = client.list_memberships(subject_descriptor=subject_descriptor, direction=direction) lookup_keys = [] for members in membership_list: if relationship == 'memberof': key = GraphSubjectLookupKey(members.container_descriptor) else: key = GraphSubjectLookupKey(members.member_descriptor) lookup_keys.append(key) subject_lookup = GraphSubjectLookup(lookup_keys=lookup_keys) members_details = client.lookup_subjects(subject_lookup=subject_lookup) return members_details
def download_package(feed, name, version, path, organization=None, detect=None): """(PREVIEW) Download a package. :param feed: Name or ID of the feed. :type feed: str :param name: Name of the package, e.g. 'foo-package'. :type name: str :param version: Version of the package, e.g. 1.0.0. :type version: str :param path: Directory to place the package contents. :type path: str :param organization: Azure Devops organization URL. Example: https://dev.azure.com/MyOrganizationName/ :type organization: str :param detect: Automatically detect organization. Default is "on". :type detect: str """ try: colorama.init( ) # Needed for humanfriendly spinner to display correctly logger.warning(_UNIVERSAL_PREVIEW_MESSAGE) organization = resolve_instance(detect=detect, organization=organization) artifact_tool = ArtifactToolInvoker( ProgressReportingExternalToolInvoker(), ArtifactToolUpdater()) return artifact_tool.download_universal(organization, feed, name, version, path) except VstsServiceError as ex: raise CLIError(ex)
def update_user_entitlement(user, license_type, organization=None, detect=None): """Update license type for a user. :param user: Email ID or ID of the user. :type user: str :param license_type: License type for the user. :type license_type: str :rtype: UserEntitlementsPatchResponse """ patch_document = [] value = {} value['accountLicenseType'] = license_type patch_document.append( _create_patch_operation('replace', '/accessLevel', value)) organization = resolve_instance(detect=detect, organization=organization) if '@' in user: user = resolve_identity_as_id(user, organization) client = get_member_entitlement_management_client(organization) user_entitlement_update = client.update_user_entitlement( document=patch_document, user_id=user) if user_entitlement_update.is_success is False and \ user_entitlement_update.operation_results[0].errors[0] is not None: raise CLIError( user_entitlement_update.operation_results[0].errors[0]['value']) return user_entitlement_update.user_entitlement
def show_namespace(namespace_id, organization=None, detect=None): """ Show details of permissions available in each namespace. """ organization = resolve_instance(detect=detect, organization=organization) client = get_security_client(organization) response = _get_permission_types(client, namespace_id) return response
def add_user_entitlement(email_id, license_type, send_email_invite='true', organization=None, detect=None): """Add user. :param email_id: Email ID of the user. :type email_id: str :param license_type: License type for the user. :type license_type: str :rtype: UserEntitlementsPatchResponse """ do_not_send_invite = False do_not_send_invite = not resolve_true_false(send_email_invite) organization = resolve_instance(detect=detect, organization=organization) client = get_member_entitlement_management_client(organization) user_access_level = AccessLevel() user_access_level.account_license_type = license_type graph_user = GraphUser() graph_user.subject_kind = 'user' graph_user.principal_name = email_id value = {} value['accessLevel'] = user_access_level value['extensions'] = [] value['projectEntitlements'] = [] value['user'] = graph_user patch_document = [] patch_document.append(_create_patch_operation('add', '', value)) user_entitlement = client.update_user_entitlements( document=patch_document, do_not_send_invite_for_new_users=do_not_send_invite) if user_entitlement.have_results_succeeded is False and user_entitlement.results[ 0].errors[0] is not None: raise CLIError(user_entitlement.results[0].errors[0]['value']) return user_entitlement.results[0].result
def uninstall_extension(publisher_id, extension_id, organization=None, detect=None): """ Uninstall an extension """ organization = resolve_instance(detect=detect, organization=organization) extension_client = get_extension_client(organization) return extension_client.uninstall_extension_by_name(publisher_name=publisher_id, extension_name=extension_id)
def update_permissions(namespace_id, subject, token, merge=True, allow_bit=0, deny_bit=0, organization=None, detect=None): """ Assign allow or deny permission to given user/group. """ if allow_bit == 0 and deny_bit == 0: raise CLIError('Either --allow-bit or --deny-bit parameter should be provided.') organization = resolve_instance(detect=detect, organization=organization) client = get_security_client(organization) subject = _resolve_subject_as_identity_descriptor(subject, organization) container_object = {} aces_list = [] ace_object = AccessControlEntry(descriptor=subject, allow=allow_bit, deny=deny_bit) aces_list.append(ace_object) container_object['token'] = token if merge: container_object['merge'] = True else: container_object['merge'] = False container_object['accessControlEntries'] = aces_list client.set_access_control_entries(security_namespace_id=namespace_id, container=container_object) allow_bit = allow_bit & (~deny_bit) changed_bits = allow_bit + deny_bit list_response = _query_permissions(client, namespace_id, subject, token, False) permissions_types = _get_permission_types(client, namespace_id) resolved_permissions_response = _resolve_bits(list_response, permissions_types, changed_bits) response = _update_json(list_response, resolved_permissions_response) return response
def delete_project(id, organization=None, detect=None): # pylint: disable=redefined-builtin """Delete team project. :param id: The id (UUID) of the project to delete. :type id: str :param organization: Azure Devops organization URL. Example: https://dev.azure.com/MyOrganizationName/ :type organization: str :param detect: When 'On' unsupplied arg values will be detected from the current working directory's repo. :type detect: str """ try: organization = resolve_instance(detect=detect, organization=organization) core_client = get_core_client(organization) operation_reference = core_client.queue_delete_project(project_id=id) operation = wait_for_long_running_operation(organization, operation_reference.id, 1) status = operation.status.lower() if status == 'failed': raise CLIError('Project deletion failed.') elif status == 'cancelled': raise CLIError('Project deletion was cancelled.') print('Deleted project {}'.format(id)) return operation except VstsServiceError as ex: raise CLIError(ex)
def publish_package(feed, name, version, path, description=None, organization=None, detect=None): """Publish a package to a feed. :param feed: Name or ID of the feed. :type feed: str :param name: Name of the package, e.g. 'foo-package'. :type name: str :param version: Version of the package, e.g. '1.0.0'. :type version: str :param description: Description of the package. :type description: str :param path: Directory containing the package contents. :type path: str """ colorama.init() # Needed for humanfriendly spinner to display correctly organization = resolve_instance(detect=detect, organization=organization) artifact_tool = ArtifactToolInvoker(ProgressReportingExternalToolInvoker(), ArtifactToolUpdater()) return artifact_tool.publish_universal(organization, feed, name, version, description, path)
def show_work_item(id, open=False, organization=None, detect=None): # pylint: disable=redefined-builtin """Show details for a work item. :param id: The ID of the work item :type id: int :param open: Open the work item in the default web browser. :type open: bool :param organization: Azure Devops organization URL. Example: https://dev.azure.com/MyOrganizationName/ :type organization: str :param detect: When 'On' unsupplied arg values will be detected from the current working directory's repo. :type detect: str :rtype: :class:`<WorkItem> <work-item-tracking.v4_0.models.WorkItem>` """ try: organization = resolve_instance(detect=detect, organization=organization) try: client = get_work_item_tracking_client(organization) work_item = client.get_work_item(id) except VstsServiceError as ex: _handle_vsts_service_error(ex) if open: _open_work_item(work_item, organization) return work_item except VstsServiceError as ex: raise CLIError(ex)
def show_work_item(id, as_of=None, expand='all', fields=None, open=False, organization=None, detect=None): # pylint: disable=redefined-builtin """Show details for a work item. :param id: The ID of the work item :type id: int :param as_of: Work item details as of a particular date and time. Provide a date or date time string. Assumes local time zone. Example: '2019-01-20', '2019-01-20 00:20:00'. For UTC, append 'UTC' to the date time string, '2019-01-20 00:20:00 UTC'. :type as_of:string :param expand: The expand parameters for work item attributes. :type expand:str :param fields: Comma-separated list of requested fields. Example:System.Id,System.AreaPath. Refer https://aka.ms/azure-devops-cli-field-api for more details on fields. :type fields: str :param open: Open the work item in the default web browser. :type open: bool :rtype: :class:`<WorkItem> <v5_0.work-item-tracking.models.WorkItem>` """ organization = resolve_instance(detect=detect, organization=organization) try: client = get_work_item_tracking_client(organization) as_of_iso = None if as_of: as_of_iso = convert_date_string_to_iso8601(value=as_of, argument='as-of') if fields: fields = fields.split(',') work_item = client.get_work_item(id, as_of=as_of_iso, fields=fields, expand=expand) except AzureDevOpsServiceError as ex: _handle_vsts_service_error(ex) if open: _open_work_item(work_item, organization) return work_item
def list_agents(pool_id, agent_name=None, include_capabilities=None, include_assigned_request=None, include_last_completed_request=None, demands=None, organization=None, detect=None): """ (PREVIEW) Get a list of agents in a pool :param pool_id: The agent pool containing the agents. :type pool_id: int :param agent_name: Filter on agent name. :type agent_name: str :param include_capabilities: Whether to include the agents' capabilities in the response. :type include_capabilities: bool :param include_assigned_request: Whether to include details about the agents' current work. :type include_assigned_request: bool :param include_last_completed_request: Whether to include details about the agents' most recent completed work. :type include_last_completed_request: bool :param demands: Filter by demands the agents can satisfy. Comma separated list. :type demands: str """ organization = resolve_instance(organization=organization, detect=detect) task_agent_client = get_new_task_agent_client(organization=organization) if demands: demands = list(map(str, demands.split(','))) return task_agent_client.get_agents( pool_id=pool_id, agent_name=agent_name, include_capabilities=include_capabilities, include_last_completed_request=include_last_completed_request, include_assigned_request=include_assigned_request, property_filters=None, demands=demands)
def show_agent(pool_id, agent_id, include_capabilities=None, include_assigned_request=None, include_last_completed_request=None, organization=None, detect=None): """ (PREVIEW) Show agent details :param pool_id: The agent pool containing the agent. :type pool_id: int :param agent_id: The agent ID to get information about. :type agent_id: str :param include_capabilities: Whether to include the agents' capabilities in the response. :type include_capabilities: bool :param include_assigned_request: Whether to include details about the agents' current work. :type include_assigned_request: bool :param include_last_completed_request: Whether to include details about the agents' most recent completed work. :type include_last_completed_request: bool """ organization = resolve_instance(organization=organization, detect=detect) task_agent_client = get_new_task_agent_client(organization=organization) return task_agent_client.get_agent( pool_id=pool_id, agent_id=agent_id, include_capabilities=include_capabilities, include_assigned_request=include_assigned_request, include_last_completed_request=include_last_completed_request, property_filters=None)
def get_extension(publisher_id, extension_id, organization=None, detect=None): """ Get detail of single extension """ organization = resolve_instance(detect=detect, organization=organization) extension_client = get_extension_client(organization) return extension_client.get_installed_extension_by_name(publisher_name=publisher_id, extension_name=extension_id)
def update_group(id, name=None, description=None, organization=None, detect=None): # pylint: disable=redefined-builtin """Update name AND/OR description for an Azure DevOps group. :param id: Descriptor of the group. :type id: str :param name: New name for Azure DevOps group. :type name: str :param description: New description for Azure DevOps group. :type description: str :rtype: :class:`<GraphGroup> <azure.devops.v5_0.graph.models.GraphGroup>` """ if name is None and description is None: raise CLIError('Either name or description argument must be provided.') patch_document = [] if name is not None: patch_document.append( _create_patch_operation('replace', '/displayName', name)) if description is not None: patch_document.append( _create_patch_operation('replace', '/description', description)) organization = resolve_instance(detect=detect, organization=organization) client = get_graph_client(organization) update_group_details = client.update_group(group_descriptor=id, patch_document=patch_document) return update_group_details
def list_projects(organization=None, top=None, skip=None, state_filter='all', continuation_token=None, get_default_team_image_url=None, detect=None): """List team projects :param top: Maximum number of results to list. :type top: int :param skip: Number of results to skip. :type skip: int :rtype: list of :class:`<TeamProject> <v5_0.core.models.TeamProject>` """ logger.debug('Opening web page: %s', 'Test CLI Release') logger.debug('__________________________________________________________________________________________________') organization = resolve_instance(detect=detect, organization=organization) core_client = get_core_client_v51(organization) team_projects = core_client.get_projects(state_filter=state_filter, top=top, skip=skip, continuation_token=continuation_token, get_default_team_image_url=get_default_team_image_url) return team_projects
def _update_extension_state(disable, enable, publisher_name, extension_name, organization=None, detect=None): organization = resolve_instance(detect=detect, organization=organization) extension_client = get_extension_client(organization) current_extension = extension_client.get_installed_extension_by_name( publisher_name=publisher_name, extension_name=extension_name) state_from_service = str(current_extension.install_state.flags) logger.info('state received from service') logger.info(state_from_service) if disable: flags = [x.strip() for x in state_from_service.split(',')] if 'disabled' in flags: raise CLIError('Extension is already in disabled state') flags.append('disabled') updated_state = ', '.join(flags) if enable: flags = [x.strip() for x in state_from_service.split(',')] if 'disabled' not in flags: raise CLIError('Extension is already in enabled state') flags.remove('disabled') updated_state = ', '.join(flags) current_extension.install_state.flags = updated_state return extension_client.update_installed_extension(current_extension)
def download_package(feed, name, version, path, file_filter=None, organization=None, detect=None): """Download a package. :param feed: Name or ID of the feed. :type feed: str :param name: Name of the package, e.g. 'foo-package'. :type name: str :param version: Version of the package, e.g. 1.0.0. :type version: str :param path: Directory to place the package contents. :type path: str :param file_filter: Wildcard filter for file download. :type file_filter: str """ colorama.init() # Needed for humanfriendly spinner to display correctly organization = resolve_instance(detect=detect, organization=organization) artifact_tool = ArtifactToolInvoker(ProgressReportingExternalToolInvoker(), ArtifactToolUpdater()) return artifact_tool.download_universal(organization, feed, name, version, path, file_filter)
def add_relation(id, relation_type, target_id=None, target_url=None, organization=None, detect=None): # pylint: disable=redefined-builtin """ Add relation(s) to work item. """ if target_id is None and target_url is None: raise CLIError('--target-id or --target-url must be provided') organization = resolve_instance(detect=detect, organization=organization) patch_document = [] client = get_work_item_tracking_client(organization) relation_types_from_service = client.get_relation_types() relation_type_system_name = get_system_relation_name( relation_types_from_service, relation_type) patch_document = [] if target_id is not None: target_work_item_ids = target_id.split(',') work_item_query_clause = [] for target_work_item_id in target_work_item_ids: work_item_query_clause.append( '[System.Id] = {}'.format(target_work_item_id)) wiql_query_format = 'SELECT [System.Id] FROM WorkItems WHERE ({})' wiql_query_to_get_target_work_items = wiql_query_format.format( ' OR '.join(work_item_query_clause)) wiql_object = Wiql() wiql_object.query = wiql_query_to_get_target_work_items target_work_items = client.query_by_wiql(wiql=wiql_object).work_items if len(target_work_items) != len(target_work_item_ids): raise CLIError('Id(s) supplied in --target-id is not valid') for target_work_item in target_work_items: op = _create_patch_operation('add', '/relations/-', relation_type_system_name, target_work_item.url) patch_document.append(op) if target_url is not None: target_urls = target_url.split(',') for url in target_urls: op = _create_patch_operation('add', '/relations/-', relation_type_system_name, url) patch_document.append(op) client.update_work_item(document=patch_document, id=id) work_item = client.get_work_item(id, expand='All') work_item = fill_friendly_name_for_relations_in_work_item( relation_types_from_service, work_item) return work_item
def remove_pull_request_work_items(id, work_items, organization=None, detect=None): # pylint: disable=redefined-builtin """Unlink one or more work items from a pull request. :param id: ID of the pull request. :type id: int :param work_items: IDs of the work items to unlink. Space separated. :type work_items: list of int :rtype: list of :class:`AssociatedWorkItem <v5_0.git.models.AssociatedWorkItem>` """ # pylint: disable=too-many-nested-blocks organization = resolve_instance(detect=detect, organization=organization) client = get_git_client(organization) existing_pr = client.get_pull_request_by_id(id) if work_items is not None and work_items: work_items = list(set(work_items)) # make distinct wit_client = get_work_item_tracking_client(organization) work_items_full = wit_client.get_work_items(ids=work_items, expand=1) if work_items_full: url = 'vstfs:///Git/PullRequestId/{project}%2F{repo}%2F{id}'.format( project=existing_pr.repository.project.id, repo=existing_pr.repository.id, id=id) for work_item in work_items_full: if work_item.relations is not None: index = 0 for relation in work_item.relations: if relation.url == url: patch_document = [] patch_test_operation = JsonPatchOperation() patch_test_operation.op = 'test' patch_test_operation.path = '/rev' patch_test_operation.value = work_item.rev patch_document.append(patch_test_operation) patch_operation = JsonPatchOperation() patch_operation.op = 1 patch_operation.path = '/relations/{index}'.format( index=index) patch_document.append(patch_operation) wit_client.update_work_item( document=patch_document, id=work_item.id) else: index += 1 refs = client.get_pull_request_work_item_refs( project=existing_pr.repository.project.id, repository_id=existing_pr.repository.id, pull_request_id=id) if refs: ids = [] for ref in refs: ids.append(ref.id) if ids: return wit_client.get_work_items(ids=ids) return None
def create_group(name=None, description=None, origin_id=None, email_id=None, groups=None, scope='project', project=None, organization=None, detect=None): """ :param name: Name of Azure DevOps group. :type name: str :param description: Description of Azure DevOps group. :type description: str :param origin_id: Create new group using the OriginID as a reference to an existing group from an external AD or AAD backed provider. Required if name or email-id is missing. :type origin_id: str :param email_id: Create new group using the mail address as a reference to an existing group from an external AD or AAD backed provider. Required if name or origin-id is missing. :type email_id: str :param groups: A comma separated list of descriptors referencing groups you want the newly created group to join. :type groups: [str] :param scope: Create group at project or organization level. :type scope: str :rtype: :class:`<GraphGroup> <azure.devops.v5_0.graph.models.GraphGroup>` """ if scope == 'project': organization, project = resolve_instance_and_project( detect=detect, organization=organization, project=project) else: organization = resolve_instance(detect=detect, organization=organization) client = get_graph_client(organization) if name is not None and origin_id is None and email_id is None: group_creation_context = GraphGroupVstsCreationContext( display_name=name, description=description) elif origin_id is not None and email_id is None and name is None: group_creation_context = GraphGroupOriginIdCreationContext( origin_id=origin_id) elif email_id is not None and name is None and origin_id is None: group_creation_context = GraphGroupMailAddressCreationContext( mail_address=email_id) else: raise CLIError( 'Provide exactly one argument out of name, origin-id or email-id.') scope_descriptor = None if project is not None: project_id = get_project_id_from_name(organization, project) scope_descriptor = get_descriptor_from_storage_key(project_id, client) if groups is not None: groups = groups.split(',') group_details = client.create_group( creation_context=group_creation_context, scope_descriptor=scope_descriptor, group_descriptors=groups) return group_details
def reset_all_permissions(namespace_id, subject, token, organization=None, detect=None): """ Clear all permissions of this token for a user/group. """ organization = resolve_instance(detect=detect, organization=organization) client = get_security_client(organization) subject = _resolve_subject_as_identity_descriptor(subject, organization) response = client.remove_access_control_entries(security_namespace_id=namespace_id, token=token, descriptors=subject) return response
def list_namespaces(local_only=False, organization=None, detect=None): """ List all available namespaces for an organization. :param local_only: If true, retrieve only local security namespaces. :type local_only: bool """ organization = resolve_instance(detect=detect, organization=organization) client = get_security_client(organization) response = client.query_security_namespaces(local_only=local_only) return response
def delete_group(id, organization=None, detect=None): # pylint: disable=redefined-builtin """Delete an Azure DevOps group. :param id: Descriptor of the group. :type id: str """ organization = resolve_instance(detect=detect, organization=organization) client = get_graph_client(organization) delete_group_details = client.delete_group(group_descriptor=id) return delete_group_details
def get_user_entitlements(top=100, skip=None, organization=None, detect=None): """List users in an organization [except for users which are added via AAD groups]. :param int top: Maximum number of users to return. Max value is 10000. :param int skip: Offset: Number of records to skip. :rtype: [UserEntitlement] """ organization = resolve_instance(detect=detect, organization=organization) client = get_member_entitlement_management_client(organization) user_entitlements = client.get_user_entitlements(top=top, skip=skip) return user_entitlements
def get_group(id, organization=None, detect=None): # pylint: disable=redefined-builtin """Show group details. :param id: Descriptor of the group. :type id: str :rtype: :class:`<GraphGroup> <azure.devops.v5_0.graph.models.GraphGroup>` """ organization = resolve_instance(detect=detect, organization=organization) client = get_graph_client(organization) group_details = client.get_group(group_descriptor=id) return group_details
def delete_user_entitlement(user, organization=None, detect=None): """Remove user from an organization. :param user: Email ID or ID of the user. :type user: str """ organization = resolve_instance(detect=detect, organization=organization) if '@' in user: user = resolve_identity_as_id(user, organization) client = get_member_entitlement_management_client(organization) delete_user_entitlement_details = client.delete_user_entitlement(user_id=user) return delete_user_entitlement_details
def show_work_item(id, organization=None, detect=None): # pylint: disable=redefined-builtin """ Get work item, fill relations with friendly name """ organization = resolve_instance(detect=detect, organization=organization) client = get_work_item_tracking_client(organization) work_item = client.get_work_item(id, expand='All') relation_types_from_service = client.get_relation_types() work_item = fill_friendly_name_for_relations_in_work_item( relation_types_from_service, work_item) return work_item