Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion drivers/customscript_shell/drivermetadata.xml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<Driver Description="" MainClass="driver.CustomScriptShellDriver" Name="Custom Script Shell Driver" Version="2.0.0" PythonVersion="3" >
<Driver Description="" MainClass="driver.CustomScriptShellDriver" Name="Custom Script Shell Driver" PythonVersion="3" Version="2.0.0">
<Layout>
<Category Name="General">
<Command Description="" DisplayName="Execute Script" EnableCancellation="true" Name="execute_script" Tags="allow_unreserved" />
Expand Down
11 changes: 8 additions & 3 deletions drivers/customscript_shellPackage/DataModel/datamodel.xml
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<DataModelInfo xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.qualisystems.com/ResourceManagement/DataModelSchema.xsd">
<Attributes>
<AttributeInfo Name="Execution Server Selector" Type="String" DefaultValue="" Description="This attribute points to a pre-defined group of execution servers. Usually used for different sites or domains." IsReadOnly="false">
</AttributeInfo>
<AttributeInfo Name="Timeout Minutes" Type="Numeric" DefaultValue="0" Description="Maximum number of minutes to connect to the target machine." IsReadOnly="false">
<AttributeInfo Name="Execution Server Selector" Type="String" DefaultValue="" Description="This attribute points to a pre-defined group of execution servers. Usually used for different sites or domains." IsReadOnly="false">
</AttributeInfo>
<AttributeInfo Name="Timeout Minutes" Type="Numeric" DefaultValue="0" Description="Maximum number of minutes to connect to the target machine." IsReadOnly="false">
</AttributeInfo>
<AttributeInfo Name="Verify Certificate" Type="String" DefaultValue="True"
Description="Verify server certificate when getting script if True, otherwise ignore." IsReadOnly="false">
</AttributeInfo>
</Attributes>
<ResourceFamilies>
Expand All @@ -20,10 +23,12 @@
<ResourceModel Name="Custom Script Configuration" Description="" SupportsConcurrentCommands="false">
<AttachedAttributes>
<AttachedAttribute Name="Timeout Minutes" IsLocal="true" IsOverridable="true"/>
<AttachedAttribute Name="Verify Certificate" IsLocal="true" IsOverridable="true" />
</AttachedAttributes>
<AttributeValues>
<AttributeValue Name="Execution Server Selector" Value="" />
<AttributeValue Name="Timeout Minutes" Value="20" />
<AttributeValue Name="Verify Certificate" Value="True" />
</AttributeValues>
<ParentModels />
<Drivers>
Expand Down
6 changes: 3 additions & 3 deletions package/cloudshell/cm/customscript/customscript_shell.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ def execute_script(self, command_context, script_conf_json, cancellation_context
output_writer = ReservationOutputWriter(api, command_context)

logger.info('Downloading file from \'%s\' ...' % script_conf.script_repo.url)
script_file = self._download_script(script_conf.script_repo, logger, cancel_sampler)
script_file = self._download_script(script_conf.script_repo, logger, cancel_sampler, script_conf.verify_certificate)
logger.info('Done (%s, %s chars).' % (script_file.name, len(script_file.text)))

service = ScriptExecutorSelector.get(script_conf.host_conf, logger, cancel_sampler)
Expand All @@ -54,7 +54,7 @@ def execute_script(self, command_context, script_conf_json, cancellation_context

service.execute(script_file, script_conf.host_conf.parameters, output_writer, script_conf.print_output)

def _download_script(self, script_repo, logger, cancel_sampler):
def _download_script(self, script_repo, logger, cancel_sampler, verify_certificate):
"""
:type script_repo: ScriptRepository
:type logger: Logger
Expand All @@ -65,7 +65,7 @@ def _download_script(self, script_repo, logger, cancel_sampler):
auth = None
if script_repo.username or script_repo.token:
auth = HttpAuth(script_repo.username, script_repo.password, script_repo.token)
return ScriptDownloader(logger, cancel_sampler).download(url, auth)
return ScriptDownloader(logger, cancel_sampler).download(url, auth, verify_certificate)

def _warn_for_unexpected_file_type(self, target_host, service, script_file, output_writer):
"""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ def __init__(self, script_repo = None, host_conf = None, timeout_minutes = None,
self.script_repo = script_repo or ScriptRepository()
self.host_conf = host_conf or HostConfiguration()
self.print_output = print_output
self.verify_certificate = True


class ScriptRepository(object):
Expand Down Expand Up @@ -55,6 +56,7 @@ def json_to_object(self, json_str):
script_conf = ScriptConfiguration()
script_conf.timeout_minutes = json_obj.get('timeoutMinutes', 0.0)
script_conf.print_output = bool_parse(json_obj.get('printOutput', True))
script_conf.verify_certificate = json_obj.get('verifyCertificate', 'true').lower()=='true'

repo = json_obj['repositoryDetails']
script_conf.script_repo.url = repo.get('url')
Expand Down
10 changes: 6 additions & 4 deletions package/cloudshell/cm/customscript/domain/script_downloader.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ def __init__(self, logger, cancel_sampler):
}


def download(self, url, auth):
def download(self, url, auth, verify_certificate):
"""
:type url: str
:type auth: HttpAuth
Expand All @@ -44,7 +44,9 @@ def download(self, url, auth):

# assume repo is public, try to download without credentials
self.logger.info("Starting download script as public...")
response = requests.get(url, auth=None, stream=True)
if not verify_certificate:
self.logger.info("Skipping server certificate")
response = requests.get(url, auth=None, stream=True, verify=verify_certificate)
response_valid = self._is_response_valid(response, "public")

if response_valid:
Expand All @@ -54,7 +56,7 @@ def download(self, url, auth):
if not response_valid and auth.token is not None:
self.logger.info("Token provided. Starting download script with Token...")
headers = {"Authorization": "Bearer %s" % auth.token }
response = requests.get(url, stream=True, headers=headers)
response = requests.get(url, stream=True, headers=headers, verify=verify_certificate)

response_valid = self._is_response_valid(response, "Token")

Expand All @@ -64,7 +66,7 @@ def download(self, url, auth):
# repo is private and credentials provided, and Token did not provided or did not work. this will NOT work for github. github require Token
if not response_valid and (auth.username is not None and auth.password is not None):
self.logger.info("username\password provided, Starting download script with username\password...")
response = requests.get(url, auth=(auth.username, auth.password) , stream=True)
response = requests.get(url, auth=(auth.username, auth.password) , stream=True, verify=verify_certificate)
file_name = self._get_filename(response)

response_valid = self._is_response_valid(response, "username\password")
Expand Down
2 changes: 1 addition & 1 deletion package/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
cloudshell-automation-api>=2021.1.0,<2021.2.1
cloudshell-automation-api>=2021.2.0,<2021.2.1
cloudshell-shell-core>=5.0.0,<6.0.0
cloudshell-core>=2.2.0,<2.3.0
pywinrm>=0.2.2
Expand Down
4 changes: 2 additions & 2 deletions package/tests/test_customscript_shell.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ def test_download_script_without_auth(self):

CustomScriptShell().execute_script(self.context, '', self.cancel_context)

self.downloader.assert_called_with('some url', None)
self.downloader.assert_called_with('some url', None, True)

def test_download_script_with_auth(self):
self.script_conf.script_repo.url = 'some url'
Expand All @@ -63,7 +63,7 @@ def test_download_script_with_auth(self):

CustomScriptShell().execute_script(self.context, '', self.cancel_context)

self.downloader.assert_called_with('some url', Any(lambda x: x.username == 'admin' and x.password=='1234'))
self.downloader.assert_called_with('some url', Any(lambda x: x.username == 'admin' and x.password=='1234'), True)

def test_selector_is_called_with_host_details(self):
CustomScriptShell().execute_script(self.context, '', self.cancel_context)
Expand Down
46 changes: 44 additions & 2 deletions package/tests/test_script_configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,48 @@ def test_cannot_parse_json_without_repository_details(self):
self.parser.json_to_object(json)
self.assertIn('Missing "repositoryDetails" node.', str(context.exception))

def test_verify_certificate_false(self):
json = """
{
"verifyCertificate": "nope",
"timeoutMinutes": 12.3,
"repositoryDetails" : {
"url": "B",
"username": "C",
"password": "D"
},
"hostsDetails": [{
"ip": "E",
"username": "F",
"password": "G",
"accessKey": "H",
"connectionMethod": "IiIiI"
}]
}"""
res = self.parser.json_to_object(json)
self.assertEqual(res.verify_certificate, False)

def test_verify_certificate_true(self):
json = """
{
"verifyCertificate": "TrUe",
"timeoutMinutes": 12.3,
"repositoryDetails" : {
"url": "B",
"username": "C",
"password": "D"
},
"hostsDetails": [{
"ip": "E",
"username": "F",
"password": "G",
"accessKey": "H",
"connectionMethod": "IiIiI"
}]
}"""
res = self.parser.json_to_object(json)
self.assertEqual(res.verify_certificate, True)

def test_cannot_parse_json_without_repository_url(self):
json = '{"repositoryDetails":{}}'
with self.assertRaises(SyntaxError) as context:
Expand Down Expand Up @@ -114,7 +156,7 @@ def wrapIt(x):
self.assertEqual("decrypted-G", conf.host_conf.password)
self.assertEqual("decrypted-H", conf.host_conf.access_key)
self.assertEqual("iiiii", conf.host_conf.connection_method)
self.assertCountEqual('K12', conf.host_conf.parameters['K11'])
self.assertCountEqual('K22', conf.host_conf.parameters['K21'])
self.assertEqual('K12', conf.host_conf.parameters['K11'])
self.assertEqual('K22', conf.host_conf.parameters['K21'])
self.api.DecryptPassword.assert_any_call('G')
self.api.DecryptPassword.assert_any_call('H')
6 changes: 3 additions & 3 deletions package/tests/test_script_downloader.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ def test_download_as_public(self, mock_requests):
# set downloaded and downaload
self.logger.info = print_logs
script_downloader = ScriptDownloader(self.logger, self.cancel_sampler)
script_file = script_downloader.download(public_repo_url, self.auth)
script_file = script_downloader.download(public_repo_url, self.auth, True)

# assert name and content
self.assertEqual(script_file.name, "bashScript.sh")
Expand All @@ -50,7 +50,7 @@ def test_download_as_private_with_token(self, mocked_requests_get):
# set downloaded and downaload
self.logger.info = print_logs
script_downloader = ScriptDownloader(self.logger, self.cancel_sampler)
script_file = script_downloader.download(private_repo_url, self.auth)
script_file = script_downloader.download(private_repo_url, self.auth, True)

# assert name and content
self.assertEqual(script_file.name, "bashScript.sh")
Expand All @@ -65,7 +65,7 @@ def test_download_as_private_with_credentials_and_failed_token(self, mocked_requ
# set downloaded and downaload
self.logger.info = print_logs
script_downloader = ScriptDownloader(self.logger, self.cancel_sampler)
script_file = script_downloader.download(private_repo_url, self.auth)
script_file = script_downloader.download(private_repo_url, self.auth, True)

# assert name and content
self.assertEqual(script_file.name, "bashScript.sh")
Expand Down