Skip to content

Commit b73a22a

Browse files
author
AdamSharon
authored
Merge pull request #97 from QualiSystems/wip/adam.s/add_token_179924
PBI 179924 - Support token authentication for github private repos in app configuration management
2 parents 318adf2 + 055d668 commit b73a22a

File tree

11 files changed

+128
-17
lines changed

11 files changed

+128
-17
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,6 @@ cloudshell_cm_ansible.egg-info/
99
package/cloudshell_cm_ansible.egg-info/
1010
drivers/ansible_shell.zip
1111
*.zip
12+
.vscode/
13+
venv
14+
dist/

drivers/ansible_shell/drivermetadata.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<Driver Description="" MainClass="driver.AnsibleShellDriver" Name="Ansible Shell Driver" Version="1.4.0">
1+
<Driver Description="" MainClass="driver.AnsibleShellDriver" Name="Ansible Shell Driver" Version="1.6.0">
22
<Layout>
33
<Category Name="General">
44
<Command Description="" DisplayName="Execute Playbook" EnableCancellation="true" Name="execute_playbook" Tags="allow_unreserved" />
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
cloudshell-shell-core>=3.1.0,<3.2.0
2-
cloudshell-cm-ansible>=1.5.1,<1.6.0
2+
cloudshell-cm-ansible>=1.6.0,<1.7.0

drivers/version.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
1.5.0
1+
1.6.0

package/cloudshell/cm/ansible/ansible_shell.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,8 +118,11 @@ def _download_playbook(self, ansi_conf, cancellation_sampler, logger):
118118
:rtype str
119119
"""
120120
repo = ansi_conf.playbook_repo
121-
auth = HttpAuth(repo.username, repo.password) if repo.username else None
121+
auth = None
122+
if ansi_conf.playbook_repo.username:
123+
auth = HttpAuth(repo.username, repo.password, repo.token)
122124
playbook_name = self.downloader.get(ansi_conf.playbook_repo.url, auth, logger, cancellation_sampler)
125+
logger.info('download playbook file' + str(playbook_name))
123126
return playbook_name
124127

125128
def _run_playbook(self, ansi_conf, playbook_name, output_writer, cancellation_sampler, logger):

package/cloudshell/cm/ansible/domain/ansible_configuration.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ def __init__(self):
2222
self.url = None
2323
self.username = None
2424
self.password = None
25+
self.token = None
2526

2627

2728
class HostConfiguration(object):
@@ -61,6 +62,7 @@ def json_to_object(self, json_str):
6162
ansi_conf.playbook_repo.url = json_obj['repositoryDetails'].get('url')
6263
ansi_conf.playbook_repo.username = json_obj['repositoryDetails'].get('username')
6364
ansi_conf.playbook_repo.password = json_obj['repositoryDetails'].get('password')
65+
ansi_conf.playbook_repo.token = json_obj['repositoryDetails'].get('token')
6466

6567
for json_host in json_obj.get('hostsDetails',[]):
6668
host_conf = HostConfiguration()

package/cloudshell/cm/ansible/domain/http_request_service.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,7 @@
33

44
class HttpRequestService(object):
55
def get_response(self, url, auth):
6-
return requests.get(url, auth=(auth.username, auth.password) if auth else None, stream=True)
6+
return requests.get(url, auth=(auth.username, auth.password) if auth else None, stream=True)
7+
8+
def get_response_with_headers(self, url, headers):
9+
return requests.get(url, headers=headers, stream=True)

package/cloudshell/cm/ansible/domain/playbook_downloader.py

Lines changed: 53 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
import os
22

33
from cloudshell.cm.ansible.domain.cancellation_sampler import CancellationSampler
4-
from file_system_service import FileSystemService
4+
from cloudshell.cm.ansible.domain.file_system_service import FileSystemService
55
from logging import Logger
66

77

88
class HttpAuth(object):
9-
def __init__(self, username, password):
9+
def __init__(self, username, password, token):
1010
self.username = username
1111
self.password = password
12-
12+
self.token = token
1313

1414
class PlaybookDownloader(object):
1515
CHUNK_SIZE = 1024 * 1024
@@ -50,9 +50,40 @@ def _download(self, url, auth, logger, cancel_sampler):
5050
:rtype [str,int]
5151
:return The downloaded file name
5252
"""
53-
logger.info('Downloading file from \'%s\' ...'%url)
53+
54+
response_valid = False
55+
56+
# assume repo is public, try to download without credentials
57+
logger.info('Starting download script as public... from \'%s\' ...'%url)
5458
response = self.http_request_service.get_response(url, auth)
55-
file_name = self.filename_extractor.get_filename(response)
59+
response_valid = self._is_response_valid(logger ,response, "public")
60+
61+
if response_valid:
62+
file_name = self.filename_extractor.get_filename(response)
63+
64+
# repo is private and token provided
65+
if not response_valid and auth.token is not None:
66+
logger.info("Token provided. Starting download script with Token...")
67+
headers = {"Authorization": "Bearer %s" % auth.token }
68+
response = self.http_request_service.get_response_with_headers(url, headers)
69+
70+
response_valid = self._is_response_valid(logger, response, "Token")
71+
72+
if response_valid:
73+
file_name = self.filename_extractor.get_filename(response)
74+
75+
# repo is private and credentials provided, and Token did not provided or did not work. this will NOT work for github. github require Token
76+
if not response_valid and (auth.username is not None and auth.password is not None):
77+
logger.info("username\password provided, Starting download script with username\password...")
78+
response = self.http_request_service.get_response(url, auth)
79+
80+
response_valid = self._is_response_valid(logger, response, "username\password")
81+
82+
if response_valid:
83+
file_name = self.filename_extractor.get_filename(response)
84+
85+
if not response_valid:
86+
raise Exception('Failed to download script file. please check the logs for more details.')
5687

