Skip to content

Commit fe7e108

Browse files
Celso Providelosergiusens
authored andcommitted
cli: implement enable-ci travis --refresh command (canonical#932)
This will allow users to refresh credentials bound to expire after 1 year (clock-ticking bomb) or more likely when there is a SSO password change (rotation). Travis 'deploy' phase will fail on expired credentials, so users will know when it's time to refresh. LP: #1646660
1 parent 6c01219 commit fe7e108

File tree

5 files changed

+226
-147
lines changed

5 files changed

+226
-147
lines changed

snapcraft/integrations/__init__.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
)
2828

2929

30-
def enable_ci(ci_system):
30+
def enable_ci(ci_system, refresh_only):
3131
if not ci_system:
3232
# XXX cprov 20161116: we could possibly auto-detect currently
3333
# integration systems in master ?
@@ -45,7 +45,9 @@ def enable_ci(ci_system):
4545
module = importlib.import_module(
4646
'snapcraft.integrations.{}'.format(ci_system))
4747

48-
print(module.__doc__)
49-
50-
if input('Continue (y/N): ') == 'y':
51-
module.enable()
48+
if refresh_only:
49+
module.refresh()
50+
else:
51+
print(module.__doc__)
52+
if input('Continue (y/N): ') == 'y':
53+
module.enable()

snapcraft/integrations/tests/test_travis.py

