vsts-cd-manager/vsts_cd_manager/continuous_delivery_manager.py

265 строки
14 KiB
Python

# --------------------------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See License.txt in the project root for license information.
# --------------------------------------------------------------------------------------------
from __future__ import print_function
import re
import time
try:
from urllib.parse import quote, urlparse
except ImportError:
from urllib import quote #pylint: disable=no-name-in-module
from urlparse import urlparse #pylint: disable=import-error
from azuretfs import VstsInfoProvider
from continuous_delivery import ContinuousDelivery
from continuous_delivery.models import (AuthorizationInfo, AuthorizationInfoParameters, BuildConfiguration,
CiArtifact, CiConfiguration, ProvisioningConfiguration,
ProvisioningConfigurationSource, ProvisioningConfigurationTarget,
SlotSwapConfiguration, SourceRepository)
from vsts_accounts import Account
from vsts_accounts.models import (AccountCreateInfoInternal)
# Use this class to setup or remove continuous delivery mechanisms for Azure web sites using VSTS build and release
class ContinuousDeliveryManager(object):
def __init__(self, progress_callback):
"""
Use this class to setup or remove continuous delivery mechanisms for Azure web sites using VSTS build and release
:param progress_callback: method of the form func(count, total, message)
"""
self._update_progress = progress_callback or self._skip_update_progress
self._azure_info = _AzureInfo()
self._repo_info = _RepositoryInfo()
def get_vsts_app_id(self):
"""
Use this method to get the 'resource' value for creating an Azure token to be used by VSTS
:return: App id for VSTS
"""
return '499b84ac-1321-427f-aa17-267ca6975798'
def set_azure_web_info(self, resource_group_name, website_name, credentials,
subscription_id, subscription_name, tenant_id, webapp_location):
"""
Call this method before attempting to setup continuous delivery to setup the azure settings
:param resource_group_name:
:param website_name:
:param credentials:
:param subscription_id:
:param subscription_name:
:param tenant_id:
:param webapp_location:
:return:
"""
self._azure_info.resource_group_name = resource_group_name
self._azure_info.website_name = website_name
self._azure_info.credentials = credentials
self._azure_info.subscription_id = subscription_id
self._azure_info.subscription_name = subscription_name
self._azure_info.tenant_id = tenant_id
self._azure_info.webapp_location = webapp_location
def set_repository_info(self, repo_url, branch, git_token):
"""
Call this method before attempting to setup continuous delivery to setup the source control settings
:param repo_url:
:param branch:
:param git_token:
:return:
"""
self._repo_info.url = repo_url
self._repo_info.branch = branch
self._repo_info.git_token = git_token
def remove_continuous_delivery(self):
"""
To be Implemented
:return:
"""
# TODO: this would be called by appservice web source-control delete
return
def setup_continuous_delivery(self, azure_deployment_slot, app_type, vsts_account_name, create_account,
vsts_app_auth_token):
"""
Use this method to setup Continuous Delivery of an Azure web site from a source control repository.
:param azure_deployment_slot: the slot to use for deployment
:param app_type: the type of app that will be deployed. i.e. AspNetWap, AspNetCore, etc.
:param vsts_account_name:
:param create_account:
:param vsts_app_auth_token:
:return: a message indicating final status and instructions for the user
"""
branch = self._repo_info.branch or 'refs/heads/master'
# Verify inputs before we start generating tokens
source_repository, account_name, team_project_name = self._get_source_repository(self._repo_info.url,
self._repo_info.git_token, branch, self._azure_info.credentials)
self._verify_vsts_parameters(vsts_account_name, source_repository)
vsts_account_name = vsts_account_name or account_name
cd_project_name = team_project_name or self._azure_info.website_name
account_url = 'https://{}.visualstudio.com'.format(quote(vsts_account_name))
portalext_account_url = 'https://{}.portalext.visualstudio.com'.format(quote(vsts_account_name))
# Try to create the account (return value of None means that the account already exists
#accountClient = Account('3.2-preview', 'https://app.vssps.visualstudio.com', self._azure_info.credentials)
#account_creation_parameters = AccountCreateInfoInternal(vsts_account_name)
#creation_results = accountClient.create_account(account_creation_parameters, True)
#account_created = not creation_results == None
#print(creation_results)
#return None
account_created = False
# Create ContinuousDelivery client
cd = ContinuousDelivery('3.2-preview.1', portalext_account_url, self._azure_info.credentials)
# Construct the config body of the continuous delivery call
build_configuration = BuildConfiguration(app_type)
source = ProvisioningConfigurationSource('codeRepository', source_repository, build_configuration)
auth_info = AuthorizationInfo('Headers', AuthorizationInfoParameters('Bearer ' + vsts_app_auth_token))
slot_name = azure_deployment_slot or 'staging'
slot_swap = None # TODO SlotSwapConfiguration(slot_name)
target = ProvisioningConfigurationTarget('azure', 'windowsAppService', 'production', 'Production',
self._azure_info.subscription_id,
self._azure_info.subscription_name, self._azure_info.tenant_id,
self._azure_info.website_name, self._azure_info.resource_group_name,
self._azure_info.webapp_location, auth_info, slot_swap)
ci_config = CiConfiguration(CiArtifact(name=cd_project_name))
config = ProvisioningConfiguration(None, source, [target], ci_config)
# Configure the continuous deliver using VSTS as a backend
response = cd.provisioning_configuration(config)
if response.ci_configuration.result.status == 'queued':
final_status = self._wait_for_cd_completion(cd, response)
return self._get_summary(final_status, account_url, vsts_account_name, account_created, self._azure_info.subscription_id,
self._azure_info.resource_group_name, self._azure_info.website_name)
else:
raise RuntimeError('Unknown status returned from provisioning_configuration: ' + response.ci_configuration.result.status)
def _verify_vsts_parameters(self, cd_account, source_repository):
# if provider is vsts and repo is not vsts then we need the account name
if source_repository.type in ['Github', 'ExternalGit'] and not cd_account:
raise RuntimeError('You must provide a value for cd-account since your repo-url is not a Team Services repository.')
def _get_source_repository(self, uri, token, branch, cred):
# Determine the type of repository (TfsGit, github, tfvc, externalGit)
# Find the identifier and set the properties; default to externalGit
type = 'ExternalGit'
identifier = uri
account_name = None
team_project_name = None
auth_info = None
match = re.match(r'[htps]+\:\/\/(.+)\.visualstudio\.com.*\/_git\/(.+)', uri, re.IGNORECASE)
if match:
type = 'TfsGit'
account_name = match.group(1)
# we have to get the repo id as the identifier
info = self._get_vsts_info(uri, cred)
identifier = info.repository_info.id
team_project_name = info.repository_info.project_info.name
else:
match = re.match(r'[htps]+\:\/\/github\.com\/(.+)', uri, re.IGNORECASE)
if match:
type = 'Github'
identifier = match.group(1)
auth_info = AuthorizationInfo('PersonalAccessToken', AuthorizationInfoParameters(None, token))
else:
match = re.match(r'[htps]+\:\/\/(.+)\.visualstudio\.com\/(.+)', uri, re.IGNORECASE)
if match:
type = 'TFVC'
identifier = match.group(2)
account_name = match.group(1)
sourceRepository = SourceRepository(type, identifier, branch, auth_info)
return sourceRepository, account_name, team_project_name
def _get_vsts_info(self, vsts_repo_url, cred):
vsts_info_client = VstsInfoProvider('3.2-preview', vsts_repo_url, cred)
return vsts_info_client.get_vsts_info()
def _wait_for_cd_completion(self, cd, response):
# Wait for the configuration to finish and report on the status
step = 5
max = 100
self._update_progress(step, max, 'Setting up Team Services continuous deployment')
config = cd.get_provisioning_configuration(response.id)
while config.ci_configuration.result.status == 'queued' or config.ci_configuration.result.status == 'inProgress':
step += 5 if step + 5 < max else 0
self._update_progress(step, max, 'Setting up Team Services continuous deployment (' + config.ci_configuration.result.status + ')')
time.sleep(2)
config = cd.get_provisioning_configuration(response.id)
if config.ci_configuration.result.status == 'failed':
self._update_progress(max, max, 'Setting up Team Services continuous deployment (FAILED)')
raise RuntimeError(config.ci_configuration.result.status_message)
self._update_progress(max, max, 'Setting up Team Services continuous deployment (SUCCEEDED)')
return config
def _get_summary(self, provisioning_configuration, account_url, account_name, account_created, subscription_id, resource_group_name, website_name):
summary = '\n'
if not provisioning_configuration: return None
# Add the vsts account info
if not account_created:
summary += "The Team Services account '{}' was updated to handle the continuous delivery.\n".format(account_url)
else:
summary += "The Team Services account '{}' was created to handle the continuous delivery.\n".format(account_url)
# Add the subscription info
website_url = 'https://portal.azure.com/#resource/subscriptions/{}/resourceGroups/{}/providers/Microsoft.Web/sites/{}/vstscd'.format(
quote(subscription_id), quote(resource_group_name), quote(website_name))
summary += 'You can check on the status of the Azure web site deployment here:\n'
summary += website_url + '\n'
# setup the build url and release url
build_url = ''
release_url = ''
if provisioning_configuration.ci_configuration and provisioning_configuration.ci_configuration.project:
project_id = provisioning_configuration.ci_configuration.project.id
if provisioning_configuration.ci_configuration.build_definition:
build_url = '{}/{}/_build?_a=simple-process&definitionId={}'.format(
account_url, quote(project_id), quote(provisioning_configuration.ci_configuration.build_definition.id))
if provisioning_configuration.ci_configuration.release_definition:
release_url = '{}/{}/_apps/hub/ms.vss-releaseManagement-web.hub-explorer?definitionId={}&_a=releases'.format(
account_url, quote(project_id), quote(provisioning_configuration.ci_configuration.release_definition.id))
return ContinuousDeliveryResult(account_created, account_url, resource_group_name,
subscription_id, website_name, website_url, summary,
build_url, release_url, provisioning_configuration)
def _skip_update_progress(self, count, total, message):
return
class _AzureInfo(object):
def __init__(self):
self.resource_group_name = None
self.website_name = None
self.credentials = None
self.subscription_id = None
self.subscription_name = None
self.tenant_id = None
self.webapp_location = None
class _RepositoryInfo(object):
def __init__(self):
self.url = None
self.branch = None
self.git_token = None
class ContinuousDeliveryResult(object):
def __init__(self, account_created, account_url, resource_group, subscription_id, website_name, cd_url, message, build_url, release_url, final_status):
self.vsts_account_created = account_created
self.vsts_account_url = account_url
self.vsts_build_def_url = build_url
self.vsts_release_def_url = release_url
self.azure_resource_group = resource_group
self.azure_subscription_id = subscription_id
self.azure_website_name = website_name
self.azure_continuous_delivery_url = cd_url
self.status = 'SUCCESS'
self.status_message = message
self.status_details = final_status