5788
with self.file_system.create_file(file_name) as file:
5889
for chunk in response.iter_content(PlaybookDownloader.CHUNK_SIZE):
@@ -87,3 +118,20 @@ def _unzip(self, file_name, logger):
87118
logger.info('Found playbook: \'%s\' in zip file' % (playbook_name))
88119

89120
return playbook_name
121+
122+
def _is_response_valid(self, logger, response, request_method):
123+
try:
124+
self._validate_response(response)
125+
response_valid = True
126+
except Exception as ex:
127+
failure_message = "failed to Authorize repository with %s" % request_method
128+
logger.error(failure_message + " :" + str(ex))
129+
response_valid = False
130+
131+
return response_valid
132+
133+
134+
def _validate_response(self, response):
135+
if response.status_code < 200 or response.status_code > 300:
136+
raise Exception('Failed to download script file: '+str(response.status_code)+' '+response.reason+
137+
'. Please make sure the URL is valid, and the credentials are correct and necessary.')

package/tests/test_playbook_downloader.py

Lines changed: 57 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,59 +26,111 @@ def _set_extract_all_zip(self, files_to_create):
2626

2727
def test_playbook_downloader_zip_file_one_yaml(self):
2828
self.zip_service.extract_all = lambda zip_file_name: self._set_extract_all_zip(["lie.yaml"])
29-
auth = HttpAuth("user", "pass")
29+
auth = HttpAuth("user", "pass", "token")
3030
self.reqeust.url = "blabla/lie.zip"
3131
dic = dict([('content-disposition', 'lie.zip')])
3232
self.reqeust.headers = dic
3333
self.reqeust.iter_content.return_value = ''
3434
self.http_request_serivce.get_response=Mock(return_value=self.reqeust)
35+
self.http_request_serivce.get_response_with_headers = Mock(return_value=self.reqeust)
36+
self.playbook_downloader._is_response_valid = Mock(return_value=True)
37+
3538
file_name = self.playbook_downloader.get("", auth, self.logger, Mock())
3639
self.assertEquals(file_name, "lie.yaml")
3740

3841

3942
def test_playbook_downloader_zip_file_two_yaml_correct(self):
4043
self.zip_service.extract_all = lambda zip_file_name: self._set_extract_all_zip(["lie.yaml", "site.yaml"])
41-
auth = HttpAuth("user", "pass")
44+
auth = HttpAuth("user", "pass", "token")
4245
self.reqeust.url = "blabla/lie.zip"
4346
dic = dict([('content-disposition', 'lie.zip')])
4447
self.reqeust.headers = dic
4548
self.reqeust.iter_content.return_value = ''
4649
self.http_request_serivce.get_response = Mock(return_value=self.reqeust)
50+
self.http_request_serivce.get_response_with_headers = Mock(return_value=self.reqeust)
51+
self.playbook_downloader._is_response_valid = Mock(return_value=True)
52+
4753
file_name = self.playbook_downloader.get("", auth, self.logger, Mock())
54+
4855
self.assertEquals(file_name, "site.yaml")
4956