Lines changed: 90 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -45,25 +45,22 @@
4545
"""
4646

4747

48-
class TravisTestCase(tests.TestCase):
48+
class TravisPreconditionsTestCase(tests.TestCase):
4949

50-
def setUp(self):
51-
super().setUp()
52-
self.fake_logger = fixtures.FakeLogger(level=logging.DEBUG)
53-
self.useFixture(self.fake_logger)
54-
self.fake_terminal = tests.fixture_setup.FakeTerminal()
55-
self.useFixture(self.fake_terminal)
50+
scenarios = [
51+
('enable', {'entry_point': 'enable'}),
52+
('refresh', {'entry_point': 'refresh'}),
53+
]
5654

57-
def make_travis_yml(self, content):
58-
with open('.travis.yml', 'w') as fd:
59-
fd.write(content)
55+
def run_command(self, cmd):
56+
getattr(travis, cmd)()
6057

6158
@mock.patch('subprocess.check_call')
62-
def test_enable_missing_travis_cli(self, mock_check_call):
59+
def test_missing_travis_cli(self, mock_check_call):
6360
mock_check_call.side_effect = [FileNotFoundError()]
6461

6562
with self.assertRaises(RequiredCommandNotFound) as raised:
66-
travis.enable()
63+
self.run_command(self.entry_point)
6764

6865
self.assertEqual([
6966
'Travis CLI (`travis`) is not available.',
@@ -74,25 +71,26 @@ def test_enable_missing_travis_cli(self, mock_check_call):
7471
], str(raised.exception).splitlines())
7572

7673
@mock.patch('subprocess.check_call')
77-
def test_enable_broken_travis_cli(self, mock_check_call):
74+
def test_broken_travis_cli(self, mock_check_call):
7875
mock_check_call.side_effect = [
7976
subprocess.CalledProcessError(1, 'test')]
8077

8178
with self.assertRaises(RequiredCommandFailure) as raised:
82-
travis.enable()
79+
self.run_command(self.entry_point)
8380

8481
self.assertEqual([
85-
'Travis CLI (`travis version`) is not functional.',
82+
'Travis CLI (`travis settings`) is not functional or you are '
83+
'not allowed to access this repository settings.',
8684
'Make sure it works correctly in your system before trying this '
8785
'command again.',
8886
], str(raised.exception).splitlines())
8987

9088
@mock.patch('subprocess.check_call')
91-
def test_enable_missing_git(self, mock_check_call):
89+
def test_missing_git(self, mock_check_call):
9290
mock_check_call.side_effect = [None, FileNotFoundError()]
9391

9492
with self.assertRaises(RequiredCommandNotFound) as raised:
95-
travis.enable()
93+
self.run_command(self.entry_point)
9694

9795
self.assertEqual([
9896
'Git (`git`) is not available, this tool cannot verify its '
@@ -103,12 +101,12 @@ def test_enable_missing_git(self, mock_check_call):
103101
], str(raised.exception).splitlines())
104102

105103
@mock.patch('subprocess.check_call')
106-
def test_enable_broken_git_repo(self, mock_check_call):
104+
def test_broken_git_repo(self, mock_check_call):
107105
mock_check_call.side_effect = [
108106
None, subprocess.CalledProcessError(1, 'test')]
109107

110108
with self.assertRaises(RequiredCommandFailure) as raised:
111-
travis.enable()
109+
self.run_command(self.entry_point)
112110

113111
self.assertEqual([
114112
'The current directory is not a Git repository.',
@@ -117,18 +115,32 @@ def test_enable_broken_git_repo(self, mock_check_call):
117115
], str(raised.exception).splitlines())
118116

119117
@mock.patch('subprocess.check_call')
120-
def test_enable_missing_travis_setup(self, mock_check_call):
118+
def test_missing_travis_setup(self, mock_check_call):
121119
mock_check_call.side_effect = [None, None]
122120

123121
with self.assertRaises(RequiredPathDoesNotExist) as raised:
124-
travis.enable()
122+
self.run_command(self.entry_point)
125123

126124
self.assertEqual([
127125
'Travis project is not initialized for the current directory.',
128126
'Please initialize Travis project (e.g. `travis init`) with '
129127
'appropriate parameters.',
130128
], str(raised.exception).splitlines())
131129

130+
131+
class TravisSuccessfulTestCase(tests.TestCase):
132+
133+
def setUp(self):
134+
super().setUp()
135+
self.fake_logger = fixtures.FakeLogger(level=logging.DEBUG)
136+
self.useFixture(self.fake_logger)
137+
self.fake_terminal = tests.fixture_setup.FakeTerminal()
138+
self.useFixture(self.fake_terminal)
139+
140+
def make_travis_yml(self, content):
141+
with open('.travis.yml', 'w') as fd:
142+
fd.write(content)
143+
132144
@mock.patch('subprocess.check_output')
133145
@mock.patch('subprocess.check_call')
134146
@mock.patch('builtins.input')
@@ -188,8 +200,8 @@ def test_enable_successfully(
188200

189201
# Descriptive logging ...
190202
self.assertEqual([
191-
'Enabling Travis testbeds to push and release "foo" snaps '
192-
'to edge channel in series 16',
203+
"Enabling Travis testbeds to push and release 'foo' snaps "
204+
"to edge channel in series '16'",
193205
'Acquiring specific authorization information ...',
194206
'Login successful.',
195207
'Encrypting authorization for Travis and adjusting project '
@@ -201,3 +213,58 @@ def test_enable_successfully(
201213
'Also make sure you add the new '
202214
'`.snapcraft/travis_snapcraft.cfg` file.',
203215
], self.fake_logger.output.splitlines()[1:])
216+
217+
@mock.patch('subprocess.check_output')
218+
@mock.patch('subprocess.check_call')
219+
@mock.patch('builtins.input')
220+
@mock.patch('getpass.getpass')
221+
@mock.patch.object(storeapi.StoreClient, 'login')
222+
@mock.patch.object(storeapi.SCAClient, 'get_account_information')
223+
def test_refresh_successfully(
224+
self, mock_get_account_information, mock_login, mock_getpass,
225+
mock_input, mock_check_call, mock_check_output):
226+
mock_input.side_effect = ['[email protected]', '123456']
227+
mock_getpass.return_value = 'secret'
228+
mock_login.side_effect = [
229+
storeapi.errors.StoreTwoFactorAuthenticationRequired(), None]
230+
mock_get_account_information.return_value = {'account_id': 'abcd'}
231+
232+
mock_check_call.side_effect = [None, None]
233+
mock_check_output.side_effect = [None]
234+
self.make_snapcraft_yaml(test_snapcraft_yaml)
235+
self.make_travis_yml('after_success: ["<travis-cli-decrypt>"]')
236+
237+
travis.refresh()
238+
239+
# Attenuated credentials requested from the Store.
240+
mock_login.assert_called_with(
241+
'[email protected]', 'secret',
242+
one_time_password='123456', acls=None, save=False,
243+
channels=['edge'], packages=[{'series': '16', 'name': 'foo'}])
244+
245+
# Credentials encrypted with travis CLI.
246+
mock_check_output.assert_called_with(
247+
['travis', 'encrypt-file', '--force',
248+
'--add', 'after_success', '--decrypt-to',
249+
travis.LOCAL_CONFIG_FILENAME,
250+
mock.ANY, travis.ENCRYPTED_CONFIG_FILENAME],
251+
stderr=subprocess.PIPE)
252+
253+
# '.travis.yml' updated only with the decrypt command.
254+
with open('.travis.yml') as fd:
255+
travis_conf = yaml.load(fd)
256+
self.assertEqual([
257+
'<travis-cli-decrypt>',
258+
], travis_conf['after_success'])
259+
260+
# Descriptive logging ...
261+
self.assertEqual([
262+
'Refreshing credentials to push and release "foo" snaps to '
263+
'edge channel in series 16',
264+
'Acquiring specific authorization information ...',
265+
'Login successful.',
266+
'Encrypting authorization for Travis and adjusting project '
267+
'to automatically decrypt and use it during "after_success".',
268+
'Done. Please commit the changes to '
269+
'`.snapcraft/travis_snapcraft.cfg` file.',
270+
], self.fake_logger.output.splitlines()[1:])

0 commit comments

Comments
 (0)