Change the src folder location, and update the scripts.
This commit is contained in:
Родитель
e7f303d5d6
Коммит
03923a6dfe
52
Dockerfile
52
Dockerfile
|
@ -1,52 +0,0 @@
|
|||
#---------------------------------------------------------------------------------------------
|
||||
# Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
# Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
#---------------------------------------------------------------------------------------------
|
||||
|
||||
# This Dockerfile uses the latest code from the Git repo.
|
||||
# Clone the repo then run 'docker build' with this Dockerfile file to get the latest versions of
|
||||
# *all* CLI modules as in the Git repo.
|
||||
|
||||
FROM python:3.5.2-alpine
|
||||
|
||||
WORKDIR azure-cli
|
||||
COPY . /azure-cli
|
||||
# pip wheel - required for CLI packaging
|
||||
# jmespath-terminal - we include jpterm as a useful tool
|
||||
RUN pip install --upgrade pip wheel jmespath-terminal
|
||||
# bash gcc openssl-dev libffi-dev musl-dev - dependencies required for CLI
|
||||
# jq - we include jq as a useful tool
|
||||
# openssh - included for ssh-keygen
|
||||
# ca-certificates
|
||||
# wget - required for installing jp
|
||||
RUN apk update && apk add bash gcc openssl-dev libffi-dev musl-dev jq openssh ca-certificates wget openssl && update-ca-certificates
|
||||
# We also, install jp
|
||||
RUN wget https://github.com/jmespath/jp/releases/download/0.1.2/jp-linux-amd64 -qO /usr/local/bin/jp && chmod +x /usr/local/bin/jp
|
||||
|
||||
# 1. Build packages and store in tmp dir
|
||||
# 2. Install the cli and the other command modules that weren't included
|
||||
RUN /bin/bash -c 'TMP_PKG_DIR=$(mktemp -d); \
|
||||
for d in src/azure-cli src/azure-cli-core src/azure-cli-nspkg src/command_modules/azure-cli-*/; \
|
||||
do cd $d; python setup.py bdist_wheel -d $TMP_PKG_DIR; cd -; \
|
||||
done; \
|
||||
MODULE_NAMES=""; \
|
||||
for m in src/command_modules/azure-cli-*/; \
|
||||
do MODULE_NAMES="$MODULE_NAMES $(echo $m | cut -d '/' -f 3)"; \
|
||||
done; \
|
||||
pip install azure-cli $MODULE_NAMES -f $TMP_PKG_DIR;'
|
||||
|
||||
# Tab completion
|
||||
RUN echo -e "\
|
||||
_python_argcomplete() {\n\
|
||||
local IFS='\v'\n\
|
||||
COMPREPLY=( \$(IFS=\"\$IFS\" COMP_LINE=\"\$COMP_LINE\" COMP_POINT=\"\$COMP_POINT\" _ARGCOMPLETE_COMP_WORDBREAKS=\"\$COMP_WORDBREAKS\" _ARGCOMPLETE=1 \"\$1\" 8>&1 9>&2 1>/dev/null 2>/dev/null) )\n\
|
||||
if [[ \$? != 0 ]]; then\n\
|
||||
unset COMPREPLY\n\
|
||||
fi\n\
|
||||
}\n\
|
||||
complete -o nospace -F _python_argcomplete \"az\"\n\
|
||||
" > ~/.bashrc
|
||||
|
||||
WORKDIR /
|
||||
|
||||
CMD bash
|
21
LICENSE
21
LICENSE
|
@ -1,21 +0,0 @@
|
|||
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
|
|
@ -0,0 +1,96 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" DefaultTargets="Build">
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<SchemaVersion>2.0</SchemaVersion>
|
||||
<ProjectGuid>{938454f7-93bd-41a7-84b2-3c89d64b969d}</ProjectGuid>
|
||||
<ProjectHome>src\</ProjectHome>
|
||||
<StartupFile>azure-cli\azure\cli\__main__.py</StartupFile>
|
||||
<SearchPath>.</SearchPath>
|
||||
<WorkingDirectory>
|
||||
</WorkingDirectory>
|
||||
<OutputPath>.</OutputPath>
|
||||
<ProjectTypeGuids>{888888a0-9f3d-457c-b088-3a5042f75d52}</ProjectTypeGuids>
|
||||
<LaunchProvider>Standard Python launcher</LaunchProvider>
|
||||
<InterpreterId>{54f4b6dc-0859-46dc-99bb-b275c9d0aca3}</InterpreterId>
|
||||
<InterpreterVersion>3.5</InterpreterVersion>
|
||||
<EnableNativeCodeDebugging>False</EnableNativeCodeDebugging>
|
||||
<CommandLineArguments>
|
||||
</CommandLineArguments>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)' == 'Debug'" />
|
||||
<PropertyGroup Condition="'$(Configuration)' == 'Release'" />
|
||||
<PropertyGroup>
|
||||
<VisualStudioVersion Condition=" '$(VisualStudioVersion)' == '' ">10.0</VisualStudioVersion>
|
||||
<PtvsTargetsFile>$(MSBuildextensionPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\Python Tools\Microsoft.PythonTools.targets</PtvsTargetsFile>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="command_modules\azure-cli-batch-extensions\azure\cli\command_modules\batch_extensions\commands.py" />
|
||||
<Compile Include="command_modules\azure-cli-batch-extensions\azure\cli\command_modules\batch_extensions\custom.py" />
|
||||
<Compile Include="command_modules\azure-cli-batch-extensions\azure\cli\command_modules\batch_extensions\tests\test_file_utils.py">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="command_modules\azure-cli-batch-extensions\azure\cli\command_modules\batch_extensions\tests\test_template_utils.py" />
|
||||
<Compile Include="command_modules\azure-cli-batch-extensions\azure\cli\command_modules\batch_extensions\tests\__init__.py" />
|
||||
<Compile Include="command_modules\azure-cli-batch-extensions\azure\cli\command_modules\batch_extensions\_client_factory.py" />
|
||||
<Compile Include="command_modules\azure-cli-batch-extensions\azure\cli\command_modules\batch_extensions\_file_utils.py" />
|
||||
<Compile Include="command_modules\azure-cli-batch-extensions\azure\cli\command_modules\batch_extensions\_help.py" />
|
||||
<Compile Include="command_modules\azure-cli-batch-extensions\azure\cli\command_modules\batch_extensions\_job_utils.py" />
|
||||
<Compile Include="command_modules\azure-cli-batch-extensions\azure\cli\command_modules\batch_extensions\_params.py" />
|
||||
<Compile Include="command_modules\azure-cli-batch-extensions\azure\cli\command_modules\batch_extensions\_pool_utils.py" />
|
||||
<Compile Include="command_modules\azure-cli-batch-extensions\azure\cli\command_modules\batch_extensions\_shipyard_utils.py">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="command_modules\azure-cli-batch-extensions\azure\cli\command_modules\batch_extensions\_template_utils.py" />
|
||||
<Compile Include="command_modules\azure-cli-batch-extensions\azure\cli\command_modules\batch_extensions\_validators.py" />
|
||||
<Compile Include="command_modules\azure-cli-batch-extensions\azure\cli\command_modules\batch_extensions\__init__.py" />
|
||||
<Compile Include="command_modules\azure-cli-batch-extensions\azure\cli\command_modules\__init__.py" />
|
||||
<Compile Include="command_modules\azure-cli-batch-extensions\azure\cli\__init__.py" />
|
||||
<Compile Include="command_modules\azure-cli-batch-extensions\azure\__init__.py" />
|
||||
<Compile Include="command_modules\azure-cli-batch-extensions\setup.py" />
|
||||
<Folder Include="command_modules\" />
|
||||
<Folder Include="command_modules\azure-cli-batch-extensions\" />
|
||||
<Folder Include="command_modules\azure-cli-batch-extensions\azure\" />
|
||||
<Folder Include="command_modules\azure-cli-batch-extensions\azure\cli\" />
|
||||
<Folder Include="command_modules\azure-cli-batch-extensions\azure\cli\command_modules\" />
|
||||
<Folder Include="command_modules\azure-cli-batch-extensions\azure\cli\command_modules\batch_extensions\" />
|
||||
<Folder Include="command_modules\azure-cli-batch-extensions\azure\cli\command_modules\batch_extensions\tests\" />
|
||||
<Folder Include="command_modules\azure-cli-batch-extensions\azure\cli\command_modules\batch_extensions\tests\data\" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Content Include="command_modules\azure-cli-batch-extensions\azure\cli\command_modules\batch_extensions\tests\data\batch-applicationTemplate-parameters.json" />
|
||||
<Content Include="command_modules\azure-cli-batch-extensions\azure\cli\command_modules\batch_extensions\tests\data\batch-applicationTemplate-prohibitedApplicationTemplateInfo.json" />
|
||||
<Content Include="command_modules\azure-cli-batch-extensions\azure\cli\command_modules\batch_extensions\tests\data\batch-applicationTemplate-prohibitedId.json" />
|
||||
<Content Include="command_modules\azure-cli-batch-extensions\azure\cli\command_modules\batch_extensions\tests\data\batch-applicationTemplate-prohibitedPoolInfo.json" />
|
||||
<Content Include="command_modules\azure-cli-batch-extensions\azure\cli\command_modules\batch_extensions\tests\data\batch-applicationTemplate-prohibitedPriority.json" />
|
||||
<Content Include="command_modules\azure-cli-batch-extensions\azure\cli\command_modules\batch_extensions\tests\data\batch-applicationTemplate-static.json" />
|
||||
<Content Include="command_modules\azure-cli-batch-extensions\azure\cli\command_modules\batch_extensions\tests\data\batch-applicationTemplate-unsupportedProperty.json" />
|
||||
<Content Include="command_modules\azure-cli-batch-extensions\azure\cli\command_modules\batch_extensions\tests\data\batch-applicationTemplate-untypedParameter.json" />
|
||||
<Content Include="command_modules\azure-cli-batch-extensions\azure\cli\command_modules\batch_extensions\tests\data\batch.job.parameters.json" />
|
||||
<Content Include="command_modules\azure-cli-batch-extensions\azure\cli\command_modules\batch_extensions\tests\data\batch.job.parametricsweep.json" />
|
||||
<Content Include="command_modules\azure-cli-batch-extensions\azure\cli\command_modules\batch_extensions\tests\data\batch.job.simple.json" />
|
||||
<Content Include="command_modules\azure-cli-batch-extensions\azure\cli\command_modules\batch_extensions\tests\data\batch.pool.parameters.json" />
|
||||
<Content Include="command_modules\azure-cli-batch-extensions\azure\cli\command_modules\batch_extensions\tests\data\batch.pool.simple.json" />
|
||||
<Content Include="command_modules\azure-cli-batch-extensions\azure\cli\command_modules\batch_extensions\tests\data\batch.shipyard.job.json" />
|
||||
<Content Include="command_modules\azure-cli-batch-extensions\azure\cli\command_modules\batch_extensions\tests\data\batch.shipyard.pool.json" />
|
||||
<Content Include="command_modules\azure-cli-batch-extensions\HISTORY.rst" />
|
||||
<Content Include="command_modules\azure-cli-batch-extensions\MANIFEST.in" />
|
||||
<Content Include="command_modules\azure-cli-batch-extensions\README.rst" />
|
||||
<Content Include="command_modules\azure-cli-batch-extensions\setup.cfg" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Interpreter Include="..\env\">
|
||||
<Id>{54f4b6dc-0859-46dc-99bb-b275c9d0aca3}</Id>
|
||||
<BaseInterpreter>{2af0f10d-7135-4994-9156-5d01c9c11b7e}</BaseInterpreter>
|
||||
<Version>3.5</Version>
|
||||
<Description>env (Python 3.5)</Description>
|
||||
<InterpreterPath>Scripts\python.exe</InterpreterPath>
|
||||
<WindowsInterpreterPath>Scripts\pythonw.exe</WindowsInterpreterPath>
|
||||
<LibraryPath>Lib\</LibraryPath>
|
||||
<PathEnvironmentVariable>PYTHONPATH</PathEnvironmentVariable>
|
||||
<Architecture>X86</Architecture>
|
||||
</Interpreter>
|
||||
</ItemGroup>
|
||||
<Import Project="$(PtvsTargetsFile)" Condition="Exists($(PtvsTargetsFile))" />
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.Common.targets" Condition="!Exists($(PtvsTargetsFile))" />
|
||||
</Project>
|
|
@ -0,0 +1,20 @@
|
|||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio 14
|
||||
VisualStudioVersion = 14.0.25420.1
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{888888A0-9F3D-457C-B088-3A5042F75D52}") = "azure-batch-cli-extensions", "azure-batch-cli-extensions.pyproj", "{938454F7-93BD-41A7-84B2-3C89D64B969D}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{938454F7-93BD-41A7-84B2-3C89D64B969D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{938454F7-93BD-41A7-84B2-3C89D64B969D}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
EndGlobal
|
3
pylintrc
3
pylintrc
|
@ -16,4 +16,5 @@ max-locals=25
|
|||
max-branches=20
|
||||
[SIMILARITIES]
|
||||
min-similarity-lines=10
|
||||
|
||||
[Master]
|
||||
init-hook='import sys; sys.path.extend(["./src/command_modules/azure-cli-batch-extensions/azure", "./env/Lib/site-packages/azure"])'
|
||||
|
|
|
@ -68,14 +68,6 @@ if __name__ == '__main__':
|
|||
# Run pylint on all modules
|
||||
return_code_sum = run_pylint(selected_modules)
|
||||
|
||||
# Run flake8 on white-listed modules
|
||||
pep8_ready_modules = automation_path.filter_user_selected_modules(
|
||||
['azure-cli', 'azure-cli-core', 'azure-cli-nspkg', 'acs',
|
||||
'component', 'cloud', 'feedback', 'profile', 'sql', 'storage',
|
||||
'vm'])
|
||||
|
||||
return_code_sum += run_pep8(pep8_ready_modules)
|
||||
|
||||
sys.exit(return_code_sum)
|
||||
|
||||
selected_modules = automation_path.filter_user_selected_modules(args.modules)
|
||||
|
|
|
@ -19,7 +19,7 @@ def get_repo_root():
|
|||
|
||||
def get_all_module_paths():
|
||||
"""List all core and command modules"""
|
||||
return list(get_core_modules_paths()) + get_command_modules_paths(include_prefix=True)
|
||||
return get_command_modules_paths(include_prefix=True)
|
||||
|
||||
|
||||
def get_command_modules_paths(include_prefix=False):
|
||||
|
@ -40,23 +40,6 @@ def get_command_modules_paths_with_tests():
|
|||
yield name, module_path, test_path
|
||||
|
||||
|
||||
def get_core_modules_paths():
|
||||
def _get_path(name):
|
||||
return os.path.join(get_repo_root(), 'src', name)
|
||||
|
||||
yield 'azure-cli', _get_path('azure-cli')
|
||||
yield 'azure-cli-core', _get_path('azure-cli-core')
|
||||
yield 'azure-cli-nspkg', _get_path('azure-cli-nspkg')
|
||||
|
||||
|
||||
def get_core_modules_paths_with_tests():
|
||||
# the pattern for the test folder here is not consistent with command modules'
|
||||
yield 'azure-cli', os.path.join(get_repo_root(), 'src', 'azure-cli'), \
|
||||
os.path.join(get_repo_root(), 'src', 'azure-cli', 'azure', 'cli', 'tests')
|
||||
yield 'azure-cli-core', os.path.join(get_repo_root(), 'src', 'azure-cli-core'), \
|
||||
os.path.join(get_repo_root(), 'src', 'azure-cli-core', 'azure', 'cli', 'core', 'tests')
|
||||
|
||||
|
||||
def make_dirs(path):
|
||||
"""Create a directories recursively"""
|
||||
import errno
|
||||
|
@ -96,8 +79,7 @@ def get_test_results_dir(with_timestamp=None, prefix=None):
|
|||
def filter_user_selected_modules(user_input_modules):
|
||||
import itertools
|
||||
|
||||
existing_modules = list(itertools.chain(get_core_modules_paths(),
|
||||
get_command_modules_paths()))
|
||||
existing_modules = list(itertools.chain(get_command_modules_paths()))
|
||||
|
||||
if user_input_modules:
|
||||
selected_modules = set(user_input_modules)
|
||||
|
@ -115,8 +97,7 @@ def filter_user_selected_modules(user_input_modules):
|
|||
def filter_user_selected_modules_with_tests(user_input_modules):
|
||||
import itertools
|
||||
|
||||
existing_modules = list(itertools.chain(get_core_modules_paths_with_tests(),
|
||||
get_command_modules_paths_with_tests()))
|
||||
existing_modules = list(itertools.chain(get_command_modules_paths_with_tests()))
|
||||
|
||||
if user_input_modules:
|
||||
selected_modules = set(user_input_modules)
|
||||
|
|
|
@ -28,6 +28,6 @@ print('Root directory \'{}\'\n'.format(root_dir))
|
|||
exec_command('pip install -r requirements.txt')
|
||||
|
||||
# install reference to extension module package
|
||||
exec_command('pip install -e src')
|
||||
exec_command('pip install -e src/command_modules/azure-cli-batch-extensions')
|
||||
|
||||
print('Finished dev setup.')
|
||||
|
|
|
@ -11,7 +11,7 @@ import datetime
|
|||
from azure.storage import CloudStorageAccount
|
||||
from azure.storage.blob import BlobPermissions
|
||||
from azure.batch.models import BatchErrorException
|
||||
from azure.cli.core.test_utils.vcr_test_base import VCRTestBase
|
||||
from azure.cli.command_modules.batch_extensions.tests.vcr_test_base import VCRTestBase
|
||||
|
||||
|
||||
class TestFileUpload(VCRTestBase):
|
|
@ -0,0 +1,522 @@
|
|||
# --------------------------------------------------------------------------------------------
|
||||
# Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
# Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
# --------------------------------------------------------------------------------------------
|
||||
|
||||
# pylint: disable=wrong-import-order
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
import collections
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import shlex
|
||||
import sys
|
||||
import tempfile
|
||||
from random import choice
|
||||
from string import digits, ascii_lowercase
|
||||
|
||||
from six.moves.urllib.parse import urlparse, parse_qs # pylint: disable=import-error
|
||||
|
||||
import unittest
|
||||
try:
|
||||
import unittest.mock as mock
|
||||
except ImportError:
|
||||
import mock
|
||||
|
||||
import vcr
|
||||
import jmespath
|
||||
from six import StringIO
|
||||
|
||||
# TODO Should not depend on azure.cli.main package here.
|
||||
# Will be ok if this test file is not part of azure.cli.core.utils
|
||||
from azure.cli.main import main as cli_main
|
||||
|
||||
from azure.cli.core import __version__ as core_version
|
||||
import azure.cli.core._debug as _debug
|
||||
from azure.cli.core._profile import Profile
|
||||
from azure.cli.core._util import CLIError, random_string
|
||||
|
||||
LIVE_TEST_CONTROL_ENV = 'AZURE_CLI_TEST_RUN_LIVE'
|
||||
COMMAND_COVERAGE_CONTROL_ENV = 'AZURE_CLI_TEST_COMMAND_COVERAGE'
|
||||
MOCKED_SUBSCRIPTION_ID = '00000000-0000-0000-0000-000000000000'
|
||||
MOCKED_TENANT_ID = '00000000-0000-0000-0000-000000000000'
|
||||
MOCKED_STORAGE_ACCOUNT = 'dummystorage'
|
||||
|
||||
|
||||
# MOCK METHODS
|
||||
|
||||
# Workaround until https://github.com/kevin1024/vcrpy/issues/293 is fixed.
|
||||
vcr_connection_request = vcr.stubs.VCRConnection.request
|
||||
|
||||
|
||||
def patch_vcr_connection_request(*args, **kwargs):
|
||||
kwargs.pop('encode_chunked', None)
|
||||
vcr_connection_request(*args, **kwargs)
|
||||
|
||||
|
||||
vcr.stubs.VCRConnection.request = patch_vcr_connection_request
|
||||
|
||||
|
||||
def _mock_get_mgmt_service_client(client_type, subscription_bound=True, subscription_id=None,
|
||||
api_version=None):
|
||||
# version of _get_mgmt_service_client to use when recording or playing tests
|
||||
profile = Profile()
|
||||
cred, subscription_id, _ = profile.get_login_credentials(subscription_id=subscription_id)
|
||||
if subscription_bound:
|
||||
client = client_type(cred, subscription_id, api_version=api_version) \
|
||||
if api_version else client_type(cred, subscription_id)
|
||||
else:
|
||||
client = client_type(cred, api_version=api_version) \
|
||||
if api_version else client_type(cred)
|
||||
|
||||
client = _debug.allow_debug_connection(client)
|
||||
|
||||
client.config.add_user_agent("AZURECLI/TEST/{}".format(core_version))
|
||||
|
||||
return (client, subscription_id)
|
||||
|
||||
|
||||
def _mock_generate_deployment_name(namespace):
|
||||
if not namespace.deployment_name:
|
||||
namespace.deployment_name = 'mock-deployment'
|
||||
|
||||
|
||||
def _mock_handle_exceptions(ex):
|
||||
raise ex
|
||||
|
||||
|
||||
def _mock_subscriptions(self): # pylint: disable=unused-argument
|
||||
return [{
|
||||
"id": MOCKED_SUBSCRIPTION_ID,
|
||||
"user": {
|
||||
"name": "example@example.com",
|
||||
"type": "user"
|
||||
},
|
||||
"state": "Enabled",
|
||||
"name": "Example",
|
||||
"tenantId": MOCKED_TENANT_ID,
|
||||
"isDefault": True}]
|
||||
|
||||
|
||||
def _mock_user_access_token(_, _1, _2, _3): # pylint: disable=unused-argument
|
||||
return ('Bearer', 'top-secret-token-for-you')
|
||||
|
||||
|
||||
def _mock_operation_delay(_):
|
||||
# don't run time.sleep()
|
||||
return
|
||||
|
||||
|
||||
# TEST CHECKS
|
||||
|
||||
|
||||
class JMESPathCheckAssertionError(AssertionError):
|
||||
def __init__(self, comparator, actual_result, json_data):
|
||||
message = "Actual value '{}' != Expected value '{}'. ".format(
|
||||
actual_result,
|
||||
comparator.expected_result)
|
||||
message += "Query '{}' used on json data '{}'".format(comparator.query, json_data)
|
||||
super(JMESPathCheckAssertionError, self).__init__(message)
|
||||
|
||||
|
||||
class JMESPathCheck(object): # pylint: disable=too-few-public-methods
|
||||
|
||||
def __init__(self, query, expected_result):
|
||||
self.query = query
|
||||
self.expected_result = expected_result
|
||||
|
||||
def compare(self, json_data):
|
||||
actual_result = _search_result_by_jmespath(json_data, self.query)
|
||||
if not actual_result == self.expected_result:
|
||||
raise JMESPathCheckAssertionError(self, actual_result, json_data)
|
||||
|
||||
|
||||
class JMESPathPatternCheck(object): # pylint: disable=too-few-public-methods
|
||||
|
||||
def __init__(self, query, expected_result):
|
||||
self.query = query
|
||||
self.expected_result = expected_result
|
||||
|
||||
def compare(self, json_data):
|
||||
actual_result = _search_result_by_jmespath(json_data, self.query)
|
||||
if not re.match(self.expected_result, str(actual_result), re.IGNORECASE):
|
||||
raise JMESPathCheckAssertionError(self, actual_result, json_data)
|
||||
|
||||
|
||||
class BooleanCheck(object): # pylint: disable=too-few-public-methods
|
||||
|
||||
def __init__(self, expected_result):
|
||||
self.expected_result = expected_result
|
||||
|
||||
def compare(self, data):
|
||||
result = str(str(data).lower() in ['yes', 'true', '1'])
|
||||
try:
|
||||
assert result == str(self.expected_result)
|
||||
except AssertionError:
|
||||
raise AssertionError("Actual value '{}' != Expected value {}".format(
|
||||
result, self.expected_result))
|
||||
|
||||
|
||||
class NoneCheck(object): # pylint: disable=too-few-public-methods
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def compare(self, data): # pylint: disable=no-self-use
|
||||
none_strings = ['[]', '{}', 'false']
|
||||
try:
|
||||
assert not data or data in none_strings
|
||||
except AssertionError:
|
||||
raise AssertionError("Actual value '{}' != Expected value falsy (None, '', []) or "
|
||||
"string in {}".format(data, none_strings))
|
||||
|
||||
|
||||
class StringCheck(object): # pylint: disable=too-few-public-methods
|
||||
|
||||
def __init__(self, expected_result):
|
||||
self.expected_result = expected_result
|
||||
|
||||
def compare(self, data):
|
||||
try:
|
||||
result = data.replace('"', '')
|
||||
assert result == self.expected_result
|
||||
except AssertionError:
|
||||
raise AssertionError("Actual value '{}' != Expected value {}".format(
|
||||
data, self.expected_result))
|
||||
|
||||
|
||||
# HELPER METHODS
|
||||
|
||||
|
||||
def _scrub_deployment_name(uri):
|
||||
return re.sub('/deployments/([^/?]+)', '/deployments/mock-deployment', uri)
|
||||
|
||||
|
||||
def _search_result_by_jmespath(json_data, query):
|
||||
if not json_data:
|
||||
json_data = '{}'
|
||||
json_val = json.loads(json_data)
|
||||
return jmespath.search(
|
||||
query,
|
||||
json_val,
|
||||
jmespath.Options(collections.OrderedDict))
|
||||
|
||||
|
||||
def _custom_request_matcher(r1, r2):
|
||||
""" Ensure method, path, and query parameters match. """
|
||||
if r1.method != r2.method:
|
||||
return False
|
||||
|
||||
url1 = urlparse(r1.uri)
|
||||
url2 = urlparse(r2.uri)
|
||||
|
||||
if url1.path != url2.path:
|
||||
return False
|
||||
|
||||
q1 = parse_qs(url1.query)
|
||||
q2 = parse_qs(url2.query)
|
||||
shared_keys = set(q1.keys()).intersection(set(q2.keys()))
|
||||
|
||||
if len(shared_keys) != len(q1) or len(shared_keys) != len(q2):
|
||||
return False
|
||||
|
||||
for key in shared_keys:
|
||||
if q1[key][0].lower() != q2[key][0].lower():
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
# MAIN CLASS
|
||||
|
||||
|
||||
class VCRTestBase(unittest.TestCase): # pylint: disable=too-many-instance-attributes
|
||||
|
||||
FILTER_HEADERS = [
|
||||
'authorization',
|
||||
'client-request-id',
|
||||
'x-ms-client-request-id',
|
||||
'x-ms-correlation-request-id',
|
||||
'x-ms-ratelimit-remaining-subscription-reads',
|
||||
'x-ms-request-id',
|
||||
'x-ms-routing-request-id',
|
||||
'x-ms-gateway-service-instanceid',
|
||||
'x-ms-ratelimit-remaining-tenant-reads',
|
||||
'x-ms-served-by',
|
||||
]
|
||||
|
||||
# pylint: disable=too-many-arguments
|
||||
def __init__(self, test_file, test_name, run_live=False, debug=False, debug_vcr=False,
|
||||
skip_setup=False, skip_teardown=False):
|
||||
super(VCRTestBase, self).__init__(test_name)
|
||||
self.test_name = test_name
|
||||
self.recording_dir = os.path.join(os.path.dirname(test_file), 'recordings')
|
||||
self.cassette_path = os.path.join(self.recording_dir, '{}.yaml'.format(test_name))
|
||||
self.playback = os.path.isfile(self.cassette_path)
|
||||
|
||||
if os.environ.get(LIVE_TEST_CONTROL_ENV, None) == 'True':
|
||||
self.run_live = True
|
||||
else:
|
||||
self.run_live = run_live
|
||||
|
||||
self.skip_setup = skip_setup
|
||||
self.skip_teardown = skip_teardown
|
||||
self.success = False
|
||||
self.exception = None
|
||||
self.track_commands = os.environ.get(COMMAND_COVERAGE_CONTROL_ENV, None)
|
||||
self._debug = debug
|
||||
|
||||
if not self.playback and ('--buffer' in sys.argv) and not run_live:
|
||||
self.exception = CLIError('No recorded result provided for {}.'.format(self.test_name))
|
||||
|
||||
if debug_vcr:
|
||||
import logging
|
||||
logging.basicConfig()
|
||||
vcr_log = logging.getLogger('vcr')
|
||||
vcr_log.setLevel(logging.INFO)
|
||||
self.my_vcr = vcr.VCR(
|
||||
cassette_library_dir=self.recording_dir,
|
||||
before_record_request=self._before_record_request,
|
||||
before_record_response=self._before_record_response,
|
||||
decode_compressed_response=True
|
||||
)
|
||||
self.my_vcr.register_matcher('custom', _custom_request_matcher)
|
||||
self.my_vcr.match_on = ['custom']
|
||||
|
||||
def _track_executed_commands(self, command):
|
||||
if self.track_commands:
|
||||
with open(self.track_commands, 'a+') as f:
|
||||
f.write(' '.join(command))
|
||||
f.write('\n')
|
||||
|
||||
def _before_record_request(self, request): # pylint: disable=no-self-use
|
||||
# scrub subscription from the uri
|
||||
request.uri = re.sub('/subscriptions/([^/]+)/',
|
||||
'/subscriptions/{}/'.format(MOCKED_SUBSCRIPTION_ID), request.uri)
|
||||
request.uri = re.sub('/graph.windows.net/([^/]+)/',
|
||||
'/graph.windows.net/{}/'.format(MOCKED_TENANT_ID), request.uri)
|
||||
request.uri = re.sub('/sig=([^/]+)&', '/sig=0000&', request.uri)
|
||||
request.uri = _scrub_deployment_name(request.uri)
|
||||
|
||||
# replace random storage account name with dummy name
|
||||
request.uri = re.sub(r'(vcrstorage[\d]+)', MOCKED_STORAGE_ACCOUNT, request.uri)
|
||||
# prevents URI mismatch between Python 2 and 3 if request URI has extra / chars
|
||||
request.uri = re.sub('//', '/', request.uri)
|
||||
request.uri = re.sub('/', '//', request.uri, count=1)
|
||||
# do not record requests sent for token refresh'
|
||||
if (request.body and 'grant-type=refresh_token' in str(request.body)) or \
|
||||
('/oauth2/token' in request.uri):
|
||||
request = None
|
||||
return request
|
||||
|
||||
def _before_record_response(self, response): # pylint: disable=no-self-use
|
||||
for key in VCRTestBase.FILTER_HEADERS:
|
||||
if key in response['headers']:
|
||||
del response['headers'][key]
|
||||
|
||||
def _scrub_body_parameters(value):
|
||||
value = re.sub('/subscriptions/([^/]+)/',
|
||||
'/subscriptions/{}/'.format(MOCKED_SUBSCRIPTION_ID), value)
|
||||
return value
|
||||
|
||||
for key in response['body']:
|
||||
value = response['body'][key].decode('utf-8')
|
||||
value = _scrub_body_parameters(value)
|
||||
try:
|
||||
response['body'][key] = bytes(value, 'utf-8')
|
||||
except TypeError:
|
||||
response['body'][key] = value.encode('utf-8')
|
||||
|
||||
return response
|
||||
|
||||
@mock.patch('azure.cli.main.handle_exception', _mock_handle_exceptions)
|
||||
@mock.patch('azure.cli.core.commands.client_factory._get_mgmt_service_client',
|
||||
_mock_get_mgmt_service_client) # pylint: disable=line-too-long
|
||||
def _execute_live_or_recording(self):
|
||||
# pylint: disable=no-member
|
||||
try:
|
||||
set_up = getattr(self, "set_up", None)
|
||||
if callable(set_up) and not self.skip_setup:
|
||||
self.set_up()
|
||||
|
||||
if self.run_live:
|
||||
self.body()
|
||||
else:
|
||||
with self.my_vcr.use_cassette(self.cassette_path):
|
||||
self.body()
|
||||
self.success = True
|
||||
except Exception as ex:
|
||||
raise ex
|
||||
finally:
|
||||
tear_down = getattr(self, "tear_down", None)
|
||||
if callable(tear_down) and not self.skip_teardown:
|
||||
self.tear_down()
|
||||
|
||||
@mock.patch('azure.cli.core._profile.Profile.load_cached_subscriptions', _mock_subscriptions)
|
||||
@mock.patch('azure.cli.core._profile.CredsCache.retrieve_token_for_user',
|
||||
_mock_user_access_token) # pylint: disable=line-too-long
|
||||
@mock.patch('azure.cli.main.handle_exception', _mock_handle_exceptions)
|
||||
@mock.patch('azure.cli.core.commands.client_factory._get_mgmt_service_client',
|
||||
_mock_get_mgmt_service_client) # pylint: disable=line-too-long
|
||||
@mock.patch('msrestazure.azure_operation.AzureOperationPoller._delay', _mock_operation_delay)
|
||||
@mock.patch('time.sleep', _mock_operation_delay)
|
||||
@mock.patch('azure.cli.core.commands.LongRunningOperation._delay', _mock_operation_delay)
|
||||
@mock.patch('azure.cli.core.commands.validators.generate_deployment_name',
|
||||
_mock_generate_deployment_name)
|
||||
def _execute_playback(self):
|
||||
# pylint: disable=no-member
|
||||
with self.my_vcr.use_cassette(self.cassette_path):
|
||||
self.body()
|
||||
self.success = True
|
||||
|
||||
def _post_recording_scrub(self):
|
||||
""" Perform post-recording cleanup on the YAML file that can't be accomplished with the
|
||||
VCR recording hooks. """
|
||||
src_path = self.cassette_path
|
||||
rg_name = getattr(self, 'resource_group', None)
|
||||
rg_original = getattr(self, 'resource_group_original', None)
|
||||
|
||||
t = tempfile.NamedTemporaryFile('r+')
|
||||
with open(src_path, 'r') as f:
|
||||
for line in f:
|
||||
# scrub resource group names
|
||||
if rg_name != rg_original:
|
||||
line = line.replace(rg_name, rg_original)
|
||||
# omit bearer tokens
|
||||
if 'authorization:' not in line.lower():
|
||||
t.write(line)
|
||||
t.seek(0)
|
||||
with open(src_path, 'w') as f:
|
||||
for line in t:
|
||||
f.write(line)
|
||||
t.close()
|
||||
|
||||
# COMMAND METHODS
|
||||
|
||||
def cmd(self, command, checks=None, allowed_exceptions=None,
|
||||
debug=False): # pylint: disable=no-self-use
|
||||
allowed_exceptions = allowed_exceptions or []
|
||||
if not isinstance(allowed_exceptions, list):
|
||||
allowed_exceptions = [allowed_exceptions]
|
||||
|
||||
if self._debug or debug:
|
||||
print('\n\tRUNNING: {}'.format(command))
|
||||
command_list = shlex.split(command)
|
||||
output = StringIO()
|
||||
try:
|
||||
cli_main(command_list, file=output)
|
||||
except Exception as ex: # pylint: disable=broad-except
|
||||
ex_msg = str(ex)
|
||||
if not next((x for x in allowed_exceptions if x in ex_msg), None):
|
||||
raise ex
|
||||
self._track_executed_commands(command_list)
|
||||
result = output.getvalue().strip()
|
||||
output.close()
|
||||
|
||||
if self._debug or debug:
|
||||
print('\tRESULT: {}\n'.format(result))
|
||||
|
||||
if checks:
|
||||
checks = [checks] if not isinstance(checks, list) else checks
|
||||
for check in checks:
|
||||
check.compare(result)
|
||||
|
||||
if '-o' in command_list and 'tsv' in command_list:
|
||||
return result
|
||||
else:
|
||||
try:
|
||||
result = result or '{}'
|
||||
return json.loads(result)
|
||||
except Exception: # pylint: disable=broad-except
|
||||
return result
|
||||
|
||||
def set_env(self, key, val): # pylint: disable=no-self-use
|
||||
os.environ[key] = val
|
||||
|
||||
def pop_env(self, key): # pylint: disable=no-self-use
|
||||
return os.environ.pop(key, None)
|
||||
|
||||
def execute(self):
|
||||
''' Method to actually start execution of the test. Must be called from the test_<name>
|
||||
method of the test class. '''
|
||||
try:
|
||||
if self.run_live:
|
||||
print('RUN LIVE: {}'.format(self.test_name))
|
||||
self._execute_live_or_recording()
|
||||
elif self.playback:
|
||||
print('PLAYBACK: {}'.format(self.test_name))
|
||||
self._execute_playback()
|
||||
else:
|
||||
print('RECORDING: {}'.format(self.test_name))
|
||||
self._execute_live_or_recording()
|
||||
except Exception as ex:
|
||||
raise ex
|
||||
finally:
|
||||
if not self.success and not self.playback and os.path.isfile(self.cassette_path):
|
||||
print('DISCARDING RECORDING: {}'.format(self.cassette_path))
|
||||
os.remove(self.cassette_path)
|
||||
elif self.success and not self.playback and os.path.isfile(self.cassette_path):
|
||||
try:
|
||||
self._post_recording_scrub()
|
||||
except Exception: # pylint: disable=broad-except
|
||||
os.remove(self.cassette_path)
|
||||
|
||||
|
||||
class ResourceGroupVCRTestBase(VCRTestBase):
|
||||
# pylint: disable=too-many-arguments
|
||||
|
||||
def __init__(self, test_file, test_name, resource_group='vcr_resource_group', run_live=False,
|
||||
debug=False, debug_vcr=False, skip_setup=False, skip_teardown=False):
|
||||
super(ResourceGroupVCRTestBase, self).__init__(test_file, test_name, run_live=run_live,
|
||||
debug=debug, debug_vcr=debug_vcr,
|
||||
skip_setup=skip_setup,
|
||||
skip_teardown=skip_teardown)
|
||||
self.resource_group_original = resource_group
|
||||
random_tag = '_{}_'.format(''.join((choice(ascii_lowercase + digits) for _ in range(4))))
|
||||
self.resource_group = '{}{}'.format(resource_group, '' if self.playback else random_tag)
|
||||
self.location = 'westus'
|
||||
|
||||
def set_up(self):
|
||||
self.cmd('group create --location {} --name {} --tags use=az-test'.format(
|
||||
self.location, self.resource_group))
|
||||
|
||||
def tear_down(self):
|
||||
self.cmd('group delete --name {} --no-wait --yes'.format(self.resource_group))
|
||||
|
||||
|
||||
class StorageAccountVCRTestBase(VCRTestBase):
|
||||
account_location = 'westus'
|
||||
account_sku = 'Standard_LRS'
|
||||
|
||||
# pylint: disable=too-many-arguments
|
||||
def __init__(self, test_file, test_name, resource_group='vcr_resource_group', run_live=False,
|
||||
debug=False, debug_vcr=False, skip_setup=False, skip_teardown=False):
|
||||
super(StorageAccountVCRTestBase, self).__init__(test_file, test_name, run_live=run_live,
|
||||
debug=debug, debug_vcr=debug_vcr,
|
||||
skip_setup=skip_setup,
|
||||
skip_teardown=skip_teardown)
|
||||
self.resource_group_original = resource_group
|
||||
self.resource_group = '{}{}'.format(resource_group,
|
||||
'' if self.playback else self.generate_random_tag())
|
||||
self.account = MOCKED_STORAGE_ACCOUNT if self.playback else self.generate_account_name()
|
||||
|
||||
def set_up(self):
|
||||
self.cmd('group create --location {} --name {} --tags use=az-test'.format(
|
||||
self.account_location, self.resource_group))
|
||||
self.cmd('storage account create --sku {} -l {} -n {} -g {}'.format(
|
||||
self.account_sku, self.account_location, self.account, self.resource_group))
|
||||
|
||||
def tear_down(self):
|
||||
self.cmd('storage account delete -g {} -n {} --yes'.format(
|
||||
self.resource_group, self.account))
|
||||
self.cmd('group delete --name {} --no-wait --yes'.format(self.resource_group))
|
||||
|
||||
@classmethod
|
||||
def generate_account_name(cls):
|
||||
return 'vcrstorage{}'.format(random_string(12, digits_only=True))
|
||||
|
||||
@classmethod
|
||||
def generate_random_tag(cls):
|
||||
return '_{}_'.format(random_string(4, force_lower=True))
|
Загрузка…
Ссылка в новой задаче