5057
def test_playbook_downloader_zip_file_two_yaml_incorrect(self):
5158
self.zip_service.extract_all = lambda zip_file_name: self._set_extract_all_zip(["lie.yaml", "lie2.yaml"])
52-
auth = HttpAuth("user", "pass")
59+
auth = HttpAuth("user", "pass", "token")
5360
self.reqeust.url = "blabla/lie.zip"
5461
dic = dict([('content-disposition', 'lie.zip')])
5562
self.reqeust.headers = dic
5663
self.reqeust.iter_content.return_value = ''
5764
self.http_request_serivce.get_response = Mock(return_value=self.reqeust)
65+
self.http_request_serivce.get_response_with_headers = Mock(return_value=self.reqeust)
66+
self.playbook_downloader._is_response_valid = Mock(return_value=True)
5867
with self.assertRaises(Exception) as e:
5968
self.playbook_downloader.get("", auth, self.logger, Mock())
6069
self.assertEqual(e.exception.message,"Playbook file name was not found in zip file")
6170

6271
def test_playbook_downloader_with_one_yaml(self):
63-
auth = HttpAuth("user", "pass")
72+
auth = HttpAuth("user", "pass", "token")
6473
self.reqeust.url = "blabla/lie.yaml"
6574
dic = dict([('content-disposition', 'lie.yaml')])
6675
self.reqeust.headers = dic
6776
self.reqeust.iter_content.return_value = 'hello'
6877
self.http_request_serivce.get_response = Mock(return_value=self.reqeust)
78+
self.http_request_serivce.get_response_with_headers = Mock(return_value=self.reqeust)
79+
self.playbook_downloader._is_response_valid = Mock(return_value=True)
6980

7081
file_name = self.playbook_downloader.get("", auth, self.logger, Mock())
7182

7283
self.assertEquals(file_name, "lie.yaml")
7384

7485
def test_playbook_downloader_no_parsing_from_rfc(self):
75-
auth = HttpAuth("user", "pass")
86+
auth = HttpAuth("user", "pass", "token")
7687
self.reqeust.url = "blabla/lie.yaml"
7788
dic = dict([('content-disposition', 'lie.yaml')])
7889
self.reqeust.headers = dic
7990
self.reqeust.iter_content.return_value = ''
8091
self.http_request_serivce.get_response = Mock(return_value=self.reqeust)
92+
self.http_request_serivce.get_response_with_headers = Mock(return_value=self.reqeust)
93+
self.playbook_downloader._is_response_valid = Mock(return_value=True)
94+
95+
file_name = self.playbook_downloader.get("", auth, self.logger, Mock())
96+
97+
self.assertEquals(file_name, "lie.yaml")
98+
99+
def test_playbook_downloader_with_one_yaml_only_credentials(self):
100+
auth = HttpAuth("user", "pass", None)
101+
self.reqeust.url = "blabla/lie.yaml"
102+
dic = dict([('content-disposition', 'lie.yaml')])
103+
self.reqeust.headers = dic
104+
self.reqeust.iter_content.return_value = 'hello'
105+
self.http_request_serivce.get_response = Mock(return_value=self.reqeust)
106+
self.http_request_serivce.get_response_with_headers = Mock(return_value=self.reqeust)
107+
self.playbook_downloader._is_response_valid = Mock(side_effect=self.mock_response_valid_for_credentials)
81108

82109
file_name = self.playbook_downloader.get("", auth, self.logger, Mock())
83110

84111
self.assertEquals(file_name, "lie.yaml")
112+
113+
def test_playbook_downloader_with_one_yaml_only_token(self):
114+
auth = HttpAuth(None, None, "Token")
115+
self.reqeust.url = "blabla/lie.yaml"
116+
dic = dict([('content-disposition', 'lie.yaml')])
117+
self.reqeust.headers = dic
118+
self.reqeust.iter_content.return_value = 'hello'
119+
self.http_request_serivce.get_response = Mock(return_value=self.reqeust)
120+
self.http_request_serivce.get_response_with_headers = Mock(return_value=self.reqeust)
121+
self.playbook_downloader._is_response_valid = Mock(side_effect=self.mock_response_valid_for_not_public)
122+
123+
file_name = self.playbook_downloader.get("", auth, self.logger, Mock())
124+
125+
self.assertEquals(file_name, "lie.yaml")
126+
127+
128+
129+
130+
# helpers method to mock the request according the request in order to test the right flow for Token\Cred
131+
def mock_response_valid_for_not_public(self, logger, response, request_method):
132+
return request_method != "public"
133+
134+
135+
def mock_response_valid_for_credentials(self, logger, response, request_method):
136+
return request_method == "username\password"

package/version.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
1.5.0
1+
1.6.0

0 commit comments

Comments
 (0)