Skip to content

Commit 0457e7c

Browse files
sushantkhannaSushant Khanna
andauthored
Add banner suggesting migration to gcloud storage (GoogleCloudPlatform#1899)
Co-authored-by: Sushant Khanna <sushantkhanna@google.com>
1 parent ba96f09 commit 0457e7c

File tree

2 files changed

+176
-0
lines changed

2 files changed

+176
-0
lines changed

gslib/__main__.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -433,6 +433,29 @@ def main():
433433
if os.environ.get('_ARGCOMPLETE', '0') in ('1', '2'):
434434
return _PerformTabCompletion(command_runner)
435435

436+
# Check for suppression (interactive users can disable it)
437+
no_banner = os.environ.get('GSUTIL_NO_BANNER',
438+
'false').lower() in ('true', '1')
439+
440+
# Check for forced display (only for testing/verification)
441+
force_banner = os.environ.get('GSUTIL_TEST_FORCE_BANNER',
442+
'false').lower() in ('true', '1')
443+
444+
# Standard check: Is this an interactive TTY?
445+
is_tty = system_util.IsRunningInteractively()
446+
447+
# Show banner if:
448+
# 1. Not quiet mode (-q)
449+
# 2. Not explicitly suppressed (GSUTIL_NO_BANNER=true)
450+
# 3. AND (It's an interactive TTY OR We are forcing it for tests)
451+
if not quiet and not no_banner and (is_tty or force_banner):
452+
sys.stderr.write(
453+
'Google recommends using Gcloud storage CLI '
454+
'(https://docs.cloud.google.com/storage/docs/discover-object-storage-gcloud) '
455+
'instead of gsutil. Please refer to migration guide '
456+
'(https://docs.cloud.google.com/storage/docs/gsutil-transition-to-gcloud) '
457+
'for assistance.\n')
458+
436459
return _RunNamedCommandAndHandleExceptions(
437460
command_runner,
438461
command_name,

gslib/tests/test_gsutil.py

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@
2020
from __future__ import unicode_literals
2121

2222
import importlib
23+
import os
24+
import select
25+
import subprocess
26+
import sys
2327
import unittest
2428
from unittest import mock
2529
from google.auth import exceptions as google_auth_exceptions
@@ -56,6 +60,155 @@ def test_version_long(self):
5660
self.assertIn('gsutil path', stdout)
5761

5862

63+
class TestGsUtilBanner(testcase.GsUtilIntegrationTestCase):
64+
"""Integration tests for the deprecation banner."""
65+
66+
BANNER_TEXT = 'Google recommends using Gcloud storage CLI'
67+
68+
def test_banner_displayed_forced(self):
69+
# Tests require forcing the banner because subprocess execution is non-interactive.
70+
stderr = self.RunGsUtil(['version'],
71+
return_stderr=True,
72+
env_vars={'GSUTIL_TEST_FORCE_BANNER': 'true'})
73+
self.assertIn(self.BANNER_TEXT, stderr)
74+
75+
def test_banner_hidden_non_interactive_default(self):
76+
# Verify banner is HIDDEN by default in non-interactive (script) usage.
77+
stderr = self.RunGsUtil(['version'], return_stderr=True)
78+
self.assertNotIn(self.BANNER_TEXT, stderr)
79+
80+
def test_banner_suppressed_quiet(self):
81+
# Even if forced, -q should suppress it.
82+
stderr = self.RunGsUtil(['-q', 'version'],
83+
return_stderr=True,
84+
env_vars={'GSUTIL_TEST_FORCE_BANNER': 'true'})
85+
self.assertNotIn(self.BANNER_TEXT, stderr)
86+
87+
def test_banner_suppressed_env_var_true(self):
88+
# Even if forced for testing, GSUTIL_NO_BANNER=true takes precedence.
89+
stderr = self.RunGsUtil(['version'],
90+
return_stderr=True,
91+
env_vars={
92+
'GSUTIL_TEST_FORCE_BANNER': 'true',
93+
'GSUTIL_NO_BANNER': 'true'
94+
})
95+
self.assertNotIn(self.BANNER_TEXT, stderr)
96+
97+
def test_banner_suppressed_env_var_1(self):
98+
stderr = self.RunGsUtil(['version'],
99+
return_stderr=True,
100+
env_vars={
101+
'GSUTIL_TEST_FORCE_BANNER': 'true',
102+
'GSUTIL_NO_BANNER': '1'
103+
})
104+
self.assertNotIn(self.BANNER_TEXT, stderr)
105+
106+
def test_banner_displayed_env_var_false(self):
107+
# GSUTIL_NO_BANNER=false should allow banner if interactive/forced.
108+
stderr = self.RunGsUtil(['version'],
109+
return_stderr=True,
110+
env_vars={
111+
'GSUTIL_TEST_FORCE_BANNER': 'true',
112+
'GSUTIL_NO_BANNER': 'false'
113+
})
114+
self.assertIn(self.BANNER_TEXT, stderr)
115+
116+
def test_banner_displayed_on_failure(self):
117+
# Command failure (exit code 1) shouldn't prevent banner display
118+
# if it's forced/interactive.
119+
stderr = self.RunGsUtil(
120+
['ls', 'gs://non-existent-bucket-failure-test-12345'],
121+
return_stderr=True,
122+
expected_status=1,
123+
env_vars={'GSUTIL_TEST_FORCE_BANNER': 'true'})
124+
self.assertIn(self.BANNER_TEXT, stderr)
125+
126+
def get_terminal_output(self, master_fd, slave_fd, env, cmd):
127+
proc = subprocess.Popen([sys.executable] + cmd,
128+
stdout=slave_fd,
129+
stderr=slave_fd,
130+
stdin=slave_fd,
131+
env=env,
132+
close_fds=True)
133+
134+
output = b""
135+
while True:
136+
r, _, _ = select.select([master_fd], [], [], 2.0)
137+
if not r:
138+
if proc.poll() is not None:
139+
break
140+
continue
141+
try:
142+
chunk = os.read(master_fd, 1024)
143+
if not chunk:
144+
break
145+
output += chunk
146+
except OSError:
147+
break
148+
149+
proc.wait()
150+
return output
151+
152+
@unittest.skipIf(system_util.IS_WINDOWS, 'PTY not supported on Windows')
153+
def test_banner_displayed_interactive_tty(self):
154+
"""Verifies banner is displayed when running in a real PTY."""
155+
import pty
156+
master_fd, slave_fd = pty.openpty()
157+
try:
158+
cmd = [gslib.GSUTIL_PATH, 'version']
159+
env = os.environ.copy()
160+
# Ensure BOTO_CONFIG is passed through
161+
# Ensure no suppression env vars are set from the test runner environment
162+
if 'GSUTIL_NO_BANNER' in env:
163+
del env['GSUTIL_NO_BANNER']
164+
if 'GSUTIL_TEST_FORCE_BANNER' in env:
165+
del env['GSUTIL_TEST_FORCE_BANNER']
166+
167+
output = self.get_terminal_output(master_fd=master_fd, slave_fd=slave_fd, env=env, cmd=cmd)
168+
output_str = output.decode('utf-8', 'ignore')
169+
self.assertIn(self.BANNER_TEXT, output_str)
170+
finally:
171+
os.close(master_fd)
172+
os.close(slave_fd)
173+
174+
@unittest.skipIf(system_util.IS_WINDOWS, 'PTY not supported on Windows')
175+
def test_banner_suppressed_interactive_tty_quiet(self):
176+
import pty
177+
master_fd, slave_fd = pty.openpty()
178+
try:
179+
cmd = [gslib.GSUTIL_PATH, '-q', 'version']
180+
env = os.environ.copy()
181+
if 'GSUTIL_NO_BANNER' in env:
182+
del env['GSUTIL_NO_BANNER']
183+
if 'GSUTIL_TEST_FORCE_BANNER' in env:
184+
del env['GSUTIL_TEST_FORCE_BANNER']
185+
186+
output = self.get_terminal_output(master_fd=master_fd, slave_fd=slave_fd, env=env, cmd=cmd)
187+
output_str = output.decode('utf-8', 'ignore')
188+
self.assertNotIn(self.BANNER_TEXT, output_str)
189+
finally:
190+
os.close(master_fd)
191+
os.close(slave_fd)
192+
193+
@unittest.skipIf(system_util.IS_WINDOWS, 'PTY not supported on Windows')
194+
def test_banner_suppressed_interactive_tty_env_var(self):
195+
import pty
196+
master_fd, slave_fd = pty.openpty()
197+
try:
198+
cmd = [gslib.GSUTIL_PATH, 'version']
199+
env = os.environ.copy()
200+
if 'GSUTIL_TEST_FORCE_BANNER' in env:
201+
del env['GSUTIL_TEST_FORCE_BANNER']
202+
env['GSUTIL_NO_BANNER'] = 'true'
203+
204+
output = self.get_terminal_output(master_fd=master_fd, slave_fd=slave_fd, env=env, cmd=cmd)
205+
output_str = output.decode('utf-8', 'ignore')
206+
self.assertNotIn(self.BANNER_TEXT, output_str)
207+
finally:
208+
os.close(master_fd)
209+
os.close(slave_fd)
210+
211+
59212
class TestGsUtilUnit(testcase.GsUtilUnitTestCase):
60213
"""Unit tests for top-level gsutil command."""
61214

0 commit comments

Comments
 (0)