pax_global_header00006660000000000000000000000064134664017160014522gustar00rootroot0000000000000052 comment=c8249670acc77333e3de8b21dec60faf7ecf0951 azure-functions-devops-build-0.0.22/000077500000000000000000000000001346640171600173325ustar00rootroot00000000000000azure-functions-devops-build-0.0.22/.flake8000066400000000000000000000000371346640171600205050ustar00rootroot00000000000000[flake8] max-line-length = 120 azure-functions-devops-build-0.0.22/.gitignore000066400000000000000000000023761346640171600213320ustar00rootroot00000000000000# the test config file tests/_config.py python_test_application/ # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] *$py.class # C extensions *.so # Distribution / packaging .Python build/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ lib/ lib64/ parts/ sdist/ var/ wheels/ *.egg-info/ .installed.cfg *.egg MANIFEST # PyInstaller # Usually these files are written by a python script from a template # before PyInstaller builds the exe, so as to inject date/other infos into it. *.manifest *.spec # Installer logs pip-log.txt pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ .coverage .coverage.* .cache nosetests.xml coverage.xml *.cover .hypothesis/ .pytest_cache/ # Translations *.mo *.pot # Django stuff: *.log local_settings.py db.sqlite3 # Flask stuff: instance/ .webassets-cache # Scrapy stuff: .scrapy # Sphinx documentation docs/_build/ # PyBuilder target/ # Jupyter Notebook .ipynb_checkpoints # pyenv .python-version # celery beat schedule file celerybeat-schedule # SageMath parsed files *.sage.py # Environments .env .venv env/ venv/ ENV/ env.bak/ venv.bak/ # Spyder project settings .spyderproject .spyproject # Rope project settings .ropeproject # mkdocs documentation /site # mypy .mypy_cache/ .vscode azure-functions-devops-build-0.0.22/.pylintrc000066400000000000000000000032111346640171600211740ustar00rootroot00000000000000[MASTER] ignore-patterns=test_* reports=no [MESSAGES CONTROL] # For all codes, run 'pylint --list-msgs' or go to 'https://pylint.readthedocs.io/en/latest/reference_guide/features.html' # locally-disabled: Warning locally suppressed using disable-msg # cyclic-import: because of https://github.com/PyCQA/pylint/issues/850 # too-many-arguments: Due to the nature of the CLI many commands have large arguments set which reflect in large arguments set in corresponding methods. disable=missing-docstring,locally-disabled,fixme,cyclic-import,too-many-arguments,invalid-name,duplicate-code [FORMAT] max-line-length=120 [VARIABLES] # Tells whether we should check for unused import in __init__ files. init-import=yes [DESIGN] # Maximum number of locals for function / method body max-locals=25 # Maximum number of branch for function / method body max-branches=20 [SIMILARITIES] min-similarity-lines=10 [BASIC] # Naming hints based on PEP 8 (https://www.python.org/dev/peps/pep-0008/#naming-conventions). # Consider these guidelines and not hard rules. Read PEP 8 for more details. # The invalid-name checker must be **enabled** for these hints to be used. include-naming-hint=yes module-name-hint=lowercase (keep short; underscores are discouraged) const-name-hint=UPPER_CASE_WITH_UNDERSCORES class-name-hint=CapitalizedWords class-attribute-name-hint=lower_case_with_underscores attr-name-hint=lower_case_with_underscores method-name-hint=lower_case_with_underscores function-name-hint=lower_case_with_underscores argument-name-hint=lower_case_with_underscores variable-name-hint=lower_case_with_underscores inlinevar-name-hint=lower_case_with_underscores (short is OK) azure-functions-devops-build-0.0.22/LICENSE000066400000000000000000000022121346640171600203340ustar00rootroot00000000000000 MIT License Copyright (c) Microsoft Corporation. All rights reserved. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE azure-functions-devops-build-0.0.22/MANIFEST.in000066400000000000000000000001261346640171600210670ustar00rootroot00000000000000include azure_functions_devops_build/yaml/templates/*.jinja include LICENSE README.md azure-functions-devops-build-0.0.22/README.md000066400000000000000000000075411346640171600206200ustar00rootroot00000000000000# Azure Devops Build Manager For Azure Functions > :construction: The project is currently **work in progress**. Please **do not use in production** as we expect developments over time. :construction: This project provides the class AzureDevopsBuildManager and supporting classes. This manager class allows the caller to manage Azure Devops pipelines that are maintained within an Azure Devops account. This project was created to be able to support command line tooling for the AZ Cli. ## Install To install the package from pip: ``` pip install azure-functions-devops-build ``` ## Get started To use the API, you need to first establish a connection to azure by loging into your azure account using `az login`. You can then follow the example as below. Firstly we get the token from login and use this to authenticate the different python function calls. ```python from azure.cli.core import get_default_cli from azure.cli.core._profile import Profile from azure_functions_devops_build.organization.organization_manager import OrganizationManager import pprint # Get your token from the az login cache cli_ctx = get_default_cli() profile = Profile(cli_ctx=cli_ctx) creds, _, _ = profile.get_login_credentials(subscription_id=None) # Create an organization manager using your credentials organization_manager = OrganizationManager(creds=creds) # Get the list of organizations for your user organizations = organization_manager.list_organizations() # Show details about each organization in the console for organization in organizations.value: pprint.pprint(organization.__dict__) ``` ## API documentation This Python library extensively uses the Azure DevOps REST APIs and Azure Devops Python API. See the [Azure DevOps REST API reference](https://docs.microsoft.com/en-us/rest/api/vsts/?view=vsts-rest-5.0) for details on calling different APIs and [Azure DevOps Python SDK](https://github.com/Microsoft/azure-devops-python-api) for details on the azure-devops-python-api. ## Samples See samples by looking at tests or viewing the [az-cli functionapp devops-build module](https://github.com/Azure/azure-cli/tree/dev/src/command_modules/azure-cli-appservice/azure/cli/command_modules/appservice). ## Testing Several things need to be setup before you can run the tests: 1. Signed into the az cli. You can do this by using `az login`. 2. Since this directly deploys to azure functions, [create an azure functions functionapp using the azure portal](https://docs.microsoft.com/en-us/azure/azure-functions/functions-create-first-azure-function). You need to make a functionapp for these tests to successfully run and make sure you record the details of the subscription name, project name, application type and storage name. 3. Follow the tests/_config_example.py file, create a tests/_config.py with your own testing environment. 4. Run the full test suite using `python -m tests.suite` 5. To run specific manager tests run `python -m tests.{NAME_OF_MANAGER}` eg. `python -m tests.test_builder_manager` ## Contributing This project welcomes contributions and suggestions. Most contributions require you to agree to a Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us the rights to use your contribution. For details, visit https://cla.microsoft.com. When you submit a pull request, a CLA-bot will automatically determine whether you need to provide a CLA and decorate the PR appropriately (e.g., label, comment). Simply follow the instructions provided by the bot. You will only need to do this once across all repos using our CLA. This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. azure-functions-devops-build-0.0.22/__init__.py000066400000000000000000000005321346640171600214430ustar00rootroot00000000000000# -------------------------------------------------------------------------------------------- # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- azure-functions-devops-build-0.0.22/azure_functions_devops_build/000077500000000000000000000000001346640171600253075ustar00rootroot00000000000000azure-functions-devops-build-0.0.22/azure_functions_devops_build/__init__.py000066400000000000000000000005321346640171600274200ustar00rootroot00000000000000# -------------------------------------------------------------------------------------------- # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- azure-functions-devops-build-0.0.22/azure_functions_devops_build/artifact/000077500000000000000000000000001346640171600271045ustar00rootroot00000000000000azure-functions-devops-build-0.0.22/azure_functions_devops_build/artifact/__init__.py000066400000000000000000000005301346640171600312130ustar00rootroot00000000000000# -------------------------------------------------------------------------------------------- # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. See License.txt in the project root for license information. # --------------------------------------------------------------------------------------------azure-functions-devops-build-0.0.22/azure_functions_devops_build/artifact/artifact_manager.py000066400000000000000000000022051346640171600327440ustar00rootroot00000000000000# -------------------------------------------------------------------------------------------- # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- from ..base.base_manager import BaseManager from vsts.exceptions import VstsClientRequestError class ArtifactManager(BaseManager): """ Manage DevOps Artifacts Attributes: See BaseManager """ def __init__(self, organization_name="", project_name="", creds=None): """Inits ArtifactManager as per BaseManager and includes relevant clients""" super(ArtifactManager, self).__init__(creds, organization_name=organization_name, project_name=project_name) def list_artifacts(self, build_id): """Lists artifacts from a build""" project = self._get_project_by_name(self._project_name) try: result = self._build_client.get_artifacts(build_id, project.id) except VstsClientRequestError: return [] return result azure-functions-devops-build-0.0.22/azure_functions_devops_build/base/000077500000000000000000000000001346640171600262215ustar00rootroot00000000000000azure-functions-devops-build-0.0.22/azure_functions_devops_build/base/__init__.py000066400000000000000000000005311346640171600303310ustar00rootroot00000000000000# -------------------------------------------------------------------------------------------- # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- azure-functions-devops-build-0.0.22/azure_functions_devops_build/base/base_github_manager.py000066400000000000000000000022031346640171600325360ustar00rootroot00000000000000# -------------------------------------------------------------------------------------------- # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- from msrest.service_client import ServiceClient from msrest import Configuration class BaseGithubManager(object): def __init__(self, base_url='https://api.github.com', pat=None): """Inits UserManager as to be able to send the right requests""" self._pat = pat self._config = Configuration(base_url=base_url) self._client = ServiceClient(None, self._config) def construct_github_request_header(self, pat=None): headers = { "Accept": "application/vnd.github.v3+json" } if pat: headers["Authorization"] = "token {pat}".format(pat=pat) elif self._pat: headers["Authorization"] = "token {pat}".format(pat=self._pat) return headers def close_connection(self): self._client.close() azure-functions-devops-build-0.0.22/azure_functions_devops_build/base/base_manager.py000066400000000000000000000100071346640171600311750ustar00rootroot00000000000000# -------------------------------------------------------------------------------------------- # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- from vsts.vss_connection import VssConnection class BaseManager(object): """The basic manager which the other classes are build on Attributes: organization_name : The name of the DevOps organization project_name : The name of the DevOps project creds : These are credentials for an Azure user """ def __init__(self, creds, organization_name="", project_name="", repository_name="", pool_name=""): # Create the relevant name attributes self._organization_name = organization_name self._project_name = project_name self._creds = creds self._repository_name = repository_name self._pool_name = pool_name # Create the relevant clients that are needed by the managers self._connection = VssConnection(base_url='https://dev.azure.com/' + organization_name, creds=creds) self._agent_client = self._connection.get_client("vsts.task_agent.v4_1.task_agent_client.TaskAgentClient") self._build_client = self._connection.get_client('vsts.build.v4_1.build_client.BuildClient') self._core_client = self._connection.get_client('vsts.core.v4_0.core_client.CoreClient') self._extension_management_client = self._connection.get_client('vsts.extension_management.v4_1.extension_management_client.ExtensionManagementClient') # pylint: disable=line-too-long self._git_client = self._connection.get_client("vsts.git.v4_1.git_client.GitClient") self._release_client = self._connection.get_client('vsts.release.v4_1.release_client.ReleaseClient') self._service_endpoint_client = self._connection.get_client( 'vsts.service_endpoint.v4_1.service_endpoint_client.ServiceEndpointClient' ) def _get_project_by_name(self, project_name): """Helper function to get the project object from its name""" projects = self._core_client.get_projects() return next((project for project in projects if project.name == project_name), None) def _get_repository_by_name(self, project, repository_name): """Helper function to get the repository object from its name""" repositories = self._git_client.get_repositories(project.id) return next((repository for repository in repositories if repository.name == repository_name), None) def _get_definition_by_name(self, project, definition_name): """Helper function to get definition object from its name""" definitions = self._build_client.get_definitions(project.id) return next((definition for definition in definitions if definition.name == definition_name), None) def _get_build_by_name(self, project, name): """Helper function to get build object from its name""" builds_unsorted = self._build_client.get_builds(project=project.id) builds = sorted(builds_unsorted, key=lambda x: x.start_time, reverse=True) return next((build for build in builds if build.definition.name == name), None) def _get_github_repository_by_name(self, github_repository_name): """Helper function to get a github repository object from its name""" service_endpoints = self._service_endpoint_client.get_service_endpoints(self._project_name, type="github") github_endpoint = service_endpoints[0] repositories = self._build_client.list_repositories( project=self._project_name, provider_name='github', service_endpoint_id=github_endpoint.id, repository=github_repository_name ) repository_match = next(( repository for repository in repositories.repositories if repository.full_name == github_repository_name ), None) return repository_match azure-functions-devops-build-0.0.22/azure_functions_devops_build/builder/000077500000000000000000000000001346640171600267355ustar00rootroot00000000000000azure-functions-devops-build-0.0.22/azure_functions_devops_build/builder/__init__.py000066400000000000000000000005311346640171600310450ustar00rootroot00000000000000# -------------------------------------------------------------------------------------------- # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- azure-functions-devops-build-0.0.22/azure_functions_devops_build/builder/builder_manager.py000066400000000000000000000240021346640171600324250ustar00rootroot00000000000000# -------------------------------------------------------------------------------------------- # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- import os import time from collections import OrderedDict import vsts.build.v4_1.models as build_models from vsts.exceptions import VstsServiceError from ..base.base_manager import BaseManager from ..pool.pool_manager import PoolManager from ..exceptions import ( GithubIntegrationRequestError, GithubContentNotFound, BuildErrorException ) class BuilderManager(BaseManager): """ Manage DevOps Builds This class enables users to create DevOps build definitions and builds specifically for yaml file builds. It can also be used to retrieve existing build definitions and builds. Attributes: See BaseManager """ def __init__(self, organization_name="", project_name="", repository_name="", creds=None): """Inits BuilderManager as per BaseManager""" super(BuilderManager, self).__init__(creds, organization_name, project_name, repository_name=repository_name) def create_devops_build_definition(self, build_definition_name, pool_name): """Create a build definition in Azure DevOps""" project = self._get_project_by_name(self._project_name) pool = self._get_pool_by_name(pool_name) # create the relevant objects that are needed for the build definition (this is the minimum amount needed) pool_queue = build_models.agent_pool_queue.AgentPoolQueue(id=pool.id, name=pool.name) repository = self._get_repository_by_name(project, self._repository_name) build_repository = build_models.build_repository.BuildRepository( default_branch="master", id=repository.id, name=repository.name, type="TfsGit" ) team_project_reference = self._get_project_reference(project) build_definition = self._get_build_definition( team_project_reference, build_repository, build_definition_name, pool_queue ) return self._build_client.create_definition(build_definition, project=project.name) def create_github_build_definition(self, build_definition_name, pool_name, github_repository): project = self._get_project_by_name(self._project_name) pool = self._get_pool_by_name(pool_name) pool_queue = build_models.agent_pool_queue.AgentPoolQueue(id=pool.id, name=pool.name) repository = self._get_github_repository_by_name(github_repository) if repository is None: raise GithubContentNotFound() github_properties = repository.properties build_repository = build_models.build_repository.BuildRepository( default_branch="master", id=repository.id, properties=github_properties, name=repository.full_name, type="GitHub", url=repository.properties['cloneUrl'] ) team_project_reference = self._get_project_reference(project) build_definition = self._get_build_definition( team_project_reference, build_repository, build_definition_name, pool_queue ) try: result = self._build_client.create_definition(build_definition, project=project.name) except VstsServiceError as vse: raise GithubIntegrationRequestError(vse.message) return result def list_definitions(self): """List the build definitions that exist in Azure DevOps""" project = self._get_project_by_name(self._project_name) return self._build_client.get_definitions(project=project.id) def create_build(self, build_definition_name, pool_name): """Create a build definition in Azure DevOps""" pool = self._get_pool_by_name(pool_name) project = self._get_project_by_name(self._project_name) definition = self._get_definition_by_name(project, build_definition_name) # create the relevant objects that are needed for the build (this is the minimum amount needed) team_project_reference = self._get_project_reference(project) build_definition_reference = self._get_build_definition_reference(team_project_reference, definition) pool_queue = build_models.agent_pool_queue.AgentPoolQueue(id=pool.id, name=pool_name) build = build_models.build.Build(definition=build_definition_reference, queue=pool_queue) try: result = self._build_client.queue_build(build, project=project.id) except VstsServiceError as vse: raise BuildErrorException(vse.message) return result def list_builds(self): """List the builds that exist in Azure DevOps""" project = self._get_project_by_name(self._project_name) return self._build_client.get_builds(project=project.id) # Returns a dictionary containing the log status # { # : # } def get_build_logs_status(self, build_id): try: build_logs = self._build_client.get_build_logs(self._project_name, build_id) except VstsServiceError as vse: raise BuildErrorException(vse.message) result = OrderedDict() for build_log in build_logs: result[build_log.id] = build_log return result # Return the log content by the difference between two logs def get_build_logs_content_from_statuses(self, build_id, prev_logs_status=None, curr_logs_status=None): if prev_logs_status is None: prev_logs_status = {} if curr_logs_status is None: curr_logs_status = {} result = [] for log_id in curr_logs_status: log_content = self._get_log_content_by_id( build_id, prev_logs_status.get(log_id), curr_logs_status.get(log_id) ) result.extend(log_content) return os.linesep.join(result) # Return the log content by single build_log def _get_log_content_by_id(self, build_id, prev_log_status=None, curr_log_status=None): if prev_log_status is None or prev_log_status.line_count is None: starting_line = 0 else: starting_line = prev_log_status.line_count if curr_log_status is None or curr_log_status.line_count is None: ending_line = 0 else: ending_line = curr_log_status.line_count if starting_line >= ending_line: return [] try: result = self._build_client.get_build_log_lines( self._project_name, build_id, curr_log_status.id, starting_line, ending_line ) except VstsServiceError as vse: raise BuildErrorException(vse.message) return result def _get_build_by_id(self, build_id): builds = self.list_builds() return next((build for build in builds if build.id == build_id)) def poll_build(self, build_name): project = self._get_project_by_name(self._project_name) build = self._get_build_by_name(project, build_name) while build.status != 'completed': time.sleep(1) build = self._get_build_by_id(build.id) return build def _get_pool_by_name(self, pool_name): """Helper function to get the pool object from its name""" pool_manager = PoolManager(organization_name=self._organization_name, project_name=self._project_name, creds=self._creds) pools = pool_manager.list_pools() return next((pool for pool in pools.value if pool.name == pool_name), None) def _get_process(self): """Helper function to create process dictionary""" process = {} process["yamlFilename"] = "azure-pipelines.yml" process["type"] = 2 process["resources"] = {} return process def _get_project_reference(self, project): """Helper function to create project reference""" team_project_reference = build_models.team_project_reference.TeamProjectReference( abbreviation=project.abbreviation, description=project.description, id=project.id, name=project.name, revision=project.revision, state=project.state, url=project.url, visibility=project.visibility ) return team_project_reference def _get_triggers(self): trigger = {} trigger["branchFilters"] = [] trigger["pathFilters"] = [] trigger["settingsSourceType"] = 2 trigger["batchChanges"] = False trigger["maxConcurrentBuildsPerBranch"] = 1 trigger["triggerType"] = "continuousIntegration" triggers = [trigger] return triggers def _get_build_definition(self, team_project_reference, build_repository, build_definition_name, pool_queue): """Helper function to create build definition""" process = self._get_process() triggers = self._get_triggers() build_definition = build_models.build_definition.BuildDefinition( project=team_project_reference, type=2, name=build_definition_name, process=process, repository=build_repository, triggers=triggers, queue=pool_queue ) return build_definition def _get_build_definition_reference(self, team_project_reference, build_definition): """Helper function to create build definition reference""" build_definition_reference = build_models.definition_reference.DefinitionReference( created_date=build_definition.created_date, project=team_project_reference, type=build_definition.type, name=build_definition.name, id=build_definition.id ) return build_definition_reference azure-functions-devops-build-0.0.22/azure_functions_devops_build/constants.py000066400000000000000000000010261346640171600276740ustar00rootroot00000000000000# -------------------------------------------------------------------------------------------- # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- LINUX_CONSUMPTION = 0 LINUX_DEDICATED = 1 WINDOWS = 2 PYTHON = "python" NODE = "node" DOTNET = "dotnet" JAVA = "java" POWERSHELL = "powershell" SERVICE_ENDPOINT_DOMAIN = "dev.azure.com" azure-functions-devops-build-0.0.22/azure_functions_devops_build/exceptions.py000066400000000000000000000016061346640171600300450ustar00rootroot00000000000000# -------------------------------------------------------------------------------------------- # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- class BaseException(Exception): def __init__(self, message=None): self.message = message class GitOperationException(BaseException): pass class RoleAssignmentException(BaseException): pass class LanguageNotSupportException(BaseException): pass class BuildErrorException(BaseException): pass class ReleaseErrorException(BaseException): pass class GithubContentNotFound(BaseException): pass class GithubIntegrationRequestError(BaseException): pass class GithubUnauthorizedError(BaseException): pass azure-functions-devops-build-0.0.22/azure_functions_devops_build/extension/000077500000000000000000000000001346640171600273235ustar00rootroot00000000000000azure-functions-devops-build-0.0.22/azure_functions_devops_build/extension/__init__.py000066400000000000000000000005311346640171600314330ustar00rootroot00000000000000# -------------------------------------------------------------------------------------------- # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- azure-functions-devops-build-0.0.22/azure_functions_devops_build/extension/extension_manager.py000066400000000000000000000031611346640171600334040ustar00rootroot00000000000000# -------------------------------------------------------------------------------------------- # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- from ..base.base_manager import BaseManager class ExtensionManager(BaseManager): """ Manage DevOps Extensions Install a new extension within an organization or view existing extensions. Attributes: See BaseManager """ def __init__(self, organization_name="", creds=None): """Inits ExtensionManager as per BaseManager""" super(ExtensionManager, self).__init__(creds, organization_name=organization_name) def create_extension(self, extension_name, publisher_name): """Installs an extension in Azure DevOps if it does not already exist""" extensions = self.list_extensions() extension = next((extension for extension in extensions if (extension.publisher_id == publisher_name) and (extension.extension_id == extension_name)), None) # If the extension wasn't in the installed extensions than we know we need to install it if extension is None: extension = self._extension_management_client.install_extension_by_name(publisher_name, extension_name) return extension def list_extensions(self): """Lists an extensions already installed in Azure DevOps""" return self._extension_management_client.get_installed_extensions() azure-functions-devops-build-0.0.22/azure_functions_devops_build/organization/000077500000000000000000000000001346640171600300135ustar00rootroot00000000000000azure-functions-devops-build-0.0.22/azure_functions_devops_build/organization/__init__.py000066400000000000000000000005301346640171600321220ustar00rootroot00000000000000# -------------------------------------------------------------------------------------------- # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. See License.txt in the project root for license information. # --------------------------------------------------------------------------------------------azure-functions-devops-build-0.0.22/azure_functions_devops_build/organization/models/000077500000000000000000000000001346640171600312765ustar00rootroot00000000000000azure-functions-devops-build-0.0.22/azure_functions_devops_build/organization/models/__init__.py000066400000000000000000000013671346640171600334160ustar00rootroot00000000000000# -------------------------------------------------------------------------------------------- # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- from .validate_account_name import ValidateAccountName from .region_details import RegionDetails from .regions import Regions from .organizations import Organizations from .new_organization import NewOrganization from .organization_details import OrganizationDetails __all__ = [ 'ValidateAccountName', 'RegionDetails', 'Regions', 'Organization', 'NewOrganization', 'OrganizationDetails' ]new_organization.py000066400000000000000000000013071346640171600351470ustar00rootroot00000000000000azure-functions-devops-build-0.0.22/azure_functions_devops_build/organization/models# -------------------------------------------------------------------------------------------- # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- from msrest.serialization import Model class NewOrganization(Model): _attribute_map = { 'id': {'key': 'id', 'type': 'str'}, 'name': {'key': 'name', 'type': 'str'}, 'data': {'key': 'data', 'type': 'str'}, } def __init__(self, id=None, name=None, data=None): self.id = id self.name = name self.data = dataorganization_details.py000066400000000000000000000075631346640171600360150ustar00rootroot00000000000000azure-functions-devops-build-0.0.22/azure_functions_devops_build/organization/models# -------------------------------------------------------------------------------------------- # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- from msrest.serialization import Model class OrganizationDetails(Model): _attribute_map = { 'accountId': {'key': 'accountId', 'type': 'str'}, 'accountHostType': {'key': 'accountHostType', 'type': 'str'}, 'accountName': {'key': 'accountName', 'type': 'str'}, 'subscriptionId': {'key': 'subscriptionId', 'type': 'str'}, 'subscriptionStatus': {'key': 'subscriptionStatus', 'type': 'str'}, 'resourceGroupName': {'key': 'resourceGroupName', 'type': 'str'}, 'geoLocation': {'key': 'geoLocation', 'type': 'str'}, 'locale': {'key': 'locale', 'type': 'str'}, 'regionDisplayName': {'key': 'regionDisplayName', 'type': 'str'}, 'serviceUrls': {'key': 'serviceUrls', 'type': 'str'}, 'accountTenantId': {'key': 'accountTenantId', 'type': 'str'}, 'isAccountOwner': {'key': 'isAccountOwner', 'type': 'str'}, 'resourceName': {'key': 'resourceName', 'type': 'str'}, 'subscriptionName': {'key': 'subscriptionName', 'type': 'str'}, 'isEligibleForPurchase': {'key': 'isEligibleForPurchase', 'type': 'str'}, 'isPrepaidFundSubscription': {'key': 'isPrepaidFundSubscription', 'type': 'str'}, 'isPricingAvailable': {'key': 'isPricingAvailable', 'type': 'str'}, 'subscriptionOfferCode': {'key': 'subscriptionOfferCode', 'type': 'str'}, 'offerType': {'key': 'offerType', 'type': 'str'}, 'subscriptionTenantId': {'key': 'subscriptionTenantId', 'type': 'str'}, 'subscriptionObjectId': {'key': 'subscriptionObjectId', 'type': 'str'}, 'failedPurchaseReason': {'key': 'failedPurchaseReason', 'type': 'str'} } def __init__(self , accountId=None , accountHostType=None , accountName=None , subscriptionId=None , subscriptionStatus=None , resourceGroupName=None , geoLocation=None , locale=None , regionDisplayName=None , serviceUrls=None , accountTenantId=None , isAccountOwner=None , resourceName=None , subscriptionName=None , isEligibleForPurchase=None , isPrepaidFundSubscription=None , isPricingAvailable=None , subscriptionOfferCode=None , offerType=None , subscriptionTenantId=None , subscriptionObjectId=None , failedPurchaseReason=None): self.accountId = accountId self.accountHostType = accountHostType self.accountName = accountName self.subscriptionId = subscriptionId self.subscriptionStatus = subscriptionStatus self.resourceGroupName = resourceGroupName self.geoLocation = geoLocation self.locale = locale self.regionDisplayName = regionDisplayName self.serviceUrls = serviceUrls self.accountTenantId = accountTenantId self.isAccountOwner = isAccountOwner self.resourceName = resourceName self.subscriptionName = subscriptionName self.isEligibleForPurchase = isEligibleForPurchase self.isPrepaidFundSubscription = isPrepaidFundSubscription self.isPricingAvailable = isPricingAvailable self.subscriptionOfferCode = subscriptionOfferCode self.offerType = offerType self.subscriptionTenantId = subscriptionTenantId self.subscriptionObjectId = subscriptionObjectId self.failedPurchaseReason = failedPurchaseReason organizations.py000066400000000000000000000012301346640171600344540ustar00rootroot00000000000000azure-functions-devops-build-0.0.22/azure_functions_devops_build/organization/models# -------------------------------------------------------------------------------------------- # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- from msrest.serialization import Model class Organizations(Model): _attribute_map = { 'count': {'key': 'count', 'type': 'int'}, 'value': {'key': 'value', 'type': '[OrganizationDetails]'}, } def __init__(self, count=None, value=None): self.count = count self.value = value region_details.py000066400000000000000000000014251346640171600345630ustar00rootroot00000000000000azure-functions-devops-build-0.0.22/azure_functions_devops_build/organization/models# -------------------------------------------------------------------------------------------- # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- from msrest.serialization import Model class RegionDetails(Model): _attribute_map = { 'name': {'key': 'name', 'type': 'str'}, 'display_name': {'key': 'displayName', 'type': 'str'}, 'regionCode': {'key': 'regionCode', 'type': 'str'}, } def __init__(self, name=None, display_name=None, regionCode=None): self.name = name self.display_name = display_name self.regionCode = regionCode azure-functions-devops-build-0.0.22/azure_functions_devops_build/organization/models/regions.py000066400000000000000000000012131346640171600333130ustar00rootroot00000000000000# -------------------------------------------------------------------------------------------- # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- from msrest.serialization import Model class Regions(Model): _attribute_map = { 'count': {'key': 'count', 'type': 'int'}, 'value': {'key': 'value', 'type': '[RegionDetails]'}, } def __init__(self, count=None, value=None): self.count = count self.value = valuevalidate_account_name.py000066400000000000000000000012271346640171600361000ustar00rootroot00000000000000azure-functions-devops-build-0.0.22/azure_functions_devops_build/organization/models# -------------------------------------------------------------------------------------------- # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- from msrest.serialization import Model class ValidateAccountName(Model): _attribute_map = { 'valid': {'key': 'valid', 'type': 'bool'}, 'message': {'key': 'message', 'type': 'str'}, } def __init__(self, valid=None, message=None): self.valid = valid self.message = message organization_manager.py000066400000000000000000000206171346640171600345120ustar00rootroot00000000000000azure-functions-devops-build-0.0.22/azure_functions_devops_build/organization# -------------------------------------------------------------------------------------------- # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- import re import logging from msrest.service_client import ServiceClient from msrest import Configuration, Deserializer from msrest.exceptions import HttpOperationError from ..user.user_manager import UserManager from . import models class OrganizationManager(): """ Manage DevOps organizations Create or list existing organizations Attributes: config: url configuration client: authentication client dserialize: deserializer to process http responses into python classes """ def __init__(self, base_url='https://app.vssps.visualstudio.com', creds=None, create_organization_url='https://app.vsaex.visualstudio.com'): """Inits OrganizationManager""" self._creds = creds self._config = Configuration(base_url=base_url) self._client = ServiceClient(creds, self._config) #need to make a secondary client for the creating organization as it uses a different base url self._create_organization_config = Configuration(base_url=create_organization_url) self._create_organization_client = ServiceClient(creds, self._create_organization_config) self._list_region_config = Configuration(base_url='https://aex.dev.azure.com') self._list_region_client = ServiceClient(creds, self._create_organization_config) client_models = {k: v for k, v in models.__dict__.items() if isinstance(v, type)} self._deserialize = Deserializer(client_models) self._user_mgr = UserManager(creds=self._creds) def validate_organization_name(self, organization_name): """Validate an organization name by checking it does not already exist and that it fits name restrictions""" if organization_name is None: return models.ValidateAccountName(valid=False, message="The organization_name cannot be None") if re.search("[^0-9A-Za-z-]", organization_name): return models.ValidateAccountName(valid=False, message="""The name supplied contains forbidden characters. Only alphanumeric characters and dashes are allowed. Please try another organization name.""") #construct url url = '/_AzureSpsAccount/ValidateAccountName' #construct query parameters query_paramters = {} query_paramters['accountName'] = organization_name #construct header parameters header_paramters = {} header_paramters['Accept'] = 'application/json' request = self._client.get(url, params=query_paramters) response = self._client.send(request, headers=header_paramters) # Handle Response deserialized = None if response.status_code // 100 != 2: logging.error("GET %s", request.url) logging.error("response: %s", response.status_code) logging.error(response.text) raise HttpOperationError(self._deserialize, response) else: deserialized = self._deserialize('ValidateAccountName', response) return deserialized def list_organizations(self): """List what organizations this user is part of""" if not self._user_mgr.is_msa_account(): # Only need to do the one request as ids are the same organizations = self._list_organizations_request(self._user_mgr.aad_id) else: # Need to do a request for each of the ids and then combine them (disabled) #organizations_aad = self._list_organizations_request(self._user_mgr.aad_id, msa=False) #organizations_msa = self._list_organizations_request(self._user_mgr.msa_id, msa=True) #organizations = organizations_msa # Overwrite merge aad organizations with msa organizations #duplicated_aad_orgs = [] #for msa_org in organizations_msa.value: # duplicated_aad_orgs.extend([ # o for o in organizations_aad.value if o.accountName == msa_org.accountName # ]) #filtered_organizations_aad = [o for o in organizations_aad.value if (o not in duplicated_aad_orgs)] #organizations.value += list(filtered_organizations_aad) #organizations.count = len(organizations.value) organizations = self._list_organizations_request(self._user_mgr.msa_id, msa=True) return organizations def _list_organizations_request(self, member_id, msa=False): url = '/_apis/Commerce/Subscription' query_paramters = {} query_paramters['memberId'] = member_id query_paramters['includeMSAAccounts'] = True query_paramters['queryOnlyOwnerAccounts'] = True query_paramters['inlcudeDisabledAccounts'] = False query_paramters['providerNamespaceId'] = 'VisualStudioOnline' #construct header parameters header_parameters = {} header_parameters['X-VSS-ForceMsaPassThrough'] = 'true' if msa else 'false' header_parameters['Accept'] = 'application/json' request = self._client.get(url, params=query_paramters) response = self._client.send(request, headers=header_parameters) # Handle Response deserialized = None if response.status_code // 100 != 2: logging.error("GET %s", request.url) logging.error("response: %s", response.status_code) logging.error(response.text) raise HttpOperationError(self._deserialize, response) else: deserialized = self._deserialize('Organizations', response) return deserialized def create_organization(self, region_code, organization_name): """Create a new organization for user""" url = '/_apis/HostAcquisition/collections' #construct query parameters query_paramters = {} query_paramters['collectionName'] = organization_name query_paramters['preferredRegion'] = region_code query_paramters['api-version'] = '4.0-preview.1' #construct header parameters header_paramters = {} header_paramters['Accept'] = 'application/json' header_paramters['Content-Type'] = 'application/json' if self._user_mgr.is_msa_account(): header_paramters['X-VSS-ForceMsaPassThrough'] = 'true' #construct the payload payload = {} payload['VisualStudio.Services.HostResolution.UseCodexDomainForHostCreation'] = 'true' request = self._create_organization_client.post(url=url, params=query_paramters, content=payload) response = self._create_organization_client.send(request, headers=header_paramters) # Handle Response deserialized = None if response.status_code // 100 != 2: logging.error("GET %s", request.url) logging.error("response: %s", response.status_code) logging.error(response.text) raise HttpOperationError(self._deserialize, response) else: deserialized = self._deserialize('NewOrganization', response) return deserialized def list_regions(self): """List what regions organizations can exist in""" # Construct URL url = '/_apis/hostacquisition/regions' #construct header parameters header_paramters = {} header_paramters['Accept'] = 'application/json' # Construct and send request request = self._list_region_client.get(url, headers=header_paramters) response = self._list_region_client.send(request) # Handle Response deserialized = None if response.status_code // 100 != 2: logging.error("GET %s", request.url) logging.error("response: %s", response.status_code) logging.error(response.text) raise HttpOperationError(self._deserialize, response) else: deserialized = self._deserialize('Regions', response) return deserialized def close_connection(self): """Close the sessions""" self._client.close() self._create_organization_client.close() azure-functions-devops-build-0.0.22/azure_functions_devops_build/pool/000077500000000000000000000000001346640171600262605ustar00rootroot00000000000000azure-functions-devops-build-0.0.22/azure_functions_devops_build/pool/__init__.py000066400000000000000000000005311346640171600303700ustar00rootroot00000000000000# -------------------------------------------------------------------------------------------- # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- azure-functions-devops-build-0.0.22/azure_functions_devops_build/pool/models/000077500000000000000000000000001346640171600275435ustar00rootroot00000000000000azure-functions-devops-build-0.0.22/azure_functions_devops_build/pool/models/__init__.py000066400000000000000000000002651346640171600316570ustar00rootroot00000000000000from .pools import Pools from .pool_details import PoolDetails from .pool_details_depth import PoolDetailsDepth __all__ = [ 'Pools', 'PoolDetails', 'PoolDetailsDepth' ]azure-functions-devops-build-0.0.22/azure_functions_devops_build/pool/models/pool_details.py000066400000000000000000000007551346640171600326020ustar00rootroot00000000000000from msrest.serialization import Model class PoolDetails(Model): _attribute_map = { 'id': {'key': 'id', 'type': 'str'}, 'projectId': {'key': 'projectId', 'type': 'str'}, 'name': {'key': 'name', 'type': 'str'}, 'pool': {'key': 'subscriptionId', 'type': 'PoolDetailsDepth'} } def __init__(self, id=None, projectId=None, name=None,pool=None): self.id = id self.projectId = projectId self.name = name self.pool = pool azure-functions-devops-build-0.0.22/azure_functions_devops_build/pool/models/pool_details_depth.py000066400000000000000000000012271346640171600337610ustar00rootroot00000000000000from msrest.serialization import Model class PoolDetailsDepth(Model): _attribute_map = { 'id': {'key': 'id', 'type': 'str'}, 'scope': {'key': 'scope', 'type': 'str'}, 'name': {'key': 'name', 'type': 'str'}, 'isHosted': {'key': 'isHosted', 'type': 'str'}, 'poolType': {'key': 'poolType', 'type': 'str'}, 'size': {'key': 'size', 'type': 'str'}, } def __init__(self, id=None, scope=None, name=None, isHosted=None, poolType=None, size=None): self.id = id self.scope = scope self.name = name self.isHosted = isHosted self.poolType = poolType self.size = sizeazure-functions-devops-build-0.0.22/azure_functions_devops_build/pool/models/pools.py000066400000000000000000000004551346640171600312550ustar00rootroot00000000000000from msrest.serialization import Model class Pools(Model): _attribute_map = { 'count': {'key': 'count', 'type': 'int'}, 'value': {'key': 'value', 'type': '[PoolDetails]'}, } def __init__(self, count=None, value=None): self.count = count self.value = valueazure-functions-devops-build-0.0.22/azure_functions_devops_build/pool/pool_manager.py000066400000000000000000000046521346640171600313040ustar00rootroot00000000000000# -------------------------------------------------------------------------------------------- # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- import logging from msrest.service_client import ServiceClient from msrest import Configuration, Deserializer from msrest.exceptions import HttpOperationError from ..user.user_manager import UserManager from ..base.base_manager import BaseManager from . import models class PoolManager(BaseManager): """ Manage DevOps Pools Attributes: See BaseManager """ def __init__(self, base_url='https://{}.visualstudio.com', creds=None, organization_name="", project_name=""): """Inits PoolManager""" super(PoolManager, self).__init__(creds, organization_name=organization_name, project_name=project_name) base_url = base_url.format(organization_name) self._config = Configuration(base_url=base_url) self._client = ServiceClient(creds, self._config) client_models = {k: v for k, v in models.__dict__.items() if isinstance(v, type)} self._deserialize = Deserializer(client_models) self._user_mgr = UserManager(creds=self._creds) def list_pools(self): """List what pools this project has""" project = self._get_project_by_name(self._project_name) # Construct URL url = "/" + project.id + "/_apis/distributedtask/queues?actionFilter=16" #construct header parameters header_paramters = {} if self._user_mgr.is_msa_account(): header_paramters['X-VSS-ForceMsaPassThrough'] = 'true' header_paramters['Accept'] = 'application/json' # Construct and send request request = self._client.get(url, headers=header_paramters) response = self._client.send(request) # Handle Response deserialized = None if response.status_code // 100 != 2: logging.error("GET %s", request.url) logging.error("response: %s", response.status_code) logging.error(response.text) raise HttpOperationError(self._deserialize, response) else: deserialized = self._deserialize('Pools', response) return deserialized def close_connection(self): self._client.close() azure-functions-devops-build-0.0.22/azure_functions_devops_build/project/000077500000000000000000000000001346640171600267555ustar00rootroot00000000000000azure-functions-devops-build-0.0.22/azure_functions_devops_build/project/__init__.py000066400000000000000000000005311346640171600310650ustar00rootroot00000000000000# -------------------------------------------------------------------------------------------- # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- azure-functions-devops-build-0.0.22/azure_functions_devops_build/project/models/000077500000000000000000000000001346640171600302405ustar00rootroot00000000000000azure-functions-devops-build-0.0.22/azure_functions_devops_build/project/models/__init__.py000066400000000000000000000011211346640171600323440ustar00rootroot00000000000000# -------------------------------------------------------------------------------------------- # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- from .project_details import ProjectDetails from .projects import Projects from .project_poll import ProjectPoll from .project_failed import ProjectFailed __all__ = [ 'ProjectDetails', 'ProjectPoll', 'Projects', 'ProjectFailed' ] azure-functions-devops-build-0.0.22/azure_functions_devops_build/project/models/project_details.py000066400000000000000000000017641346640171600337750ustar00rootroot00000000000000# -------------------------------------------------------------------------------------------- # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- from msrest.serialization import Model class ProjectDetails(Model): _attribute_map = { 'id': {'key': 'id', 'type': 'str'}, 'name': {'key': 'name', 'type': 'str'}, 'url': {'key': 'url', 'type': 'str'}, 'state': {'key': 'state', 'type': 'str'}, 'revision': {'key': 'revision', 'type': 'str'}, 'visibility': {'key': 'visibility', 'type': 'str'}, } def __init__(self, id=None, name=None, url=None, state=None, revision=None, visibility=None): self.id = id self.name = name self.url = url self.state = state self.revision = revision self.visibility = visibilityazure-functions-devops-build-0.0.22/azure_functions_devops_build/project/models/project_failed.py000066400000000000000000000007231346640171600335660ustar00rootroot00000000000000# -------------------------------------------------------------------------------------------- # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- class ProjectFailed(object): def __init__(self, message): self.valid = False self.message = message azure-functions-devops-build-0.0.22/azure_functions_devops_build/project/models/project_poll.py000066400000000000000000000014461346640171600333130ustar00rootroot00000000000000# -------------------------------------------------------------------------------------------- # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- from msrest.serialization import Model class ProjectPoll(Model): _attribute_map = { 'id': {'key': 'id', 'type': 'str'}, 'status': {'key': 'status', 'type': 'str'}, 'url': {'key': 'url', 'type': 'str'}, '_links': {'key': '_links', 'type': 'str'} } def __init__(self, id=None, status=None, url=None, _links=None): self.id = id self.status = status self.url = url self._links = _links azure-functions-devops-build-0.0.22/azure_functions_devops_build/project/models/projects.py000066400000000000000000000012151346640171600324420ustar00rootroot00000000000000# -------------------------------------------------------------------------------------------- # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- from msrest.serialization import Model class Projects(Model): _attribute_map = { 'count': {'key': 'count', 'type': 'int'}, 'value': {'key': 'value', 'type': '[ProjectDetails]'}, } def __init__(self, count=None, value=None): self.count = count self.value = valueazure-functions-devops-build-0.0.22/azure_functions_devops_build/project/project_manager.py000066400000000000000000000127321346640171600324740ustar00rootroot00000000000000# -------------------------------------------------------------------------------------------- # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- import time import logging from msrest.service_client import ServiceClient from msrest import Configuration, Deserializer from msrest.exceptions import HttpOperationError from vsts.exceptions import VstsServiceError import vsts.core.v4_1.models.team_project as team_project from ..user.user_manager import UserManager from ..base.base_manager import BaseManager from . import models class ProjectManager(BaseManager): """ Manage DevOps projects Create or list existing projects Attributes: config: url configuration client: authentication client dserialize: deserializer to process http responses into python classes Otherwise see BaseManager """ def __init__(self, base_url='https://{}.visualstudio.com', organization_name="", creds=None, create_project_url='https://dev.azure.com'): """Inits Project as per BaseManager and adds relevant other needed fields""" super(ProjectManager, self).__init__(creds, organization_name=organization_name) base_url = base_url.format(organization_name) self._config = Configuration(base_url=base_url) self._client = ServiceClient(creds, self._config) self._credentials = creds # Need to make a secondary client for the creating project as it uses a different base url self._create_project_config = Configuration(base_url=create_project_url) self._create_project_client = ServiceClient(creds, self._create_project_config) client_models = {k: v for k, v in models.__dict__.items() if isinstance(v, type)} self._deserialize = Deserializer(client_models) self._user_mgr = UserManager(creds=self._creds) def create_project(self, projectName): """Create a new project for an organization""" try: capabilities = dict() capabilities['versioncontrol'] = {"sourceControlType": "Git"} capabilities['processTemplate'] = {"templateTypeId": "adcc42ab-9882-485e-a3ed-7678f01f66bc"} project = team_project.TeamProject( description="Azure Functions Devops Build created project", name=projectName, visibility=0, capabilities=capabilities ) queue_object = self._core_client.queue_create_project(project) queue_id = queue_object.id self._poll_project(queue_id) # Sleep as there is normally a gap between project finishing being made and when we can retrieve it time.sleep(1) project = self._get_project_by_name(projectName) project.valid = True return project except VstsServiceError as e: return models.ProjectFailed(e) def list_projects(self): """Lists the current projects within an organization""" url = '/_apis/projects' # First pass without X-VSS-ForceMsaPassThrough header response = self._list_projects_request(url) deserialized = None if response.status_code // 100 != 2: logging.error("GET %s", response.url) logging.error("response: %s", response.status_code) logging.error(response.text) raise HttpOperationError(self._deserialize, response) else: deserialized = self._deserialize('Projects', response) return deserialized def _list_projects_request(self, url): query_paramters = {} query_paramters['includeCapabilities'] = 'true' header_paramters = {} if self._user_mgr.is_msa_account(): header_paramters['X-VSS-ForceMsaPassThrough'] = 'true' request = self._client.get(url, params=query_paramters) response = self._client.send(request, headers=header_paramters) return response def _poll_project(self, project_id): """Helper function to poll the project""" project_created = False while not project_created: time.sleep(1) res = self._is_project_created(project_id) logging.info('project creation is: %s', res.status) if res.status == 'succeeded': project_created = True def _is_project_created(self, project_id): """Helper function to see the status of a project""" url = '/' + self._organization_name + '/_apis/operations/' + project_id query_paramters = {} header_paramters = {} header_paramters['Accept'] = 'application/json' if self._user_mgr.is_msa_account(): header_paramters['X-VSS-ForceMsaPassThrough'] = 'true' request = self._create_project_client.get(url, params=query_paramters) response = self._create_project_client.send(request, headers=header_paramters) # Handle Response deserialized = None if response.status_code // 100 != 2: logging.error("GET %s", request.url) logging.error("response: %s", response.status_code) logging.error(response.text) raise HttpOperationError(self._deserialize, response) else: deserialized = self._deserialize('ProjectPoll', response) return deserialized azure-functions-devops-build-0.0.22/azure_functions_devops_build/release/000077500000000000000000000000001346640171600267275ustar00rootroot00000000000000azure-functions-devops-build-0.0.22/azure_functions_devops_build/release/__init__.py000066400000000000000000000005311346640171600310370ustar00rootroot00000000000000# -------------------------------------------------------------------------------------------- # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- azure-functions-devops-build-0.0.22/azure_functions_devops_build/release/release_manager.py000066400000000000000000000376271346640171600324320ustar00rootroot00000000000000# -------------------------------------------------------------------------------------------- # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- import logging import vsts.release.v4_1.models as models from vsts.exceptions import VstsServiceError from ..base.base_manager import BaseManager from ..pool.pool_manager import PoolManager from ..constants import (LINUX_CONSUMPTION, LINUX_DEDICATED, WINDOWS) from ..exceptions import ReleaseErrorException class ReleaseManager(BaseManager): """ Manage DevOps releases Attributes: See BaseManager """ def __init__(self, organization_name="", project_name="", creds=None): super(ReleaseManager, self).__init__(creds, organization_name=organization_name, project_name=project_name) def create_release_definition(self, build_name, artifact_name, pool_name, service_endpoint_name, release_definition_name, app_type, functionapp_name, storage_name, resource_name, settings=None): pool = self._get_pool_by_name(pool_name) project = self._get_project_by_name(self._project_name) build = self._get_build_by_name(project, build_name) retention_policy_environment = self._get_retention_policy() artifact = self._get_artifact(build, project, artifact_name) pre_release_approvals, post_release_approvals = self._get_pre_post_approvals() service_endpoint = self._get_service_endpoint_by_name(project, service_endpoint_name) release_deploy_step = models.release_definition_deploy_step.ReleaseDefinitionDeployStep(id=2) triggers = self._get_triggers(artifact_name) deployment_input = self._get_deployment_input(pool.id) phase_inputs = self._get_phase_inputs(artifact_name) workflowtasks = [] if app_type == LINUX_CONSUMPTION: workflowtasks.append(self._blob_task(service_endpoint.id, storage_name)) workflowtasks.append(self._sas_token_task(service_endpoint.id, storage_name)) workflowtasks.append(self._app_settings_task(service_endpoint.id, functionapp_name, resource_name)) elif app_type == LINUX_DEDICATED: workflowtasks.append(self._app_service_deploy_task_linux(service_endpoint.id, functionapp_name)) elif app_type == WINDOWS: workflowtasks.append(self._app_service_deploy_task_windows(service_endpoint.id, functionapp_name)) else: logging.error("Invalid app type provided. Correct types are: " "Linux Consumption: %s, Linux Dedicated: %s, Windows: %s", LINUX_CONSUMPTION, LINUX_DEDICATED, WINDOWS) if settings is not None: settings_str = "" for setting in settings: settings_str += (setting[0] + "='" + setting[1] + "'") # Check that settings were actually set otherwise we don't want to use the task if settings_str != "": workflowtasks.append(self._app_settings_task_customized( service_endpoint.id, functionapp_name, resource_name, settings_str )) deploy_phases = self._get_deploy_phases(deployment_input, workflowtasks, phase_inputs) condition = models.condition.Condition(condition_type=1, name="ReleaseStarted", value="") release_definition_environment = models.release_definition_environment.ReleaseDefinitionEnvironment( name="deploy build", rank=1, retention_policy=retention_policy_environment, pre_deploy_approvals=pre_release_approvals, post_deploy_approvals=post_release_approvals, deploy_phases=deploy_phases, deploy_step=release_deploy_step, conditions=[condition] ) release_definition = models.release_definition.ReleaseDefinition( name=release_definition_name, environments=[release_definition_environment], artifacts=[artifact], triggers=triggers ) return self._release_client.create_release_definition(release_definition, project.id) def list_release_definitions(self): project = self.get_project_by_name(self._project_name) return self._release_client.get_release_definitions(project.id) def create_release(self, release_definition_name): project = self.get_project_by_name(self._project_name) release_definition = self.get_release_definition_by_name(project, release_definition_name) release_start_metadata = models.release_start_metadata.ReleaseStartMetadata( definition_id=release_definition.id, is_draft=False, properties={"ReleaseCreationSource": "ReleaseHub"} ) try: new_release = self._release_client.create_release(release_start_metadata, project.id) except VstsServiceError as vse: raise ReleaseErrorException(vse.message) return new_release def list_releases(self): project = self.get_project_by_name(self._project_name) return self._release_client.get_releases(project.id) def get_latest_release(self, release_definition_name): project = self.get_project_by_name(self._project_name) build_definition = self.get_release_definition_by_name(project, release_definition_name) try: releases = self._release_client.get_releases(self._project_name, definition_id=build_definition.id) except VstsServiceError: return None releases.sort(key=lambda r: r.id, reverse=True) if releases: return releases[0] return None def _get_service_endpoint_by_name(self, project, service_endpoint_name): service_endpoints = self._service_endpoint_client.get_service_endpoints(project.id) return next((service_endpoint for service_endpoint in service_endpoints if service_endpoint.name == service_endpoint_name), None) def _get_pool_by_name(self, pool_name): """Helper function to get the pool object from its name""" pool_manager = PoolManager(organization_name=self._organization_name, project_name=self._project_name, creds=self._creds) pools = pool_manager.list_pools() return next((pool for pool in pools.value if pool.name == pool_name), None) def get_project_by_name(self, name): for p in self._core_client.get_projects(): if p.name == name: return p return None def get_release_definition_by_name(self, project, name): for p in self._release_client.get_release_definitions(project.id): if p.name == name: return p return None def _get_triggers(self, artifact_name): trigger = {} trigger["triggerType"] = "artifactSource" trigger["triggerConditions"] = [] trigger["artifactAlias"] = artifact_name triggers = [trigger] return triggers def _get_deployment_input(self, pool_id): deployment_input = {} deployment_input["parallelExecution"] = {"parallelExecutionType": 0} deployment_input["queueId"] = pool_id return deployment_input def _get_phase_inputs(self, artifact_name): phase_inputs = {} download_input = {} download_input["artifactItems"] = [] download_input["alias"] = artifact_name download_input["artifactType"] = "Build" download_input["artifactDownloadMode"] = "All" artifacts_download_input = {} artifacts_download_input["downloadInputs"] = [download_input] phase_input_artifact_download_input = {} phase_input_artifact_download_input["skipArtifactsDownload"] = False phase_input_artifact_download_input["artifactsDownloadInput"] = artifacts_download_input phase_inputs["phaseinput_artifactdownloadinput"] = phase_input_artifact_download_input return phase_inputs def _get_deploy_phases(self, deployment_input, workflowtasks, phase_inputs): deploy_phase = {} deploy_phase["deploymentInput"] = deployment_input deploy_phase["rank"] = 1 deploy_phase["phaseType"] = 1 deploy_phase["name"] = "Agent Job" deploy_phase["workflowTasks"] = workflowtasks deploy_phase["phaseInputs"] = phase_inputs deploy_phases = [deploy_phase] return deploy_phases def _get_retention_policy(self): return models.environment_retention_policy.EnvironmentRetentionPolicy( days_to_keep=300, releases_to_keep=3, retain_build=True ) def _get_artifact(self, build, project, artifact_name): artifacts = self._build_client.get_artifacts(build.id, project.id) artifact = None for a in artifacts: if a.name == artifact_name: artifact = a definition_reference = {} definition_reference["project"] = {"id": project.id, "name": project.name} definition_reference["definition"] = {"id": build.definition.id, "name": build.definition.name} definition_reference["defaultVersionType"] = {"id": "latestType", "name": "Latest"} return models.artifact.Artifact( source_id=artifact.id, alias=artifact.name, type="Build", definition_reference=definition_reference) def _get_pre_post_approvals(self): pre_approval = models.release_definition_approval_step.ReleaseDefinitionApprovalStep( id=0, rank=1, is_automated=True, is_notification_on=False ) post_approval = models.release_definition_approval_step.ReleaseDefinitionApprovalStep( id=0, rank=1, is_automated=True, is_notification_on=False ) pre_release_approvals = models.release_definition_approvals.ReleaseDefinitionApprovals( approvals=[pre_approval] ) post_release_approvals = models.release_definition_approvals.ReleaseDefinitionApprovals( approvals=[post_approval] ) return pre_release_approvals, post_release_approvals def _blob_task(self, connectedServiceNameARM, storage_name): blobtask = {} blobtask["name"] = "AzureBlob File Copy" blobtask["enabled"] = True blobtask_inputs = {} blobtask_inputs["SourcePath"] = "$(System.DefaultWorkingDirectory)/drop/drop/build$(Build.BuildId).zip" blobtask_inputs["ConnectedServiceNameSelector"] = 'ConnectedServiceNameARM' blobtask_inputs["ConnectedServiceNameARM"] = connectedServiceNameARM blobtask_inputs["Destination"] = "AzureBlob" blobtask_inputs["StorageAccountRM"] = storage_name blobtask_inputs["ContainerName"] = 'azure-build' blobtask_inputs["outputStorageUri"] = "outputstorageuri" blobtask_inputs["outputStorageContainerSasToken"] = "sastoken" blobtask["inputs"] = blobtask_inputs blobtask["version"] = "2.*" blobtask["definitionType"] = "task" blobtask["taskId"] = "eb72cb01-a7e5-427b-a8a1-1b31ccac8a43" return blobtask def _sas_token_task(self, connectedServiceNameARM, storage_name): sastokentask = {} sastokentask["name"] = "Create SAS Token for Storage Account " + storage_name sastokentask["enabled"] = True sastokentask["taskId"] = "9e0b2bda-6a8d-4215-8e8c-3d47614db813" sastokentask["version"] = "1.*" sastokentask["definitionType"] = "task" sastokentask_inputs = {} sastokentask_inputs["ConnectedServiceName"] = connectedServiceNameARM sastokentask_inputs["StorageAccountRM"] = storage_name sastokentask_inputs["SasTokenTimeOutInHours"] = 10000 sastokentask_inputs["Permission"] = "r" sastokentask_inputs["StorageContainerName"] = "azure-build" sastokentask_inputs["outputStorageUri"] = "storageUri" sastokentask_inputs["outputStorageContainerSasToken"] = "storageToken" sastokentask["inputs"] = sastokentask_inputs return sastokentask def _app_settings_task(self, connectedServiceNameARM, functionapp_name, resource_name): appsetttingstask = {} appsetttingstask["name"] = "Set App Settings: " appsetttingstask["enabled"] = True appsetttingstask["taskId"] = "9d2e4cf0-f3bb-11e6-978b-770d284f4f2d" appsetttingstask["version"] = "2.*" appsetttingstask["definitionType"] = "task" appsetttingstask_inputs = {} appsetttingstask_inputs["ConnectedServiceName"] = connectedServiceNameARM appsetttingstask_inputs["WebAppName"] = functionapp_name appsetttingstask_inputs["ResourceGroupName"] = resource_name appsetttingstask_inputs["AppSettings"] = ( "WEBSITE_RUN_FROM_PACKAGE='$(storageUri)/build$(Build.BuildId).zip$(storageToken)'" ) appsetttingstask["inputs"] = appsetttingstask_inputs return appsetttingstask def _app_settings_task_customized(self, connectedServiceNameARM, functionapp_name, resource_name, settings): appsetttingstask = {} appsetttingstask["name"] = "Set App Settings: " appsetttingstask["enabled"] = True appsetttingstask["taskId"] = "9d2e4cf0-f3bb-11e6-978b-770d284f4f2d" appsetttingstask["version"] = "2.*" appsetttingstask["definitionType"] = "task" appsetttingstask_inputs = {} appsetttingstask_inputs["ConnectedServiceName"] = connectedServiceNameARM appsetttingstask_inputs["WebAppName"] = functionapp_name appsetttingstask_inputs["ResourceGroupName"] = resource_name appsetttingstask_inputs["AppSettings"] = settings appsetttingstask["inputs"] = appsetttingstask_inputs return appsetttingstask def _app_service_deploy_task_linux(self, connectedServiceNameARM, functionapp_name): appservicetask = {} appservicetask["name"] = "Azure App Service Deploy: " + functionapp_name appservicetask["enabled"] = True appservicetask["taskId"] = "497d490f-eea7-4f2b-ab94-48d9c1acdcb1" appservicetask["version"] = "4.*" appservicetask["definitionType"] = "task" appservicetask_inputs = {} appservicetask_inputs["ConnectionType"] = "AzureRM" appservicetask_inputs["ConnectedServiceName"] = connectedServiceNameARM appservicetask_inputs["PublishProfilePath"] = "$(System.DefaultWorkingDirectory)/**/*.pubxml" appservicetask_inputs["WebAppKind"] = "functionAppLinux" appservicetask_inputs["WebAppName"] = functionapp_name appservicetask_inputs["SlotName"] = "production" appservicetask_inputs["Package"] = "$(System.DefaultWorkingDirectory)/**/*.zip" appservicetask["inputs"] = appservicetask_inputs return appservicetask def _app_service_deploy_task_windows(self, connectedServiceNameARM, functionapp_name): appservicetask = {} appservicetask["name"] = "Azure App Service Deploy: " + functionapp_name appservicetask["enabled"] = True appservicetask["taskId"] = "497d490f-eea7-4f2b-ab94-48d9c1acdcb1" appservicetask["version"] = "4.*" appservicetask["definitionType"] = "task" appservicetask_inputs = {} appservicetask_inputs["ConnectionType"] = "AzureRM" appservicetask_inputs["ConnectedServiceName"] = connectedServiceNameARM appservicetask_inputs["PublishProfilePath"] = "$(System.DefaultWorkingDirectory)/**/*.pubxml" appservicetask_inputs["WebAppKind"] = "functionAppWindows" appservicetask_inputs["WebAppName"] = functionapp_name appservicetask_inputs["SlotName"] = "production" appservicetask_inputs["Package"] = "$(System.DefaultWorkingDirectory)/**/*.zip" appservicetask["inputs"] = appservicetask_inputs return appservicetask azure-functions-devops-build-0.0.22/azure_functions_devops_build/repository/000077500000000000000000000000001346640171600275265ustar00rootroot00000000000000azure-functions-devops-build-0.0.22/azure_functions_devops_build/repository/__init__.py000066400000000000000000000005311346640171600316360ustar00rootroot00000000000000# -------------------------------------------------------------------------------------------- # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- github_repository_manager.py000066400000000000000000000100241346640171600352710ustar00rootroot00000000000000azure-functions-devops-build-0.0.22/azure_functions_devops_build/repository# -------------------------------------------------------------------------------------------- # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- import base64 from ..exceptions import ( GithubContentNotFound, GithubIntegrationRequestError, GithubUnauthorizedError, ) from ..base.base_github_manager import BaseGithubManager class GithubRepositoryManager(BaseGithubManager): def check_github_repository(self, repository_fullname): header_parameters = self.construct_github_request_header() request = self._client.get('/repos/{repo}'.format(repo=repository_fullname)) response = self._client.send(request, header_parameters) if response.status_code // 100 == 2: return True return False def check_github_file(self, repository_fullname, file_path): header_parameters = self.construct_github_request_header() request = self._client.get('/repos/{repo}/contents/{path}'.format( repo=repository_fullname, path=file_path )) response = self._client.send(request, header_parameters) return response.status_code // 100 == 2 def get_content(self, repository_fullname, file_path, get_metadata=True): header_parameters = self.construct_github_request_header() if get_metadata: # Get files metadata header_parameters['Content-Type'] = 'application/json' else: # Get files content header_parameters['Accept'] = 'application/vnd.github.v3.raw' request = self._client.get('/repos/{repo}/contents/{path}'.format( repo=repository_fullname, path=file_path )) response = self._client.send(request, header_parameters) # The response is a Json content if response.status_code // 100 == 2: return response.json() if response.status_code == 401: raise GithubUnauthorizedError('Failed to read {repo}/{path}'.format( repo=repository_fullname, path=file_path )) if response.status_code == 404: raise GithubContentNotFound('Failed to find {repo}/{path}'.format( repo=repository_fullname, path=file_path )) raise GithubIntegrationRequestError(response.status_code) def put_content(self, repository_fullname, file_path, data): header_parameters = self.construct_github_request_header() header_parameters['Content-Type'] = 'Application/Json' request = self._client.put( url='/repos/{repo}/contents/{path}'.format(repo=repository_fullname, path=file_path), headers=header_parameters, content=data ) response = self._client.send(request) if response.status_code // 100 == 2: return response if response.status_code == 401: raise GithubUnauthorizedError('Failed to write {repo}/{path}'.format( repo=repository_fullname, path=file_path )) if response.status_code == 404: raise GithubContentNotFound('Failed to find {repo}/{path}'.format( repo=repository_fullname, path=file_path )) raise GithubIntegrationRequestError("{res.status_code} {res.url}".format(res=response)) def commit_file(self, repository_fullname, file_path, commit_message, file_data, sha=None, encode='utf-8'): data = { "branch": "master", "message": "{message}".format(message=commit_message), "content": base64.b64encode(bytes(file_data.encode(encode))).decode('ascii'), } if sha: data["sha"] = sha return self.put_content( repository_fullname=repository_fullname, file_path=file_path, data=data ) azure-functions-devops-build-0.0.22/azure_functions_devops_build/repository/local_git_utils.py000066400000000000000000000100301346640171600332470ustar00rootroot00000000000000# -------------------------------------------------------------------------------------------- # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- import os import re # Backward Compatible with Python 2.7 try: from subprocess import DEVNULL except ImportError: DEVNULL = open(os.devnull, 'w') from subprocess import STDOUT, check_call, check_output, CalledProcessError from ..exceptions import GitOperationException def does_git_exist(): try: check_call("git", stdout=DEVNULL, stderr=STDOUT) except CalledProcessError as cpe: # Git will return exit code 1 when it exist return cpe.returncode == 1 except FileNotFoundError: return False return True def does_local_git_repository_exist(): return os.path.exists(".git") def does_git_remote_exist(remote_name): command = ["git", "remote", "show"] try: output = check_output(command).decode("utf-8").split("\n") except CalledProcessError: raise GitOperationException(message=" ".join(command)) return remote_name in output def does_git_has_credential_manager(): command = ["git", "config", "--list"] try: output = check_output(command).decode("utf-8").split("\n") except CalledProcessError: raise GitOperationException(message=" ".join(command)) return "credential.helper=manager" in output def git_init(): command = ["git", "init"] try: check_call(command, stdout=DEVNULL, stderr=STDOUT) except CalledProcessError: raise GitOperationException(message=" ".join(command)) def git_add_remote(remote_name, remote_url): command = ["git", "remote", "add", remote_name, remote_url] try: check_call(command, stdout=DEVNULL, stderr=STDOUT) except CalledProcessError: raise GitOperationException(message=" ".join(command)) def git_remove_remote(remote_name): command = ["git", "remote", "remove", remote_name] try: check_call(command, stdout=DEVNULL, stderr=STDOUT) except CalledProcessError: raise GitOperationException(message=" ".join(command)) def git_stage_all(): command = ["git", "add", "--all"] try: check_call(command, stdout=DEVNULL, stderr=STDOUT) except CalledProcessError: raise GitOperationException(message=" ".join(command)) def git_commit(message): command = ["git", "commit", "--allow-empty", "--message", message] try: check_call(command, stdout=DEVNULL, stderr=STDOUT) except CalledProcessError: raise GitOperationException(message=" ".join(command)) def git_push(remote_name, force=False): command = ["git", "push", remote_name, "--all"] if force: command.append("--force") try: check_call(command, stdout=DEVNULL, stderr=STDOUT) except CalledProcessError: raise GitOperationException(message=" ".join(command)) def _sanitize_git_remote_name(organization_name, project_name, repository_name): concatenated_remote_name = "{organization_name}_{project_name}_{repository_name}".format( organization_name=organization_name, project_name=project_name, repository_name=repository_name ) sanitized_remote_name = re.sub(r"[^A-Za-z0-9_-]|\s", "-", concatenated_remote_name) return sanitized_remote_name def construct_git_remote_name(organization_name, project_name, repository_name, remote_prefix): remote_name = "_{prefix}_{name}".format( prefix=remote_prefix, name=_sanitize_git_remote_name(organization_name, project_name, repository_name) ) return remote_name def construct_git_remote_url(organization_name, project_name, repository_name, domain_name="dev.azure.com"): url = "https://{domain}/{org}/{proj}/_git/{repo}".format( domain=domain_name, org=organization_name, proj=project_name, repo=repository_name ) return url azure-functions-devops-build-0.0.22/azure_functions_devops_build/repository/models/000077500000000000000000000000001346640171600310115ustar00rootroot00000000000000azure-functions-devops-build-0.0.22/azure_functions_devops_build/repository/models/__init__.py000066400000000000000000000006571346640171600331320ustar00rootroot00000000000000# -------------------------------------------------------------------------------------------- # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- from .github_connection import GithubConnection __all__ = [ 'GithubConnection' ] github_connection.py000066400000000000000000000012421346640171600350040ustar00rootroot00000000000000azure-functions-devops-build-0.0.22/azure_functions_devops_build/repository/models# -------------------------------------------------------------------------------------------- # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- from msrest.serialization import Model class GithubConnection(Model): _attribute_map = { 'errorMessage': {'key': 'errorMessage', 'type': 'str'}, 'url': {'key': 'url', 'type': 'str'}, } def __init__(self, errorMessage=None, url=None): self.errorMessage = errorMessage self.url = url azure-functions-devops-build-0.0.22/azure_functions_devops_build/repository/repository_manager.py000066400000000000000000000124521346640171600340150ustar00rootroot00000000000000# -------------------------------------------------------------------------------------------- # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- from msrest.service_client import ServiceClient from msrest import Configuration, Deserializer import vsts.git.v4_1.models.git_repository_create_options as git_repository_create_options from vsts.exceptions import VstsServiceError from ..base.base_manager import BaseManager from . import models from .local_git_utils import ( git_init, git_add_remote, git_remove_remote, git_stage_all, git_commit, git_push, does_git_exist, does_local_git_repository_exist, does_git_has_credential_manager, does_git_remote_exist, construct_git_remote_name, construct_git_remote_url ) class RepositoryManager(BaseManager): """ Manage DevOps repositories Attributes: See BaseManager """ def __init__(self, organization_name="", project_name="", creds=None): base_url = 'https://dev.azure.com' self._config = Configuration(base_url=base_url) self._client = ServiceClient(creds, self._config) client_models = {k: v for k, v in models.__dict__.items() if isinstance(v, type)} self._deserialize = Deserializer(client_models) super(RepositoryManager, self).__init__(creds, organization_name=organization_name, project_name=project_name) @staticmethod def check_git(): return does_git_exist() @staticmethod def check_git_local_repository(): return does_local_git_repository_exist() @staticmethod def check_git_credential_manager(): return does_git_has_credential_manager() # Check if the git repository exists first. If it does, check if the git remote exists. def check_git_remote(self, repository_name, remote_prefix): if not does_local_git_repository_exist(): return False remote_name = construct_git_remote_name( self._organization_name, self._project_name, repository_name, remote_prefix ) return does_git_remote_exist(remote_name) def remove_git_remote(self, repository_name, remote_prefix): remote_name = construct_git_remote_name( self._organization_name, self._project_name, repository_name, remote_prefix ) git_remove_remote(remote_name) def get_azure_devops_repository_branches(self, repository_name): try: result = self._git_client.get_branches(repository_name, self._project_name) except VstsServiceError: return [] return result def get_azure_devops_repository(self, repository_name): try: result = self._git_client.get_repository(repository_name, self._project_name) except VstsServiceError: return None return result def create_repository(self, repository_name): project = self._get_project_by_name(self._project_name) git_repo_options = git_repository_create_options.GitRepositoryCreateOptions( name=repository_name, project=project ) return self._git_client.create_repository(git_repo_options) def list_repositories(self): return self._git_client.get_repositories(self._project_name) def list_commits(self, repository_name): project = self._get_project_by_name(self._project_name) repository = self._get_repository_by_name(project, repository_name) return self._git_client.get_commits(repository.id, None, project=project.id) def get_local_git_remote_name(self, repository_name, remote_prefix): return construct_git_remote_name(self._organization_name, self._project_name, repository_name, remote_prefix) # Since the portal url and remote url are same. We only need one function to handle portal access and git push def get_azure_devops_repo_url(self, repository_name): return construct_git_remote_url(self._organization_name, self._project_name, repository_name) # The function will initialize a git repo, create git remote, stage all changes and commit the code # Exceptions: GitOperationException def setup_local_git_repository(self, repository_name, remote_prefix): remote_name = construct_git_remote_name( self._organization_name, self._project_name, repository_name, remote_prefix ) remote_url = construct_git_remote_url(self._organization_name, self._project_name, repository_name) if not does_local_git_repository_exist(): git_init() git_add_remote(remote_name, remote_url) git_stage_all() git_commit("Create function app with azure devops build. Remote repository url: {url}".format(url=remote_url)) # The function will push the current context in local git repository to Azure Devops # Exceptions: GitOperationException def push_local_to_azure_devops_repository(self, repository_name, remote_prefix, force): remote_name = construct_git_remote_name( self._organization_name, self._project_name, repository_name, remote_prefix ) git_push(remote_name, force) azure-functions-devops-build-0.0.22/azure_functions_devops_build/service_endpoint/000077500000000000000000000000001346640171600306475ustar00rootroot00000000000000azure-functions-devops-build-0.0.22/azure_functions_devops_build/service_endpoint/__init__.py000066400000000000000000000005311346640171600327570ustar00rootroot00000000000000# -------------------------------------------------------------------------------------------- # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- github_service_endpoint_manager.py000066400000000000000000000034571346640171600375470ustar00rootroot00000000000000azure-functions-devops-build-0.0.22/azure_functions_devops_build/service_endpointimport vsts.service_endpoint.v4_1.models as models from vsts.exceptions import VstsServiceError from ..base.base_manager import BaseManager from .service_endpoint_utils import sanitize_github_repository_fullname class GithubServiceEndpointManager(BaseManager): def __init__(self, organization_name, project_name, creds): super(GithubServiceEndpointManager, self).__init__( creds, organization_name=organization_name, project_name=project_name ) def get_github_service_endpoints(self, repository_fullname): service_endpoint_name = self._get_service_github_endpoint_name(repository_fullname) try: result = self._service_endpoint_client.get_service_endpoints_by_names( self._project_name, [service_endpoint_name], type="github" ) except VstsServiceError: return [] return result def create_github_service_endpoint(self, repository_fullname, github_pat): data = {} auth = models.endpoint_authorization.EndpointAuthorization( parameters={ "accessToken": github_pat }, scheme="PersonalAccessToken" ) service_endpoint_name = self._get_service_github_endpoint_name(repository_fullname) service_endpoint = models.service_endpoint.ServiceEndpoint( administrators_group=None, authorization=auth, data=data, name=service_endpoint_name, type="github", url="http://github.com" ) return self._service_endpoint_client.create_service_endpoint(service_endpoint, self._project_name) def _get_service_github_endpoint_name(self, repository_name): return sanitize_github_repository_fullname(repository_name) service_endpoint_manager.py000066400000000000000000000104431346640171600361760ustar00rootroot00000000000000azure-functions-devops-build-0.0.22/azure_functions_devops_build/service_endpoint# -------------------------------------------------------------------------------------------- # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- import json import os try: from subprocess import DEVNULL except ImportError: DEVNULL = open(os.devnull, 'w') from subprocess import check_output, CalledProcessError import vsts.service_endpoint.v4_1.models as models from vsts.exceptions import VstsServiceError from ..base.base_manager import BaseManager from ..constants import SERVICE_ENDPOINT_DOMAIN from ..exceptions import RoleAssignmentException class ServiceEndpointManager(BaseManager): """ Manage DevOps service endpoints within projects Attributes: See BaseManager """ def __init__(self, organization_name="", project_name="", creds=None): """Inits ServiceEndpointManager as per BaseManager""" super(ServiceEndpointManager, self).__init__(creds, organization_name=organization_name, project_name=project_name) # Get the details of a service endpoint # If endpoint does not exist, return an empty list def get_service_endpoints(self, repository_name): service_endpoint_name = self._get_service_endpoint_name(repository_name, "pipeline") try: result = self._service_endpoint_client.get_service_endpoints_by_names( self._project_name, [service_endpoint_name] ) except VstsServiceError: return [] return result # This function requires user permission of Microsoft.Authorization/roleAssignments/write # i.e. only the owner of the subscription can use this function def create_service_endpoint(self, repository_name): """Create a new service endpoint within a project with an associated service principal""" project = self._get_project_by_name(self._project_name) command = "az account show --o json" token_resp = check_output(command, shell=True).decode() account = json.loads(token_resp) data = {} data["subscriptionId"] = account['id'] data["subscriptionName"] = account['name'] data["environment"] = "AzureCloud" data["scopeLevel"] = "Subscription" # The following command requires Microsoft.Authorization/roleAssignments/write permission service_principle_name = self._get_service_endpoint_name(repository_name, "pipeline") # A service principal name has to include the http/https to be valid command = "az ad sp create-for-rbac --o json --name http://" + service_principle_name try: token_resp = check_output(command, stderr=DEVNULL, shell=True).decode() except CalledProcessError: raise RoleAssignmentException(command) token_resp_dict = json.loads(token_resp) auth = models.endpoint_authorization.EndpointAuthorization( parameters={ "tenantid": token_resp_dict['tenant'], "serviceprincipalid": token_resp_dict['appId'], "authenticationType": "spnKey", "serviceprincipalkey": token_resp_dict['password'] }, scheme="ServicePrincipal" ) service_endpoint = models.service_endpoint.ServiceEndpoint( administrators_group=None, authorization=auth, data=data, name=token_resp_dict['displayName'], type="azurerm" ) return self._service_endpoint_client.create_service_endpoint(service_endpoint, project.id) def list_service_endpoints(self): """List exisiting service endpoints within a project""" project = self._get_project_by_name(self._project_name) return self._service_endpoint_client.get_service_endpoints(project.id) def _get_service_endpoint_name(self, repository_name, service_name): return "{domain}/{org}/{proj}/{repo}/{service}".format( domain=SERVICE_ENDPOINT_DOMAIN, org=self._organization_name, proj=self._project_name, repo=repository_name, service=service_name ) service_endpoint_utils.py000066400000000000000000000002751346640171600357260ustar00rootroot00000000000000azure-functions-devops-build-0.0.22/azure_functions_devops_build/service_endpointimport re def sanitize_github_repository_fullname(github_repository_fullname): sanitized_name = re.sub(r"[^A-Za-z0-9_-]|\s", "-", github_repository_fullname) return sanitized_name azure-functions-devops-build-0.0.22/azure_functions_devops_build/user/000077500000000000000000000000001346640171600262655ustar00rootroot00000000000000azure-functions-devops-build-0.0.22/azure_functions_devops_build/user/__init__.py000066400000000000000000000005311346640171600303750ustar00rootroot00000000000000# -------------------------------------------------------------------------------------------- # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- azure-functions-devops-build-0.0.22/azure_functions_devops_build/user/github_user_manager.py000066400000000000000000000013571346640171600326570ustar00rootroot00000000000000# -------------------------------------------------------------------------------------------- # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- from ..base.base_github_manager import BaseGithubManager class GithubUserManager(BaseGithubManager): def check_github_pat(self, pat): header_parameters = self.construct_github_request_header(pat) request = self._client.get('/') response = self._client.send(request, header_parameters) if response.status_code // 100 == 2: return True return False azure-functions-devops-build-0.0.22/azure_functions_devops_build/user/models/000077500000000000000000000000001346640171600275505ustar00rootroot00000000000000azure-functions-devops-build-0.0.22/azure_functions_devops_build/user/models/__init__.py000066400000000000000000000006121346640171600316600ustar00rootroot00000000000000# -------------------------------------------------------------------------------------------- # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- from .user import User __all__ = [ 'User' ]azure-functions-devops-build-0.0.22/azure_functions_devops_build/user/models/user.py000066400000000000000000000020121346640171600310730ustar00rootroot00000000000000# -------------------------------------------------------------------------------------------- # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- from msrest.serialization import Model class User(Model): _attribute_map = { 'id': {'key': 'id', 'type': 'str'}, 'displayName': {'key': 'displayName', 'type': 'str'}, 'uniqueName': {'key': 'uniqueName', 'type': 'str'}, 'email': {'key': 'email', 'type': 'str'}, 'preferredTimeZoneOffset': {'key': 'preferredTimeZoneOffset', 'type': 'str'}, } def __init__(self, id=None, displayName=None, uniqueName=None, email=None, preferredTimeZoneOffset=None): self.id = id self.displayName = displayName self.uniqueName = uniqueName self.email = email self.preferredTimeZoneOffset = preferredTimeZoneOffset azure-functions-devops-build-0.0.22/azure_functions_devops_build/user/user_manager.py000066400000000000000000000054541346640171600313170ustar00rootroot00000000000000# -------------------------------------------------------------------------------------------- # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- import logging from msrest.service_client import ServiceClient from msrest import Configuration, Deserializer from msrest.exceptions import HttpOperationError from . import models class UserManager(object): """ Get details about a user Attributes: See BaseManager """ def __init__(self, base_url='https://peprodscussu2.portalext.visualstudio.com', creds=None): """Inits UserManager as to be able to send the right requests""" self._config = Configuration(base_url=base_url) self._client = ServiceClient(creds, self._config) #create the deserializer for the models client_models = {k: v for k, v in models.__dict__.items() if isinstance(v, type)} self._deserialize = Deserializer(client_models) # create cache for two user type self._cache_aad_user = None self._cache_msa_user = None def is_msa_account(self): user_id_aad = self.get_user(msa=False).id user_id_msa = self.get_user(msa=True).id return user_id_aad != user_id_msa def get_user(self, msa=False): # Try to get from cache if msa is True and self._cache_msa_user is not None: return self._cache_msa_user if msa is False and self._cache_aad_user is not None: return self._cache_aad_user header_parameters = {} header_parameters['X-VSS-ForceMsaPassThrough'] = 'true' if msa else 'false' header_parameters['Accept'] = 'application/json' request = self._client.get('/_apis/AzureTfs/UserContext') response = self._client.send(request, header_parameters) # Handle Response deserialized = None if response.status_code // 100 != 2: logging.error("GET %s", request.url) logging.error("response: %s", response.status_code) logging.error(response.text) raise HttpOperationError(self._deserialize, response) else: deserialized = self._deserialize('User', response) # Write to cache if msa is True and self._cache_msa_user is None: self._cache_msa_user = deserialized if msa is False and self._cache_aad_user is None: self._cache_aad_user = deserialized return deserialized @property def aad_id(self): return self.get_user(msa=False).id @property def msa_id(self): return self.get_user(msa=True).id def close_connection(self): self._client.close() azure-functions-devops-build-0.0.22/azure_functions_devops_build/yaml/000077500000000000000000000000001346640171600262515ustar00rootroot00000000000000azure-functions-devops-build-0.0.22/azure_functions_devops_build/yaml/__init__.py000066400000000000000000000005311346640171600303610ustar00rootroot00000000000000# -------------------------------------------------------------------------------------------- # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- azure-functions-devops-build-0.0.22/azure_functions_devops_build/yaml/github_yaml_manager.py000066400000000000000000000164111346640171600326240ustar00rootroot00000000000000# -------------------------------------------------------------------------------------------- # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- from os import path from datetime import datetime from jinja2 import Environment, FileSystemLoader, select_autoescape from ..repository.github_repository_manager import GithubRepositoryManager from ..base.base_github_manager import BaseGithubManager from ..constants import (WINDOWS, PYTHON, NODE, DOTNET, POWERSHELL) from ..exceptions import LanguageNotSupportException class GithubYamlManager(BaseGithubManager): def __init__(self, language, app_type, github_pat, github_repository): super(GithubYamlManager, self).__init__(pat=github_pat) self._github_repo_mgr = GithubRepositoryManager(pat=github_pat) self._github_repository = github_repository self._language = language self._app_type = app_type self.jinja_env = Environment( loader=FileSystemLoader(path.join(path.abspath(path.dirname(__file__)), 'templates')), autoescape=select_autoescape(['jinja']) ) def create_yaml(self, overwrite=False): if self._language == PYTHON: language_str = 'python' package_route = '$(System.DefaultWorkingDirectory)' dependencies = self._python_dependencies() elif self._language == NODE: language_str = 'node' package_route = '$(System.DefaultWorkingDirectory)' dependencies = self._node_dependencies() elif self._language == DOTNET: language_str = 'dotnet' package_route = '$(System.DefaultWorkingDirectory)/publish_output/s' dependencies = self._dotnet_dependencies() elif self._language == POWERSHELL: language_str = 'powershell' package_route = '$(System.DefaultWorkingDirectory)' dependencies = self._powershell_dependencies() else: raise LanguageNotSupportException(self._language) if self._app_type == WINDOWS: platform_str = 'windows' yaml = self._generate_yaml(dependencies, 'VS2017-Win2016', language_str, platform_str, package_route) else: platform_str = 'linux' yaml = self._generate_yaml(dependencies, 'ubuntu-16.04', language_str, platform_str, package_route) if overwrite: return self._overwrite_yaml_file(yaml) else: return self._commit_yaml_file(yaml) def _commit_yaml_file(self, data): return self._github_repo_mgr.commit_file( repository_fullname=self._github_repository, file_path="azure-pipelines.yml", file_data=data, commit_message="Created azure-pipelines.yml by Azure CLI ({time})".format( time=datetime.utcnow().strftime("%Y-%m-%d %X UTC") ), ) def _overwrite_yaml_file(self, data): sha = self._github_repo_mgr.get_content( self._github_repository, 'azure-pipelines.yml', get_metadata=True ).get("sha") return self._github_repo_mgr.commit_file( repository_fullname=self._github_repository, file_path="azure-pipelines.yml", file_data=data, commit_message="Overwritten azure-pipelines.yml by Azure CLI ({time})".format( time=datetime.utcnow().strftime("%Y-%m-%d %X UTC") ), sha=sha ) def _generate_yaml(self, dependencies, vmImage, language_str, platform_str, package_route): template = self.jinja_env.get_template('build.jinja') outputText = template.render(dependencies=dependencies, vmImage=vmImage, language=language_str, platform=platform_str, package_route=package_route) return outputText def _requires_extensions(self): return self._github_repo_mgr.check_github_file(self._github_repository, 'extensions.csproj') def _requires_pip(self): return self._github_repo_mgr.check_github_file(self._github_repository, 'requirements.txt') def _requires_npm(self): return self._github_repo_mgr.check_github_file(self._github_repository, 'package.json') def _inject_extensions_build(self, dependencies): if self._app_type == WINDOWS: runtime = 'win10-x64' else: runtime = 'ubuntu.16.04-x64' dependencies.append("- task: DotNetCoreCLI@2") dependencies.append(" displayName: '.NET Core build extensions'") dependencies.append(" inputs:") dependencies.append(" projects: extensions.csproj") dependencies.append(" arguments: '--runtime {runtime} --output bin/'".format(runtime=runtime)) def _python_dependencies(self): """Helper to create the standard python dependencies""" dependencies = [] if self._requires_extensions(): self._inject_extensions_build(dependencies) dependencies.append('- task: UsePythonVersion@0') dependencies.append(' displayName: "Setting python version to 3.6 as required by functions"') dependencies.append(' inputs:') dependencies.append(' versionSpec: \'3.6\'') dependencies.append(' architecture: \'x64\'') dependencies.append('- script: |') dependencies.append(' python3.6 -m venv worker_venv') dependencies.append(' source worker_venv/bin/activate') dependencies.append(' pip3.6 install setuptools') if self._requires_pip(): dependencies.append(' pip3.6 install -r requirements.txt') return dependencies def _node_dependencies(self): """Helper to create the standard node dependencies""" dependencies = [] if self._requires_extensions(): self._inject_extensions_build(dependencies) if self._requires_npm(): dependencies.append('- script: |') dependencies.append(' npm install') dependencies.append(' npm run build --if-present') dependencies.append(' npm prune --production') return dependencies def _dotnet_dependencies(self): """Helper to create the standard dotnet dependencies""" dependencies = [] dependencies.append('- script: |') dependencies.append(' dotnet restore') dependencies.append(' dotnet build --configuration Release') dependencies.append("- task: DotNetCoreCLI@2") dependencies.append(" inputs:") dependencies.append(" command: publish") dependencies.append(" arguments: '--configuration Release --output publish_output'") dependencies.append(" projects: '*.csproj'") dependencies.append(" publishWebProjects: false") dependencies.append(" modifyOutputPath: true") dependencies.append(" zipAfterPublish: false") return dependencies def _powershell_dependencies(self): dependencies = [] if self._requires_extensions(): self._inject_extensions_build(dependencies) return dependencies azure-functions-devops-build-0.0.22/azure_functions_devops_build/yaml/templates/000077500000000000000000000000001346640171600302475ustar00rootroot00000000000000azure-functions-devops-build-0.0.22/azure_functions_devops_build/yaml/templates/build.jinja000066400000000000000000000016151346640171600323660ustar00rootroot00000000000000# -------------------------------------------------------------------------------------------- # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- # Starter template for Azure functions build -- {{language}} on {{platform}} trigger: - master pool: vmImage: '{{ vmImage }}' steps: {%- for dependency in dependencies %} {{ dependency|safe }} {%- endfor %} - task: ArchiveFiles@2 displayName: 'Archive files' inputs: rootFolderOrFile: '{{package_route}}' includeRootFolder: false archiveFile: '$(System.DefaultWorkingDirectory)/build$(Build.BuildId).zip' - task: PublishBuildArtifacts@1 inputs: PathtoPublish: '$(System.DefaultWorkingDirectory)/build$(Build.BuildId).zip' name: 'drop' azure-functions-devops-build-0.0.22/azure_functions_devops_build/yaml/yaml_manager.py000066400000000000000000000140311346640171600312560ustar00rootroot00000000000000# -------------------------------------------------------------------------------------------- # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- import os.path as path from jinja2 import Environment, FileSystemLoader, select_autoescape from ..constants import (WINDOWS, PYTHON, NODE, DOTNET, POWERSHELL) from ..exceptions import LanguageNotSupportException class YamlManager(object): """ Generate yaml files for devops Attributes: language: the language of the functionapp you are creating app_type: the type of functionapp that you are creating """ def __init__(self, language, app_type): """Inits YamlManager as to be able generate the yaml files easily""" self._language = language self._app_type = app_type self.jinja_env = Environment( loader=FileSystemLoader(path.join(path.abspath(path.dirname(__file__)), 'templates')), autoescape=select_autoescape(['jinja']) ) def create_yaml(self): """Create the yaml to be able to create build in the azure-pipelines.yml file""" if self._language == PYTHON: language_str = 'python' package_route = '$(System.DefaultWorkingDirectory)' dependencies = self._python_dependencies() elif self._language == NODE: language_str = 'node' package_route = '$(System.DefaultWorkingDirectory)' dependencies = self._node_dependencies() elif self._language == DOTNET: language_str = 'dotnet' package_route = '$(System.DefaultWorkingDirectory)/publish_output/s' dependencies = self._dotnet_dependencies() elif self._language == POWERSHELL: language_str = 'powershell' package_route = '$(System.DefaultWorkingDirectory)' dependencies = self._powershell_dependencies() else: raise LanguageNotSupportException(self._language) if self._app_type == WINDOWS: platform_str = 'windows' yaml = self._generate_yaml(dependencies, 'VS2017-Win2016', language_str, platform_str, package_route) else: platform_str = 'linux' yaml = self._generate_yaml(dependencies, 'ubuntu-16.04', language_str, platform_str, package_route) with open('azure-pipelines.yml', 'w') as f: f.write(yaml) def _generate_yaml(self, dependencies, vmImage, language_str, platform_str, package_route): template = self.jinja_env.get_template('build.jinja') outputText = template.render(dependencies=dependencies, vmImage=vmImage, language=language_str, platform=platform_str, package_route=package_route) return outputText def _requires_extensions(self): return path.exists('extensions.csproj') def _requires_pip(self): return path.exists('requirements.txt') def _requires_npm(self): return path.exists('package.json') def _inject_extensions_build(self, dependencies): if self._app_type == WINDOWS: runtime = 'win10-x64' else: runtime = 'ubuntu.16.04-x64' dependencies.append("- task: DotNetCoreCLI@2") dependencies.append(" displayName: '.NET Core build extensions'") dependencies.append(" inputs:") dependencies.append(" projects: extensions.csproj") dependencies.append(" arguments: '--runtime {runtime} --output bin/'".format(runtime=runtime)) def _python_dependencies(self): """Helper to create the standard python dependencies""" dependencies = [] if self._requires_extensions(): self._inject_extensions_build(dependencies) dependencies.append('- task: UsePythonVersion@0') dependencies.append(' displayName: "Setting python version to 3.6 as required by functions"') dependencies.append(' inputs:') dependencies.append(' versionSpec: \'3.6\'') dependencies.append(' architecture: \'x64\'') dependencies.append('- script: |') dependencies.append(' python3.6 -m venv worker_venv') dependencies.append(' source worker_venv/bin/activate') dependencies.append(' pip3.6 install setuptools') if self._requires_pip(): dependencies.append(' pip3.6 install -r requirements.txt') return dependencies def _node_dependencies(self): """Helper to create the standard node dependencies""" dependencies = [] if self._requires_extensions(): self._inject_extensions_build(dependencies) if self._requires_npm(): dependencies.append('- script: |') dependencies.append(' npm install') dependencies.append(' npm run build --if-present') dependencies.append(' npm prune --production') return dependencies def _dotnet_dependencies(self): """Helper to create the standard dotnet dependencies""" dependencies = [] dependencies.append('- script: |') dependencies.append(' dotnet restore') dependencies.append(' dotnet build --configuration Release') dependencies.append("- task: DotNetCoreCLI@2") dependencies.append(" inputs:") dependencies.append(" command: publish") dependencies.append(" arguments: '--configuration Release --output publish_output'") dependencies.append(" projects: '*.csproj'") dependencies.append(" publishWebProjects: false") dependencies.append(" modifyOutputPath: true") dependencies.append(" zipAfterPublish: false") return dependencies def _powershell_dependencies(self): dependencies = [] if self._requires_extensions(): self._inject_extensions_build(dependencies) return dependencies azure-functions-devops-build-0.0.22/requirements.txt000066400000000000000000000015151346640171600226200ustar00rootroot00000000000000adal==1.2.0 antlr4-python3-runtime==4.7.2 applicationinsights==0.11.7 argcomplete==1.9.4 asn1crypto==0.24.0 azure-cli-core==2.0.55 azure-cli-nspkg==3.0.3 azure-cli-telemetry==1.0.0 azure-common==1.1.17 azure-mgmt-nspkg==3.0.2 azure-mgmt-resource==2.0.0 azure-nspkg==3.0.2 bcrypt==3.1.6 certifi==2018.11.29 cffi==1.11.5 chardet==3.0.4 colorama==0.4.1 cryptography==2.4.2 futures==3.1.1 humanfriendly==4.17 idna==2.8 isodate==0.6.0 Jinja2==2.10 jmespath==0.9.3 knack==0.5.1 MarkupSafe==1.1.0 msrest==0.6.4 msrestazure==0.6.0 oauthlib==3.0.0 paramiko==2.4.2 portalocker==1.2.1 pyasn1==0.4.5 pycparser==2.19 Pygments==2.3.1 PyJWT==1.7.1 PyNaCl==1.3.0 pyOpenSSL==18.0.0 pypiwin32==223 pyreadline==2.1 python-dateutil==2.7.5 pywin32==224 PyYAML==4.2b4 requests==2.21.0 requests-oauthlib==1.2.0 six==1.12.0 tabulate==0.8.2 urllib3==1.24.1 vsts==0.1.25 azure-functions-devops-build-0.0.22/sample_yaml_builds/000077500000000000000000000000001346640171600231775ustar00rootroot00000000000000azure-functions-devops-build-0.0.22/sample_yaml_builds/linux/000077500000000000000000000000001346640171600243365ustar00rootroot00000000000000azure-functions-devops-build-0.0.22/sample_yaml_builds/linux/consumption/000077500000000000000000000000001346640171600267145ustar00rootroot00000000000000azure-functions-devops-build-0.0.22/sample_yaml_builds/linux/consumption/dotnet-consumption.yml000066400000000000000000000044561346640171600333210ustar00rootroot00000000000000# Configure the details of the functionapp variables: azureSubscription: '{ARM ACCESS TOKEN NAME}' storageName: '{STORAGE NAME OF FUNCTIONAPP}' functionappName: '{NAME OF FUNCTIONAPP}' resourceGroupName: '{RESOURCE GROUP NAME OF FUNCTIONAPP}' jobs: # Build the python packages in a virtual env - job: Build pool: vmImage: ubuntu-16.04 steps: - script: | dotnet restore dotnet build --configuration Release - task: DotNetCoreCLI@2 inputs: command: publish arguments: '--configuration Release --output publish_output' projects: '*.csproj' publishWebProjects: false modifyOutputPath: true zipAfterPublish: false - task: ArchiveFiles@2 displayName: "Archive files" inputs: rootFolderOrFile: "$(System.DefaultWorkingDirectory)/publish_output/s" includeRootFolder: false archiveFile: "$(System.DefaultWorkingDirectory)/build$(Build.BuildId).zip" - task: PublishBuildArtifacts@1 inputs: PathtoPublish: '$(System.DefaultWorkingDirectory)/build$(Build.BuildId).zip' name: 'drop' # Download the build artifacts from - job: Deploy pool: vmImage: 'VS2017-Win2016' steps: - task: DownloadBuildArtifacts@0 inputs: buildType: 'current' downloadType: 'single' artifactName: 'drop' downloadPath: '$(System.DefaultWorkingDirectory)' - task: AzureFileCopy@2 inputs: sourcePath: "drop/build$(Build.BuildId).zip" azureConnectionType: 'ConnectedServiceNameARM' azureSubscription: "$(azureSubscription)" destination: azureBlob storage: "$(storageName)" containerName: 'azure-build' - task: createsastoken@1 inputs: ConnectedServiceName: "$(azureSubscription)" StorageAccountRM: "$(storageName)" SasTokenTimeOutInHours: 10000 Permission: 'r' StorageContainerName: 'azure-build' - task: AzureAppServiceSetAppSettings@2 inputs: ConnectedServiceName: "$(azureSubscription)" WebAppName: "$(functionappName)" ResourceGroupName: "$(resourceGroupName)" AppSettings: "WEBSITE_RUN_FROM_PACKAGE='$(storageUri)/build$(Build.BuildId).zip$(storageToken)'" dependsOn: Build condition: succeeded() azure-functions-devops-build-0.0.22/sample_yaml_builds/linux/consumption/node-consumption.yml000066400000000000000000000043401346640171600327410ustar00rootroot00000000000000# Configure the details of the functionapp variables: azureSubscription: '{ARM ACCESS TOKEN NAME}' storageName: '{STORAGE NAME OF FUNCTIONAPP}' functionappName: '{NAME OF FUNCTIONAPP}' resourceGroupName: '{RESOURCE GROUP NAME OF FUNCTIONAPP}' jobs: # Build the python packages in a virtual env - job: Build pool: vmImage: ubuntu-16.04 steps: - script: | dotnet restore # COMMENT OUT IF NOT USING FUNCTION EXTENSIONS dotnet build --runtime ubuntu.16.04-x64 --output './bin/' # COMMENT OUT IF NOT USING FUNCTION EXTENSIONS npm install npm run build --if-present npm prune --production - task: ArchiveFiles@2 displayName: "Archive files" inputs: rootFolderOrFile: "$(System.DefaultWorkingDirectory)" includeRootFolder: false archiveFile: "$(System.DefaultWorkingDirectory)/build$(Build.BuildId).zip" - task: PublishBuildArtifacts@1 inputs: PathtoPublish: '$(System.DefaultWorkingDirectory)/build$(Build.BuildId).zip' name: 'drop' # Download the build artifacts from - job: Deploy pool: vmImage: 'VS2017-Win2016' steps: - task: DownloadBuildArtifacts@0 inputs: buildType: 'current' downloadType: 'single' artifactName: 'drop' downloadPath: '$(System.DefaultWorkingDirectory)' - task: AzureFileCopy@2 inputs: sourcePath: "drop/build$(Build.BuildId).zip" azureConnectionType: 'ConnectedServiceNameARM' azureSubscription: "$(azureSubscription)" destination: azureBlob storage: "$(storageName)" containerName: 'azure-build' - task: createsastoken@1 inputs: ConnectedServiceName: "$(azureSubscription)" StorageAccountRM: "$(storageName)" SasTokenTimeOutInHours: 10000 Permission: 'r' StorageContainerName: 'azure-build' - task: AzureAppServiceSetAppSettings@2 inputs: ConnectedServiceName: "$(azureSubscription)" WebAppName: "$(functionappName)" ResourceGroupName: "$(resourceGroupName)" AppSettings: "WEBSITE_RUN_FROM_PACKAGE='$(storageUri)/build$(Build.BuildId).zip$(storageToken)'" dependsOn: Build condition: succeeded() azure-functions-devops-build-0.0.22/sample_yaml_builds/linux/consumption/python-consumption.yml000066400000000000000000000047261346640171600333450ustar00rootroot00000000000000# Configure the details of the functionapp variables: azureSubscription: '{ARM ACCESS TOKEN NAME}' storageName: '{STORAGE NAME OF FUNCTIONAPP}' functionappName: '{NAME OF FUNCTIONAPP}' resourceGroupName: '{RESOURCE GROUP NAME OF FUNCTIONAPP}' jobs: # Build the python packages in a virtual env - job: Build pool: vmImage: ubuntu-16.04 steps: - task: UsePythonVersion@0 displayName: "Setting python version to 3.6 as required by functions" inputs: versionSpec: '3.6' architecture: 'x64' - script: | dotnet restore # COMMENT OUT IF NOT USING FUNCTION EXTENSIONS dotnet build --runtime ubuntu.16.04-x64 --output './bin/' # COMMENT OUT IF NOT USING FUNCTION EXTENSIONS python3.6 -m venv worker_venv source worker_venv/bin/activate pip3.6 install setuptools pip3.6 install -r requirements.txt - task: ArchiveFiles@2 displayName: "Archive files" inputs: rootFolderOrFile: "$(System.DefaultWorkingDirectory)" includeRootFolder: false archiveFile: "$(System.DefaultWorkingDirectory)/build$(Build.BuildId).zip" - task: PublishBuildArtifacts@1 inputs: PathtoPublish: '$(System.DefaultWorkingDirectory)/build$(Build.BuildId).zip' name: 'drop' # Download the build artifacts from - job: Deploy pool: vmImage: 'VS2017-Win2016' steps: - task: DownloadBuildArtifacts@0 inputs: buildType: 'current' downloadType: 'single' artifactName: 'drop' downloadPath: '$(System.DefaultWorkingDirectory)' - task: AzureFileCopy@2 inputs: sourcePath: "drop/build$(Build.BuildId).zip" azureConnectionType: 'ConnectedServiceNameARM' azureSubscription: "$(azureSubscription)" destination: azureBlob storage: "$(storageName)" containerName: 'azure-build' - task: createsastoken@1 inputs: ConnectedServiceName: "$(azureSubscription)" StorageAccountRM: "$(storageName)" SasTokenTimeOutInHours: 10000 Permission: 'r' StorageContainerName: 'azure-build' - task: AzureAppServiceSetAppSettings@2 inputs: ConnectedServiceName: "$(azureSubscription)" WebAppName: "$(functionappName)" ResourceGroupName: "$(resourceGroupName)" AppSettings: "WEBSITE_RUN_FROM_PACKAGE='$(storageUri)/build$(Build.BuildId).zip$(storageToken)'" dependsOn: Build condition: succeeded() azure-functions-devops-build-0.0.22/sample_yaml_builds/linux/dedicated/000077500000000000000000000000001346640171600262445ustar00rootroot00000000000000azure-functions-devops-build-0.0.22/sample_yaml_builds/linux/dedicated/dotnet-dedicated.yml000066400000000000000000000017641346640171600322000ustar00rootroot00000000000000variables: azureSubscription: '{ARM ACCESS TOKEN NAME}' functionappName: '{NAME OF FUNCTIONAPP}' pool: vmImage: ubuntu-16.04 steps: - script: | dotnet restore dotnet build --configuration Release - task: DotNetCoreCLI@2 inputs: command: publish arguments: '--configuration Release --output publish_output' projects: '*.csproj' publishWebProjects: false modifyOutputPath: true zipAfterPublish: false - task: ArchiveFiles@2 displayName: "Archive files" inputs: rootFolderOrFile: "$(System.DefaultWorkingDirectory)/publish_output/s" includeRootFolder: false archiveFile: "$(System.DefaultWorkingDirectory)/build$(Build.BuildId).zip" - task: PublishBuildArtifacts@1 inputs: PathtoPublish: '$(System.DefaultWorkingDirectory)/$(Build.BuildId).zip' name: 'drop' - task: AzureRmWebAppDeployment@4 inputs: connectionType: 'AzureRM' azureSubscription: "$(azureSubscription)" appType: 'functionAppLinux' webAppName: "$(functionappName)" azure-functions-devops-build-0.0.22/sample_yaml_builds/linux/dedicated/node-dedicated.yml000066400000000000000000000016131346640171600316210ustar00rootroot00000000000000variables: azureSubscription: '{ARM ACCESS TOKEN NAME}' functionappName: '{NAME OF FUNCTIONAPP}' pool: vmImage: ubuntu-16.04 steps: - script: | dotnet restore # comment out if not using extensions dotnet build --output \'./bin/\' # comment out if not using extensions npm install npm run build --if-present npm prune --production - task: ArchiveFiles@2 displayName: "Archive files" inputs: rootFolderOrFile: "$(System.DefaultWorkingDirectory)" includeRootFolder: false archiveFile: "$(System.DefaultWorkingDirectory)/$(Build.BuildId).zip" - task: PublishBuildArtifacts@1 inputs: PathtoPublish: '$(System.DefaultWorkingDirectory)/$(Build.BuildId).zip' name: 'drop' - task: AzureRmWebAppDeployment@4 inputs: connectionType: 'AzureRM' azureSubscription: "$(azureSubscription)" appType: 'functionAppLinux' webAppName: "$(functionappName)" azure-functions-devops-build-0.0.22/sample_yaml_builds/linux/dedicated/python-dedicated.yml000066400000000000000000000022341346640171600322150ustar00rootroot00000000000000variables: azureSubscription: '{ARM ACCESS TOKEN NAME}' functionappName: '{NAME OF FUNCTIONAPP}' pool: vmImage: ubuntu-16.04 steps: - task: UsePythonVersion@0 displayName: "Setting python version to 3.6 as required by functions" inputs: versionSpec: '3.6' architecture: 'x64' - script: | dotnet restore # COMMENT OUT IF NOT USING FUNCTION EXTENSIONS dotnet build --runtime ubuntu.16.04-x64 --output './bin/' # COMMENT OUT IF NOT USING FUNCTION EXTENSIONS python3.6 -m venv worker_venv source worker_venv/bin/activate pip3.6 install setuptools pip3.6 install -r requirements.txt - task: ArchiveFiles@2 displayName: "Archive files" inputs: rootFolderOrFile: "$(System.DefaultWorkingDirectory)" includeRootFolder: false archiveFile: "$(System.DefaultWorkingDirectory)/build$(Build.BuildId).zip" - task: PublishBuildArtifacts@1 inputs: PathtoPublish: '$(System.DefaultWorkingDirectory)/build$(Build.BuildId).zip' name: 'drop' - task: AzureRmWebAppDeployment@4 inputs: connectionType: 'AzureRM' azureSubscription: "$(azureSubscription)" appType: 'functionAppLinux' webAppName: "$(functionappName)" azure-functions-devops-build-0.0.22/sample_yaml_builds/windows/000077500000000000000000000000001346640171600246715ustar00rootroot00000000000000azure-functions-devops-build-0.0.22/sample_yaml_builds/windows/dotnet-windows.yml000066400000000000000000000020751346640171600304050ustar00rootroot00000000000000# Starter template for Azure functions build for {{language}} {{appType}} variables: azureSubscription: '{ARM ACCESS TOKEN NAME}' functionappName: '{NAME OF FUNCTIONAPP}' pool: vmImage: 'VS2017-Win2016' steps: - script: | dotnet restore dotnet build --configuration Release - task: DotNetCoreCLI@2 inputs: command: publish arguments: '--configuration Release --output publish_output' projects: '*.csproj' publishWebProjects: false modifyOutputPath: true zipAfterPublish: false - task: ArchiveFiles@2 displayName: "Archive files" inputs: rootFolderOrFile: "$(System.DefaultWorkingDirectory)/publish_output/s" includeRootFolder: false archiveFile: "$(System.DefaultWorkingDirectory)/build$(Build.BuildId).zip" - task: PublishBuildArtifacts@1 inputs: PathtoPublish: '$(System.DefaultWorkingDirectory)/$(Build.BuildId).zip' name: 'drop' - task: AzureRmWebAppDeployment@4 inputs: connectionType: 'AzureRM' azureSubscription: "$(azureSubscription)" appType: 'functionApp' webAppName: "$(functionappName)" azure-functions-devops-build-0.0.22/sample_yaml_builds/windows/java-windows.yml000066400000000000000000000000001346640171600300130ustar00rootroot00000000000000azure-functions-devops-build-0.0.22/sample_yaml_builds/windows/node-windows.yml000066400000000000000000000016121346640171600300310ustar00rootroot00000000000000variables: azureSubscription: '{ARM ACCESS TOKEN NAME}' functionappName: '{NAME OF FUNCTIONAPP}' pool: vmImage: 'VS2017-Win2016' steps: - script: | dotnet restore # comment out if not using extensions dotnet build --output \'./bin/\' # comment out if not using extensions npm install npm run build --if-present npm prune --production - task: ArchiveFiles@2 displayName: "Archive files" inputs: rootFolderOrFile: "$(System.DefaultWorkingDirectory)" includeRootFolder: false archiveFile: "$(System.DefaultWorkingDirectory)/$(Build.BuildId).zip" - task: PublishBuildArtifacts@1 inputs: PathtoPublish: '$(System.DefaultWorkingDirectory)/$(Build.BuildId).zip' name: 'drop' - task: AzureRmWebAppDeployment@4 inputs: connectionType: 'AzureRM' azureSubscription: "$(azureSubscription)" appType: 'functionApp' webAppName: "$(functionappName)" azure-functions-devops-build-0.0.22/setup.py000066400000000000000000000024231346640171600210450ustar00rootroot00000000000000#------------------------------------------------------------------------- # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. See License.txt in the project root for # license information. #-------------------------------------------------------------------------- from os import path from setuptools import setup, find_packages NAME = "azure-functions-devops-build" VERSION = "0.0.22" REQUIRES = ["msrest", "vsts", "jinja2"] file_directory = path.abspath(path.dirname(__file__)) with open(path.join(file_directory, 'README.md')) as f: long_description = f.read() setup( name=NAME, version=VERSION, license="MIT", description="Python package for integrating Azure Functions with Azure DevOps. Specifically made for the Azure CLI", author="Oliver Dolk, Hanzhang Zeng", author_email="t-oldolk@microsoft.com, hazeng@microsoft.com", url="https://github.com/Azure/azure-functions-devops-build", keywords=["Microsoft", "Azure DevOps", "Azure Functions", "Azure Pipelines"], install_requires=REQUIRES, packages=find_packages(exclude=["tests", "sample_yaml_builds"]), include_package_data=True, long_description=long_description, long_description_content_type="text/markdown" ) azure-functions-devops-build-0.0.22/tests/000077500000000000000000000000001346640171600204745ustar00rootroot00000000000000azure-functions-devops-build-0.0.22/tests/__init__.py000066400000000000000000000005311346640171600226040ustar00rootroot00000000000000# -------------------------------------------------------------------------------------------- # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- azure-functions-devops-build-0.0.22/tests/_config_example.py000066400000000000000000000032461346640171600241720ustar00rootroot00000000000000# -------------------------------------------------------------------------------------------- # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- from azure_functions_devops_build.constants import LINUX_CONSUMPTION, LINUX_DEDICATED, WINDOWS, NODE, PYTHON, JAVA, DOTNET """This file contains the configs needed for the tests""" # You need to fill in the variables with names of the resources you already have # When you are finished setting the configs then you can run test.cmd # The create devops objects setting sets whether the test will run create commands. The default is true as false requires that you # have already created the devops objects CREATE_DEVOPS_OBJECTS = True # Specify the name of the devops objects you want to create/have already created (in the case of create devops objects being false) ORGANIZATION_NAME = '{Azure Devops Organization Name}' # To run the full test suite, the organization must exist PROJECT_NAME = '{Azure Devops Project Name}' # To run the full test suite, the project must exist REPOSITORY_NAME = '{Azure Devops Repository Name}' # To run the full test suite, the repository must exist # Specify the details of your azure functions resource FUNCTIONAPP_NAME = '{Functionapp Name}' STORAGE_NAME = '{Storage Account Name}' RESOURCE_GROUP_NAME = '{Resource Group Name}' FUNCTIONAPP_TYPE = '{Functionapp Type}' # choose from LINUX_CONSUMPTION, LINUX_DEDICATED, WINDOWS FUNCTIONAPP_LANGUAGE = '{Functionapp Language}' # choose from NODE, PYTHON, JAVA, DOTNET azure-functions-devops-build-0.0.22/tests/_helpers.py000066400000000000000000000015121346640171600226460ustar00rootroot00000000000000# -------------------------------------------------------------------------------------------- # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- """This file contains the some helpers needed for the tests""" import string import random from azure.cli.core import get_default_cli from azure.cli.core._profile import Profile def get_credentials(): cli_ctx = get_default_cli() profile = Profile(cli_ctx=cli_ctx) creds, _, _ = profile.get_login_credentials(subscription_id=None) return creds def id_generator(size=10, chars=string.ascii_uppercase + string.digits): return ''.join(random.choice(chars) for _ in range(size)) azure-functions-devops-build-0.0.22/tests/pipeline_scripts/000077500000000000000000000000001346640171600240505ustar00rootroot00000000000000azure-functions-devops-build-0.0.22/tests/pipeline_scripts/dotnet_linux.yml000066400000000000000000000022021346640171600273030ustar00rootroot00000000000000# -------------------------------------------------------------------------------------------- # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- # Starter template for Azure functions build -- dotnet on linux trigger: - master pool: vmImage: 'ubuntu-16.04' steps: - script: | dotnet restore dotnet build --configuration Release - task: DotNetCoreCLI@2 inputs: command: publish arguments: '--configuration Release --output publish_output' projects: '*.csproj' publishWebProjects: false modifyOutputPath: true zipAfterPublish: false - task: ArchiveFiles@2 displayName: 'Archive files' inputs: rootFolderOrFile: '$(System.DefaultWorkingDirectory)/publish_output/s' includeRootFolder: false archiveFile: '$(System.DefaultWorkingDirectory)/build$(Build.BuildId).zip' - task: PublishBuildArtifacts@1 inputs: PathtoPublish: '$(System.DefaultWorkingDirectory)/build$(Build.BuildId).zip' name: 'drop' azure-functions-devops-build-0.0.22/tests/pipeline_scripts/dotnet_windows.yml000066400000000000000000000022061346640171600276420ustar00rootroot00000000000000# -------------------------------------------------------------------------------------------- # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- # Starter template for Azure functions build -- dotnet on windows trigger: - master pool: vmImage: 'VS2017-Win2016' steps: - script: | dotnet restore dotnet build --configuration Release - task: DotNetCoreCLI@2 inputs: command: publish arguments: '--configuration Release --output publish_output' projects: '*.csproj' publishWebProjects: false modifyOutputPath: true zipAfterPublish: false - task: ArchiveFiles@2 displayName: 'Archive files' inputs: rootFolderOrFile: '$(System.DefaultWorkingDirectory)/publish_output/s' includeRootFolder: false archiveFile: '$(System.DefaultWorkingDirectory)/build$(Build.BuildId).zip' - task: PublishBuildArtifacts@1 inputs: PathtoPublish: '$(System.DefaultWorkingDirectory)/build$(Build.BuildId).zip' name: 'drop' azure-functions-devops-build-0.0.22/tests/pipeline_scripts/node_linux.yml000066400000000000000000000015021346640171600267350ustar00rootroot00000000000000# -------------------------------------------------------------------------------------------- # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- # Starter template for Azure functions build -- node on linux trigger: - master pool: vmImage: 'ubuntu-16.04' steps: - task: ArchiveFiles@2 displayName: 'Archive files' inputs: rootFolderOrFile: '$(System.DefaultWorkingDirectory)' includeRootFolder: false archiveFile: '$(System.DefaultWorkingDirectory)/build$(Build.BuildId).zip' - task: PublishBuildArtifacts@1 inputs: PathtoPublish: '$(System.DefaultWorkingDirectory)/build$(Build.BuildId).zip' name: 'drop' azure-functions-devops-build-0.0.22/tests/pipeline_scripts/node_linux_install.yml000066400000000000000000000016301346640171600304650ustar00rootroot00000000000000# -------------------------------------------------------------------------------------------- # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- # Starter template for Azure functions build -- node on linux trigger: - master pool: vmImage: 'ubuntu-16.04' steps: - script: | npm install npm run build --if-present npm prune --production - task: ArchiveFiles@2 displayName: 'Archive files' inputs: rootFolderOrFile: '$(System.DefaultWorkingDirectory)' includeRootFolder: false archiveFile: '$(System.DefaultWorkingDirectory)/build$(Build.BuildId).zip' - task: PublishBuildArtifacts@1 inputs: PathtoPublish: '$(System.DefaultWorkingDirectory)/build$(Build.BuildId).zip' name: 'drop' azure-functions-devops-build-0.0.22/tests/pipeline_scripts/node_linux_install_build.yml000066400000000000000000000016301346640171600316440ustar00rootroot00000000000000# -------------------------------------------------------------------------------------------- # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- # Starter template for Azure functions build -- node on linux trigger: - master pool: vmImage: 'ubuntu-16.04' steps: - script: | npm install npm run build --if-present npm prune --production - task: ArchiveFiles@2 displayName: 'Archive files' inputs: rootFolderOrFile: '$(System.DefaultWorkingDirectory)' includeRootFolder: false archiveFile: '$(System.DefaultWorkingDirectory)/build$(Build.BuildId).zip' - task: PublishBuildArtifacts@1 inputs: PathtoPublish: '$(System.DefaultWorkingDirectory)/build$(Build.BuildId).zip' name: 'drop' azure-functions-devops-build-0.0.22/tests/pipeline_scripts/node_linux_install_build_extensions.yml000066400000000000000000000021001346640171600341140ustar00rootroot00000000000000# -------------------------------------------------------------------------------------------- # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- # Starter template for Azure functions build -- node on linux trigger: - master pool: vmImage: 'ubuntu-16.04' steps: - task: DotNetCoreCLI@2 displayName: '.NET Core build extensions' inputs: projects: extensions.csproj arguments: '--runtime ubuntu.16.04-x64 --output bin/' - script: | npm install npm run build --if-present npm prune --production - task: ArchiveFiles@2 displayName: 'Archive files' inputs: rootFolderOrFile: '$(System.DefaultWorkingDirectory)' includeRootFolder: false archiveFile: '$(System.DefaultWorkingDirectory)/build$(Build.BuildId).zip' - task: PublishBuildArtifacts@1 inputs: PathtoPublish: '$(System.DefaultWorkingDirectory)/build$(Build.BuildId).zip' name: 'drop' azure-functions-devops-build-0.0.22/tests/pipeline_scripts/node_windows.yml000066400000000000000000000015061346640171600272740ustar00rootroot00000000000000# -------------------------------------------------------------------------------------------- # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- # Starter template for Azure functions build -- node on windows trigger: - master pool: vmImage: 'VS2017-Win2016' steps: - task: ArchiveFiles@2 displayName: 'Archive files' inputs: rootFolderOrFile: '$(System.DefaultWorkingDirectory)' includeRootFolder: false archiveFile: '$(System.DefaultWorkingDirectory)/build$(Build.BuildId).zip' - task: PublishBuildArtifacts@1 inputs: PathtoPublish: '$(System.DefaultWorkingDirectory)/build$(Build.BuildId).zip' name: 'drop' azure-functions-devops-build-0.0.22/tests/pipeline_scripts/node_windows_install.yml000066400000000000000000000016341346640171600310240ustar00rootroot00000000000000# -------------------------------------------------------------------------------------------- # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- # Starter template for Azure functions build -- node on windows trigger: - master pool: vmImage: 'VS2017-Win2016' steps: - script: | npm install npm run build --if-present npm prune --production - task: ArchiveFiles@2 displayName: 'Archive files' inputs: rootFolderOrFile: '$(System.DefaultWorkingDirectory)' includeRootFolder: false archiveFile: '$(System.DefaultWorkingDirectory)/build$(Build.BuildId).zip' - task: PublishBuildArtifacts@1 inputs: PathtoPublish: '$(System.DefaultWorkingDirectory)/build$(Build.BuildId).zip' name: 'drop' azure-functions-devops-build-0.0.22/tests/pipeline_scripts/node_windows_install_build.yml000066400000000000000000000016341346640171600322030ustar00rootroot00000000000000# -------------------------------------------------------------------------------------------- # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- # Starter template for Azure functions build -- node on windows trigger: - master pool: vmImage: 'VS2017-Win2016' steps: - script: | npm install npm run build --if-present npm prune --production - task: ArchiveFiles@2 displayName: 'Archive files' inputs: rootFolderOrFile: '$(System.DefaultWorkingDirectory)' includeRootFolder: false archiveFile: '$(System.DefaultWorkingDirectory)/build$(Build.BuildId).zip' - task: PublishBuildArtifacts@1 inputs: PathtoPublish: '$(System.DefaultWorkingDirectory)/build$(Build.BuildId).zip' name: 'drop' azure-functions-devops-build-0.0.22/tests/pipeline_scripts/node_windows_install_build_extensions.yml000066400000000000000000000020751346640171600344620ustar00rootroot00000000000000# -------------------------------------------------------------------------------------------- # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- # Starter template for Azure functions build -- node on windows trigger: - master pool: vmImage: 'VS2017-Win2016' steps: - task: DotNetCoreCLI@2 displayName: '.NET Core build extensions' inputs: projects: extensions.csproj arguments: '--runtime win10-x64 --output bin/' - script: | npm install npm run build --if-present npm prune --production - task: ArchiveFiles@2 displayName: 'Archive files' inputs: rootFolderOrFile: '$(System.DefaultWorkingDirectory)' includeRootFolder: false archiveFile: '$(System.DefaultWorkingDirectory)/build$(Build.BuildId).zip' - task: PublishBuildArtifacts@1 inputs: PathtoPublish: '$(System.DefaultWorkingDirectory)/build$(Build.BuildId).zip' name: 'drop' azure-functions-devops-build-0.0.22/tests/pipeline_scripts/powershell_windows.yml000066400000000000000000000015141346640171600305320ustar00rootroot00000000000000# -------------------------------------------------------------------------------------------- # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- # Starter template for Azure functions build -- powershell on windows trigger: - master pool: vmImage: 'VS2017-Win2016' steps: - task: ArchiveFiles@2 displayName: 'Archive files' inputs: rootFolderOrFile: '$(System.DefaultWorkingDirectory)' includeRootFolder: false archiveFile: '$(System.DefaultWorkingDirectory)/build$(Build.BuildId).zip' - task: PublishBuildArtifacts@1 inputs: PathtoPublish: '$(System.DefaultWorkingDirectory)/build$(Build.BuildId).zip' name: 'drop' azure-functions-devops-build-0.0.22/tests/pipeline_scripts/powershell_windows_extensions.yml000066400000000000000000000017551346640171600330200ustar00rootroot00000000000000# -------------------------------------------------------------------------------------------- # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- # Starter template for Azure functions build -- powershell on windows trigger: - master pool: vmImage: 'VS2017-Win2016' steps: - task: DotNetCoreCLI@2 displayName: '.NET Core build extensions' inputs: projects: extensions.csproj arguments: '--runtime win10-x64 --output bin/' - task: ArchiveFiles@2 displayName: 'Archive files' inputs: rootFolderOrFile: '$(System.DefaultWorkingDirectory)' includeRootFolder: false archiveFile: '$(System.DefaultWorkingDirectory)/build$(Build.BuildId).zip' - task: PublishBuildArtifacts@1 inputs: PathtoPublish: '$(System.DefaultWorkingDirectory)/build$(Build.BuildId).zip' name: 'drop' azure-functions-devops-build-0.0.22/tests/pipeline_scripts/python_linux.yml000066400000000000000000000021201346640171600273260ustar00rootroot00000000000000# -------------------------------------------------------------------------------------------- # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- # Starter template for Azure functions build -- python on linux trigger: - master pool: vmImage: 'ubuntu-16.04' steps: - task: UsePythonVersion@0 displayName: "Setting python version to 3.6 as required by functions" inputs: versionSpec: '3.6' architecture: 'x64' - script: | python3.6 -m venv worker_venv source worker_venv/bin/activate pip3.6 install setuptools - task: ArchiveFiles@2 displayName: 'Archive files' inputs: rootFolderOrFile: '$(System.DefaultWorkingDirectory)' includeRootFolder: false archiveFile: '$(System.DefaultWorkingDirectory)/build$(Build.BuildId).zip' - task: PublishBuildArtifacts@1 inputs: PathtoPublish: '$(System.DefaultWorkingDirectory)/build$(Build.BuildId).zip' name: 'drop' azure-functions-devops-build-0.0.22/tests/pipeline_scripts/python_linux_pip.yml000066400000000000000000000021671346640171600302110ustar00rootroot00000000000000# -------------------------------------------------------------------------------------------- # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- # Starter template for Azure functions build -- python on linux trigger: - master pool: vmImage: 'ubuntu-16.04' steps: - task: UsePythonVersion@0 displayName: "Setting python version to 3.6 as required by functions" inputs: versionSpec: '3.6' architecture: 'x64' - script: | python3.6 -m venv worker_venv source worker_venv/bin/activate pip3.6 install setuptools pip3.6 install -r requirements.txt - task: ArchiveFiles@2 displayName: 'Archive files' inputs: rootFolderOrFile: '$(System.DefaultWorkingDirectory)' includeRootFolder: false archiveFile: '$(System.DefaultWorkingDirectory)/build$(Build.BuildId).zip' - task: PublishBuildArtifacts@1 inputs: PathtoPublish: '$(System.DefaultWorkingDirectory)/build$(Build.BuildId).zip' name: 'drop' azure-functions-devops-build-0.0.22/tests/pipeline_scripts/python_linux_pip_extensions.yml000066400000000000000000000024371346640171600324700ustar00rootroot00000000000000# -------------------------------------------------------------------------------------------- # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- # Starter template for Azure functions build -- python on linux trigger: - master pool: vmImage: 'ubuntu-16.04' steps: - task: DotNetCoreCLI@2 displayName: '.NET Core build extensions' inputs: projects: extensions.csproj arguments: '--runtime ubuntu.16.04-x64 --output bin/' - task: UsePythonVersion@0 displayName: "Setting python version to 3.6 as required by functions" inputs: versionSpec: '3.6' architecture: 'x64' - script: | python3.6 -m venv worker_venv source worker_venv/bin/activate pip3.6 install setuptools pip3.6 install -r requirements.txt - task: ArchiveFiles@2 displayName: 'Archive files' inputs: rootFolderOrFile: '$(System.DefaultWorkingDirectory)' includeRootFolder: false archiveFile: '$(System.DefaultWorkingDirectory)/build$(Build.BuildId).zip' - task: PublishBuildArtifacts@1 inputs: PathtoPublish: '$(System.DefaultWorkingDirectory)/build$(Build.BuildId).zip' name: 'drop' azure-functions-devops-build-0.0.22/tests/suite.py000066400000000000000000000017101346640171600221760ustar00rootroot00000000000000# -------------------------------------------------------------------------------------------- # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- import unittest from .test_artifact_manager import TestArtifactManager from .test_builder_manager import TestBuilderManager from .test_extension_manager import TestExtensionManager from .test_organization_manager import TestOrganizationManager from .test_pool_manager import TestPoolManager from .test_project_manager import TestProjectManager from .test_release_manager import TestReleaseManager from .test_repository_manager import TestRepositoryManager from .test_service_endpoint_manager import TestServiceEndpointManager from .test_yaml_manager import TestYamlManager if __name__ == '__main__': unittest.main() azure-functions-devops-build-0.0.22/tests/test_artifact_manager.py000066400000000000000000000033441346640171600254000ustar00rootroot00000000000000# -------------------------------------------------------------------------------------------- # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- import logging import unittest from azure_functions_devops_build.artifact.artifact_manager import ArtifactManager from ._config import ORGANIZATION_NAME, PROJECT_NAME from ._helpers import get_credentials class TestArtifactManager(unittest.TestCase): @classmethod def setUpClass(cls): logging.disable(logging.CRITICAL) @classmethod def tearDownClass(cls): logging.disable(logging.NOTSET) def setUp(self): self.artifact_manager = ArtifactManager( organization_name=ORGANIZATION_NAME, project_name=PROJECT_NAME, creds=get_credentials() ) def test_list_artifacts(self): artifacts = self.artifact_manager.list_artifacts(build_id="1") if not artifacts: raise unittest.SkipTest("The build pipeline has no artifacts") # If the user is using the devops build manager to make builds there should only be one artifact # called drop as a result of running the builder commands. self.assertEqual(artifacts[0].name, 'drop') def test_invalid_list_artifacts_negative_build_id(self): artifacts = self.artifact_manager.list_artifacts(build_id="-1") self.assertEqual(len(artifacts), 0) def test_invalid_list_artifacts_str_build_id(self): with self.assertRaises(TypeError): artifacts = self.artifact_manager.list_artifacts(build_id="bad_id") azure-functions-devops-build-0.0.22/tests/test_builder_manager.py000066400000000000000000000053361346640171600252340ustar00rootroot00000000000000# -------------------------------------------------------------------------------------------- # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- import logging import unittest from azure_functions_devops_build.builder.builder_manager import BuilderManager from ._config import CREATE_DEVOPS_OBJECTS, ORGANIZATION_NAME, PROJECT_NAME, REPOSITORY_NAME from ._helpers import get_credentials class TestBuilderManager(unittest.TestCase): @classmethod def setUpClass(cls): logging.disable(logging.CRITICAL) @classmethod def tearDownClass(cls): logging.disable(logging.NOTSET) def setUp(self): self._build_definition_name = "_build_{org}_{proj}_{repo}".format( org=ORGANIZATION_NAME, proj=PROJECT_NAME, repo=REPOSITORY_NAME ) self.builder_manager = BuilderManager( organization_name=ORGANIZATION_NAME, project_name=PROJECT_NAME, repository_name=REPOSITORY_NAME, creds=get_credentials() ) @unittest.skipIf( not CREATE_DEVOPS_OBJECTS, "Set CREATE_DEVOPS_OBJECTS to True if you want to create resources for unit testing" ) def test_create_definition(self): definitions = self.builder_manager.list_definitions() # Skip if definition already exists if self._build_definition_name in [d.name for d in definitions]: raise unittest.SkipTest("Build definition exists. No need to create a new build definition.") definition = self.builder_manager.create_devops_build_definition(self._build_definition_name, "Default") self.assertEqual(definition.name, self._build_definition_name) self.assertEqual(definition.process['yamlFilename'], 'azure-pipelines.yml') def test_list_definitions(self): result = self.builder_manager.list_definitions() self.assertIsNotNone(result) self.assertGreaterEqual(len(result), 0) def test_list_builds(self): result = self.builder_manager.list_builds() self.assertIsNotNone(result) self.assertGreaterEqual(len(result), 0) @unittest.skipIf( not CREATE_DEVOPS_OBJECTS, "Set CREATE_DEVOPS_OBJECTS to True if you want to create resources for unit testing" ) def test_create_build(self): result = self.builder_manager.create_build(self._build_definition_name, "Default") self.assertIsNotNone(result) self.assertEqual(result.definition.name, self._build_definition_name) if __name__ == '__main__': unittest.main() azure-functions-devops-build-0.0.22/tests/test_extension_manager.py000066400000000000000000000031321346640171600256120ustar00rootroot00000000000000# -------------------------------------------------------------------------------------------- # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- import logging import unittest from azure_functions_devops_build.extension.extension_manager import ExtensionManager from ._config import CREATE_DEVOPS_OBJECTS, ORGANIZATION_NAME, PROJECT_NAME from ._helpers import get_credentials class TestExtensionManager(unittest.TestCase): @classmethod def setUpClass(cls): logging.disable(logging.CRITICAL) @classmethod def tearDownClass(cls): logging.disable(logging.NOTSET) def setUp(self): self.extension_manager = ExtensionManager(organization_name=ORGANIZATION_NAME, creds=get_credentials()) def test_list_extensions(self): extensions = self.extension_manager.list_extensions() self.assertIsNotNone(extensions) self.assertGreaterEqual(len(extensions), 0) @unittest.skipIf( not CREATE_DEVOPS_OBJECTS, "Set CREATE_DEVOPS_OBJECTS to True if you want to create resources for unit testing" ) def test_create_extension(self): new_extension = self.extension_manager.create_extension('AzureAppServiceSetAppSettings', 'hboelman') self.assertTrue(new_extension.publisher_id == 'hboelman') self.assertTrue(new_extension.extension_id == 'AzureAppServiceSetAppSettings') if __name__ == '__main__': unittest.main() azure-functions-devops-build-0.0.22/tests/test_organization_manager.py000066400000000000000000000106711346640171600263100ustar00rootroot00000000000000# -------------------------------------------------------------------------------------------- # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- import logging import unittest from azure_functions_devops_build.organization.organization_manager import OrganizationManager from azure_functions_devops_build.user.user_manager import UserManager from msrest.exceptions import HttpOperationError from ._config import CREATE_DEVOPS_OBJECTS, ORGANIZATION_NAME from ._helpers import get_credentials, id_generator class TestOrganizationManager(unittest.TestCase): @classmethod def setUpClass(cls): logging.disable(logging.CRITICAL) @classmethod def tearDownClass(cls): logging.disable(logging.NOTSET) def setUp(self): self.organization_manager = OrganizationManager(creds=get_credentials()) def tearDown(self): self.organization_manager.close_connection() def test_valid_organization_name(self): valid_name = "organization-name-" + id_generator(size=3).lower() validation = self.organization_manager.validate_organization_name(valid_name) self.assertTrue(validation.valid) def test_invalid_no_organization_name(self): invalid_name = None validation = self.organization_manager.validate_organization_name(invalid_name) self.assertFalse(validation.valid) def test_invalid_empty_organization_name(self): invalid_name = '' validation = self.organization_manager.validate_organization_name(invalid_name) self.assertFalse(validation.valid) def test_invalid_organization_name_characters(self): invalid_name = 'invalid-organization-name#' validation = self.organization_manager.validate_organization_name(invalid_name) self.assertFalse(validation.valid) def test_invalid_collided_organization_name(self): organizations = self.organization_manager.list_organizations() if organizations.count > 0: existing_name = organizations.value[0].accountName validation = self.organization_manager.validate_organization_name(existing_name) self.assertFalse(validation.valid) def test_list_organizations(self): organizations = self.organization_manager.list_organizations() self.assertIsNotNone(organizations) self.assertIsNotNone(organizations.value) self.assertGreaterEqual(organizations.count, 0) def test_invalid_organization_without_credential(self): no_cred_organization_manager = OrganizationManager(creds=None) with self.assertRaises(HttpOperationError): no_cred_organization_manager.list_organizations() @unittest.skipIf( not CREATE_DEVOPS_OBJECTS, "Set CREATE_DEVOPS_OBJECTS to True if you want to create resources for unit testing" ) def test_create_organization(self): existing_organization_names = [ org.accountName for org in self.organization_manager.list_organizations().value ] # If the organization exists, we will skip this test if ORGANIZATION_NAME in existing_organization_names: raise unittest.SkipTest("Organization already exists. No need to create a new organization.") result = self.organization_manager.create_organization('CUS', ORGANIZATION_NAME) self.assertIsNotNone(result.id) self.assertEqual(result.name, ORGANIZATION_NAME) def test_invalid_create_duplicated_organization(self): existing_organization_names = [ org.accountName for org in self.organization_manager.list_organizations().value ] # If there is no existing organization, we will skip this test if existing_organization_names.count == 0: raise unittest.SkipTest("There is no existing organization. Cannot create a duplicate.") organization_name = existing_organization_names[0] with self.assertRaises(HttpOperationError): self.organization_manager.create_organization('CUS', organization_name) def test_list_regions(self): regions = self.organization_manager.list_regions() self.assertIsNotNone(regions) self.assertIsNotNone(regions.value) self.assertGreaterEqual(regions.count, 0) if __name__ == '__main__': unittest.main() azure-functions-devops-build-0.0.22/tests/test_pool_manager.py000066400000000000000000000024171346640171600245540ustar00rootroot00000000000000# -------------------------------------------------------------------------------------------- # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- import logging import unittest from azure_functions_devops_build.pool.pool_manager import PoolManager from ._config import CREATE_DEVOPS_OBJECTS, ORGANIZATION_NAME, PROJECT_NAME from ._helpers import get_credentials class TestPoolManager(unittest.TestCase): @classmethod def setUpClass(cls): logging.disable(logging.CRITICAL) @classmethod def tearDownClass(cls): logging.disable(logging.NOTSET) def setUp(self): self.pool_manager = PoolManager( organization_name=ORGANIZATION_NAME, project_name=PROJECT_NAME, creds=get_credentials() ) def tearDown(self): self.pool_manager.close_connection() def test_list_pools(self): pools = self.pool_manager.list_pools() self.assertIsNotNone(pools) self.assertIsNotNone(pools.value) self.assertGreaterEqual(pools.count, 0) if __name__ == '__main__': unittest.main() azure-functions-devops-build-0.0.22/tests/test_project_manager.py000066400000000000000000000052461346640171600252540ustar00rootroot00000000000000# -------------------------------------------------------------------------------------------- # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- import logging import unittest from azure_functions_devops_build.organization.organization_manager import OrganizationManager from azure_functions_devops_build.project.project_manager import ProjectManager from msrest.exceptions import HttpOperationError from ._config import CREATE_DEVOPS_OBJECTS, ORGANIZATION_NAME, PROJECT_NAME from ._helpers import get_credentials class TestProjectManager(unittest.TestCase): @classmethod def setUpClass(cls): logging.disable(logging.CRITICAL) @classmethod def tearDownClass(cls): logging.disable(logging.NOTSET) def setUp(self): self.project_manager = ProjectManager(creds=get_credentials(), organization_name=ORGANIZATION_NAME) @unittest.skipIf( not CREATE_DEVOPS_OBJECTS, "Set CREATE_DEVOPS_OBJECTS to True if you want to create resources for unit testing" ) def test_create_project(self): existing_project_names = [ proj.name for proj in self.project_manager.list_projects().value ] # If the project exists, we will skip this test if PROJECT_NAME in existing_project_names: raise unittest.SkipTest("Project already exists. No need to create a new project.") result = self.project_manager.create_project(PROJECT_NAME) self.assertIsNotNone(result.id) self.assertEqual(result.name, PROJECT_NAME) def test_invalid_create_duplicated_project(self): existing_project_names = [ proj.name for proj in self.project_manager.list_projects().value ] # If no project exists, we will skip this test if len(existing_project_names) == 0: raise unittest.SkipTest("There is no existing project. Cannot create a duplicate.") result = self.project_manager.create_project(existing_project_names[0]) self.assertFalse(result.valid) def test_invalid_create_project_with_bad_name(self): bad_project_name = "invalid-project-name#" result = self.project_manager.create_project(bad_project_name) self.assertFalse(result.valid) def test_list_projects(self): projects = self.project_manager.list_projects() self.assertIsNotNone(projects) self.assertIsNotNone(projects.value) self.assertGreaterEqual(projects.count, 0) if __name__ == '__main__': unittest.main() azure-functions-devops-build-0.0.22/tests/test_release_manager.py000066400000000000000000000076671346640171600252370ustar00rootroot00000000000000# -------------------------------------------------------------------------------------------- # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- import unittest from azure_functions_devops_build.release.release_manager import ReleaseManager from azure_functions_devops_build.service_endpoint.service_endpoint_manager import ServiceEndpointManager from ._config import CREATE_DEVOPS_OBJECTS, ORGANIZATION_NAME, PROJECT_NAME, REPOSITORY_NAME, FUNCTIONAPP_TYPE, FUNCTIONAPP_NAME, STORAGE_NAME, RESOURCE_GROUP_NAME from ._helpers import get_credentials class TestReleaseManager(unittest.TestCase): def setUp(self): self._build_definition_name = "_build_{org}_{proj}_{repo}".format( org=ORGANIZATION_NAME, proj=PROJECT_NAME, repo=REPOSITORY_NAME ) self._release_definition_name = "_release_{org}_{proj}_{repo}".format( org=ORGANIZATION_NAME, proj=PROJECT_NAME, repo=REPOSITORY_NAME ) self.release_manager = ReleaseManager( organization_name=ORGANIZATION_NAME, project_name=PROJECT_NAME, creds=get_credentials() ) self.service_endpoint_manager = ServiceEndpointManager( organization_name=ORGANIZATION_NAME, project_name=PROJECT_NAME ) def test_list_release_definitions(self): definitions = self.release_manager.list_release_definitions() self.assertIsNotNone(definitions) self.assertGreaterEqual(len(definitions), 0) def test_list_releases(self): releases = self.release_manager.list_releases() self.assertIsNotNone(releases) self.assertGreaterEqual(len(releases), 0) @unittest.skipIf( not CREATE_DEVOPS_OBJECTS, "Set CREATE_DEVOPS_OBJECTS to True if you want to create resources for unit testing" ) def test_create_release_definition(self): service_endpoint_name = self.service_endpoint_manager._get_service_endpoint_name(REPOSITORY_NAME, "pipeline") # Skip if service endpoint does not exist if service_endpoint_name is None: raise unittest.SkipTest("There is no service endpoint. Cannot create release definition.") # Skip if release definition already exists definitions = self.release_manager.list_release_definitions() if self._release_definition_name in [d.name for d in definitions]: raise unittest.SkipTest("The release definition exists. No need to create one.") new_definition = self.release_manager.create_release_definition( build_name=self._build_definition_name, artifact_name='drop', pool_name="Hosted VS2017", service_endpoint_name=service_endpoint_name, release_definition_name=self._release_definition_name, app_type=FUNCTIONAPP_TYPE, functionapp_name=FUNCTIONAPP_NAME, storage_name=STORAGE_NAME, resource_name=RESOURCE_GROUP_NAME ) self.assertIsNotNone(new_definition) self.assertEqual(new_definition.name, self._release_definition_name) @unittest.skipIf( not CREATE_DEVOPS_OBJECTS, "Set CREATE_DEVOPS_OBJECTS to True if you want to create resources for unit testing" ) def test_create_release(self): # Skip if release definition does not exist definitions = self.release_manager.list_release_definitions() if not self._release_definition_name in [d.name for d in definitions]: raise unittest.SkipTest("There is no release definition. Cannot create a new release.") release = self.release_manager.create_release(self._release_definition_name) self.assertIsNotNone(release) if __name__ == '__main__': unittest.main() azure-functions-devops-build-0.0.22/tests/test_repository_manager.py000066400000000000000000000044401346640171600260200ustar00rootroot00000000000000# -------------------------------------------------------------------------------------------- # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- import logging import unittest from azure_functions_devops_build.repository.repository_manager import RepositoryManager from ._config import ORGANIZATION_NAME, PROJECT_NAME, REPOSITORY_NAME, CREATE_DEVOPS_OBJECTS from ._helpers import get_credentials class TestRepositoryManager(unittest.TestCase): @classmethod def setUpClass(cls): logging.disable(logging.CRITICAL) @classmethod def tearDownClass(cls): logging.disable(logging.NOTSET) def setUp(self): self.repository_manager = RepositoryManager( organization_name=ORGANIZATION_NAME, project_name=PROJECT_NAME, creds=get_credentials() ) def test_get_repo_branches(self): result = self.repository_manager.get_azure_devops_repository_branches(REPOSITORY_NAME) self.assertIsNotNone(result) self.assertGreaterEqual(len(result), 0) def test_get_repo(self): result = self.repository_manager.get_azure_devops_repository(REPOSITORY_NAME) if result is not None: self.assertEqual(result.name, REPOSITORY_NAME) def test_invalid_get_repo_bad_name(self): result = self.repository_manager.get_azure_devops_repository("bad@name") self.assertIsNone(result) def test_create_repository(self): result = self.repository_manager.get_azure_devops_repository(REPOSITORY_NAME) if result is None: repository = self.repository_manager.create_repository(REPOSITORY_NAME) self.assertEqual(repository.name, REPOSITORY_NAME) def test_list_repositories(self): result = self.repository_manager.list_repositories() self.assertIsNotNone(result) self.assertGreaterEqual(len(result), 0) def test_list_commits(self): result = self.repository_manager.list_commits(REPOSITORY_NAME) self.assertIsNotNone(result) self.assertGreaterEqual(len(result), 0) if __name__ == '__main__': unittest.main() azure-functions-devops-build-0.0.22/tests/test_service_endpoint_manager.py000066400000000000000000000053641346640171600271470ustar00rootroot00000000000000# -------------------------------------------------------------------------------------------- # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- import logging import unittest from azure_functions_devops_build.service_endpoint.service_endpoint_manager import ServiceEndpointManager from azure_functions_devops_build.exceptions import RoleAssignmentException from ._config import ORGANIZATION_NAME, PROJECT_NAME, REPOSITORY_NAME, CREATE_DEVOPS_OBJECTS from ._helpers import get_credentials class TestServiceEndpointManager(unittest.TestCase): @classmethod def setUpClass(cls): logging.disable(logging.CRITICAL) @classmethod def tearDownClass(cls): logging.disable(logging.NOTSET) def setUp(self): self.service_endpoint_manager = ServiceEndpointManager( organization_name=ORGANIZATION_NAME, project_name=PROJECT_NAME, creds=get_credentials() ) @unittest.SkipTest def test_get_service_endpoints(self): endpoints = self.service_endpoint_manager.get_service_endpoints(REPOSITORY_NAME) self.assertIsNotNone(endpoints) self.assertGreaterEqual(len(endpoints), 0) @unittest.SkipTest def test_get_service_endpoints_has_existing_endpoint(self): expected_endpoint_name = self.service_endpoint_manager._get_service_endpoint_name(REPOSITORY_NAME, "pipeline") endpoints = self.service_endpoint_manager.get_service_endpoints(REPOSITORY_NAME) if len(endpoints) > 0: self.assertEqual(endpoints[0].name, expected_endpoint_name) @unittest.skipIf( not CREATE_DEVOPS_OBJECTS, "Set CREATE_DEVOPS_OBJECTS to True if you want to create resources for testing" ) def test_create_service_endpoint(self): expected_endpoint_name = self.service_endpoint_manager._get_service_endpoint_name(REPOSITORY_NAME, "pipeline") existing_endpoints = self.service_endpoint_manager.get_service_endpoints(REPOSITORY_NAME) # Skip if endpoint exists if len(existing_endpoints) > 0: raise unittest.SkipTest("Service endpoint does not exist") # Skip if role assignment permission is not granted try: endpoint = self.service_endpoint_manager.create_service_endpoint(expected_endpoint_name) except RoleAssignmentException: raise unittest.SkipTest("Test operator may not have RoleAssignment/Write permission") self.assertIsNotNone(endpoint) self.assertEqual(endpoint.name, expected_endpoint_name) if __name__ == '__main__': unittest.main() azure-functions-devops-build-0.0.22/tests/test_yaml_manager.py000066400000000000000000000240521346640171600245440ustar00rootroot00000000000000# -------------------------------------------------------------------------------------------- # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- import os import json import logging import unittest from azure_functions_devops_build.yaml.yaml_manager import YamlManager from azure_functions_devops_build.constants import ( PYTHON, NODE, DOTNET, POWERSHELL, LINUX_CONSUMPTION, LINUX_DEDICATED, WINDOWS ) from ._helpers import id_generator class TestYamlManager(unittest.TestCase): @classmethod def setUpClass(cls): logging.disable(logging.CRITICAL) TestYamlManager.backup_file("requirements.txt") TestYamlManager.backup_file("package.json") @classmethod def tearDownClass(cls): logging.disable(logging.NOTSET) TestYamlManager.restore_file("requirements.txt") TestYamlManager.restore_file("package.json") def setUp(self): self._current_directory = os.path.dirname(os.path.realpath(__file__)) def tearDown(self): if os.path.isfile("azure-pipelines.yml"): os.remove("azure-pipelines.yml") if os.path.isfile("requirements.txt"): os.remove("requirements.txt") if os.path.isfile("package.json"): os.remove("package.json") if os.path.isfile("extensions.csproj"): os.remove("extensions.csproj") def test_create_yaml_python_linux(self): yaml_manager = YamlManager(language=PYTHON, app_type=LINUX_CONSUMPTION) yaml_manager.create_yaml() yaml_path = os.path.join(self._current_directory, "pipeline_scripts", "python_linux.yml") result = TestYamlManager.is_two_files_equal("azure-pipelines.yml", yaml_path) self.assertTrue(result) def test_create_yaml_python_linux_pip(self): # Create a temporary requirements.txt open("requirements.txt", "a").close() yaml_manager = YamlManager(language=PYTHON, app_type=LINUX_CONSUMPTION) yaml_manager.create_yaml() yaml_path = os.path.join(self._current_directory, "pipeline_scripts", "python_linux_pip.yml") result = TestYamlManager.is_two_files_equal("azure-pipelines.yml", yaml_path) self.assertTrue(result) def test_create_yaml_python_linux_pip_extensions(self): # Create a temporary requirements.txt # Create a temporary extensions.csproj open("requirements.txt", "a").close() with open("extensions.csproj", "w") as f: f.write('') yaml_manager = YamlManager(language=PYTHON, app_type=LINUX_CONSUMPTION) yaml_manager.create_yaml() yaml_path = os.path.join(self._current_directory, "pipeline_scripts", "python_linux_pip_extensions.yml") result = TestYamlManager.is_two_files_equal("azure-pipelines.yml", yaml_path) self.assertTrue(result) def test_create_yaml_dotnet_linux(self): yaml_manager = YamlManager(language=DOTNET, app_type=LINUX_CONSUMPTION) yaml_manager.create_yaml() yaml_path = os.path.join(self._current_directory, "pipeline_scripts", "dotnet_linux.yml") result = TestYamlManager.is_two_files_equal("azure-pipelines.yml", yaml_path) self.assertTrue(result) def test_create_yaml_dotnet_windows(self): yaml_manager = YamlManager(language=DOTNET, app_type=WINDOWS) yaml_manager.create_yaml() yaml_path = os.path.join(self._current_directory, "pipeline_scripts", "dotnet_windows.yml") result = TestYamlManager.is_two_files_equal("azure-pipelines.yml", yaml_path) self.assertTrue(result) def test_create_yaml_node_linux(self): yaml_manager = YamlManager(language=NODE, app_type=LINUX_CONSUMPTION) yaml_manager.create_yaml() yaml_path = os.path.join(self._current_directory, "pipeline_scripts", "node_linux.yml") result = TestYamlManager.is_two_files_equal("azure-pipelines.yml", yaml_path) self.assertTrue(result) def test_create_yaml_node_linux_install(self): # Create a temporary requirements.txt with open("package.json", "w") as f: json.dump(obj={}, fp=f) yaml_manager = YamlManager(language=NODE, app_type=LINUX_CONSUMPTION) yaml_manager.create_yaml() yaml_path = os.path.join(self._current_directory, "pipeline_scripts", "node_linux_install.yml") result = TestYamlManager.is_two_files_equal("azure-pipelines.yml", yaml_path) self.assertTrue(result) def test_create_yaml_node_linux_install_build(self): # Create a temporary requirements.txt and make a build script in it with open("package.json", "w") as f: json.dump(obj={"scripts": {"build": "echo test"}}, fp=f) yaml_manager = YamlManager(language=NODE, app_type=LINUX_CONSUMPTION) yaml_manager.create_yaml() yaml_path = os.path.join(self._current_directory, "pipeline_scripts", "node_linux_install_build.yml") result = TestYamlManager.is_two_files_equal("azure-pipelines.yml", yaml_path) self.assertTrue(result) def test_create_yaml_node_linux_install_build_extensions(self): # Create a temporary requirements.txt and make a build script in it # Create a temporary extensions.csproj with open("package.json", "w") as f: json.dump(obj={"scripts": {"build": "echo test"}}, fp=f) with open("extensions.csproj", "w") as f: f.write('') yaml_manager = YamlManager(language=NODE, app_type=LINUX_CONSUMPTION) yaml_manager.create_yaml() yaml_path = os.path.join(self._current_directory, "pipeline_scripts", "node_linux_install_build_extensions.yml") result = TestYamlManager.is_two_files_equal("azure-pipelines.yml", yaml_path) self.assertTrue(result) def test_create_yaml_node_windows(self): yaml_manager = YamlManager(language=NODE, app_type=WINDOWS) yaml_manager.create_yaml() yaml_path = os.path.join(self._current_directory, "pipeline_scripts", "node_windows.yml") result = TestYamlManager.is_two_files_equal("azure-pipelines.yml", yaml_path) self.assertTrue(result) def test_create_yaml_node_windows_install(self): # Create a temporary requirements.txt with open("package.json", "w") as f: json.dump(obj={}, fp=f) yaml_manager = YamlManager(language=NODE, app_type=WINDOWS) yaml_manager.create_yaml() yaml_path = os.path.join(self._current_directory, "pipeline_scripts", "node_windows_install.yml") result = TestYamlManager.is_two_files_equal("azure-pipelines.yml", yaml_path) self.assertTrue(result) def test_create_yaml_node_windows_install_build(self): # Create a temporary requirements.txt and make a build script in it with open("package.json", "w") as f: json.dump(obj={"scripts": {"build": "echo test"}}, fp=f) yaml_manager = YamlManager(language=NODE, app_type=WINDOWS) yaml_manager.create_yaml() yaml_path = os.path.join(self._current_directory, "pipeline_scripts", "node_windows_install_build.yml") result = TestYamlManager.is_two_files_equal("azure-pipelines.yml", yaml_path) self.assertTrue(result) def test_create_yaml_node_windows_install_build_extensions(self): # Create a temporary requirements.txt and make a build script in it # Create a temporary extensions.csproj with open("package.json", "w") as f: json.dump(obj={"scripts": {"build": "echo test"}}, fp=f) with open("extensions.csproj", "w") as f: f.write('') yaml_manager = YamlManager(language=NODE, app_type=WINDOWS) yaml_manager.create_yaml() yaml_path = os.path.join(self._current_directory, "pipeline_scripts", "node_windows_install_build_extensions.yml") result = TestYamlManager.is_two_files_equal("azure-pipelines.yml", yaml_path) self.assertTrue(result) def test_create_yaml_powershell_windows(self): yaml_manager = YamlManager(language=POWERSHELL, app_type=WINDOWS) yaml_manager.create_yaml() yaml_path = os.path.join(self._current_directory, "pipeline_scripts", "powershell_windows.yml") result = TestYamlManager.is_two_files_equal("azure-pipelines.yml", yaml_path) self.assertTrue(result) def test_create_yaml_powershell_windows_extensions(self): # Create a temporary extensions.csproj with open("extensions.csproj", "w") as f: f.write('') yaml_manager = YamlManager(language=POWERSHELL, app_type=WINDOWS) yaml_manager.create_yaml() yaml_path = os.path.join(self._current_directory, "pipeline_scripts", "powershell_windows_extensions.yml") result = TestYamlManager.is_two_files_equal("azure-pipelines.yml", yaml_path) self.assertTrue(result) @staticmethod def is_two_files_equal(filepath1, filepath2): with open(filepath1, "r") as f1: content1 = f1.read() with open(filepath2, "r") as f2: content2 = f2.read() return content1 == content2 @staticmethod def backup_file(filepath): backup_filepath = "{}.bak".format(filepath) if os.path.isfile(filepath): if os.path.isfile(backup_filepath): os.remove(backup_filepath) os.rename(filepath, backup_filepath) @staticmethod def restore_file(filepath): backup_filepath = "{}.bak".format(filepath) if os.path.isfile(backup_filepath): if os.path.isfile(filepath): os.remove(filepath) os.rename(backup_filepath, filepath) if __name__ == '__main__': unittest.main()