diff --git a/recipes/README.recipes.md b/recipes/README.recipes.md
index f1d6e5746..92b835116 100644
--- a/recipes/README.recipes.md
+++ b/recipes/README.recipes.md
@@ -640,7 +640,7 @@ PYTHON_VERSION_COMPATIBILITY: PY3
#### **class [GSUtilApi](/recipes/recipe_modules/gsutil/api.py#10)([RecipeApi][recipe_engine/wkt/RecipeApi]):**
-— **def [\_\_call\_\_](/recipes/recipe_modules/gsutil/api.py#21)(self, cmd, name=None, use_retry_wrapper=True, version=None, parallel_upload=False, multithreaded=False, infra_step=True, \*\*kwargs):**
+— **def [\_\_call\_\_](/recipes/recipe_modules/gsutil/api.py#21)(self, cmd, name=None, use_retry_wrapper=True, version=None, parallel_upload=False, multithreaded=False, infra_step=True, dry_run=False, \*\*kwargs):**
A step to run arbitrary gsutil commands.
@@ -657,10 +657,12 @@ Args:
options first (see 'gsutil help options').
* name (str) - Name of the step to use. Defaults to the first non-flag
token in the cmd.
+ * dry_run (bool): If True, don't actually run the step; just log what
+ the step would have been.
-— **def [cat](/recipes/recipe_modules/gsutil/api.py#115)(self, url, args=None, \*\*kwargs):**
+— **def [cat](/recipes/recipe_modules/gsutil/api.py#129)(self, url, args=None, \*\*kwargs):**
- **@contextlib.contextmanager**
— **def [configure\_gsutil](/recipes/recipe_modules/gsutil/api.py#169)(self, \*\*kwargs):**
+ **@contextlib.contextmanager**
— **def [configure\_gsutil](/recipes/recipe_modules/gsutil/api.py#183)(self, \*\*kwargs):**
Temporarily configures the behavior of gsutil.
@@ -672,23 +674,23 @@ possible configurations.
Args:
kwargs: Every keyword arg is treated as config line in the temp Boto file.
-— **def [copy](/recipes/recipe_modules/gsutil/api.py#129)(self, source_bucket, source, dest_bucket, dest, args=None, link_name='gsutil.copy', metadata=None, unauthenticated_url=False, \*\*kwargs):**
+— **def [copy](/recipes/recipe_modules/gsutil/api.py#143)(self, source_bucket, source, dest_bucket, dest, args=None, link_name='gsutil.copy', metadata=None, unauthenticated_url=False, \*\*kwargs):**
-— **def [download](/recipes/recipe_modules/gsutil/api.py#101)(self, bucket, source, dest, args=None, \*\*kwargs):**
+— **def [download](/recipes/recipe_modules/gsutil/api.py#115)(self, bucket, source, dest, args=None, \*\*kwargs):**
-— **def [download\_url](/recipes/recipe_modules/gsutil/api.py#108)(self, url, dest, args=None, \*\*kwargs):**
+— **def [download\_url](/recipes/recipe_modules/gsutil/api.py#122)(self, url, dest, args=None, \*\*kwargs):**
**@property**
— **def [gsutil\_py\_path](/recipes/recipe_modules/gsutil/api.py#17)(self):**
-— **def [list](/recipes/recipe_modules/gsutil/api.py#148)(self, url, args=None, \*\*kwargs):**
+— **def [list](/recipes/recipe_modules/gsutil/api.py#162)(self, url, args=None, \*\*kwargs):**
-— **def [remove\_url](/recipes/recipe_modules/gsutil/api.py#162)(self, url, args=None, \*\*kwargs):**
+— **def [remove\_url](/recipes/recipe_modules/gsutil/api.py#176)(self, url, args=None, \*\*kwargs):**
-— **def [signurl](/recipes/recipe_modules/gsutil/api.py#155)(self, private_key_file, bucket, dest, args=None, \*\*kwargs):**
+— **def [signurl](/recipes/recipe_modules/gsutil/api.py#169)(self, private_key_file, bucket, dest, args=None, \*\*kwargs):**
-— **def [stat](/recipes/recipe_modules/gsutil/api.py#122)(self, url, args=None, \*\*kwargs):**
+— **def [stat](/recipes/recipe_modules/gsutil/api.py#136)(self, url, args=None, \*\*kwargs):**
-— **def [upload](/recipes/recipe_modules/gsutil/api.py#84)(self, source, bucket, dest, args=None, link_name='gsutil.upload', metadata=None, unauthenticated_url=False, \*\*kwargs):**
+— **def [upload](/recipes/recipe_modules/gsutil/api.py#98)(self, source, bucket, dest, args=None, link_name='gsutil.upload', metadata=None, unauthenticated_url=False, \*\*kwargs):**
### *recipe_modules* / [osx\_sdk](/recipes/recipe_modules/osx_sdk)
[DEPS](/recipes/recipe_modules/osx_sdk/__init__.py#7): [recipe\_engine/cipd][recipe_engine/recipe_modules/cipd], [recipe\_engine/context][recipe_engine/recipe_modules/context], [recipe\_engine/json][recipe_engine/recipe_modules/json], [recipe\_engine/path][recipe_engine/recipe_modules/path], [recipe\_engine/platform][recipe_engine/recipe_modules/platform], [recipe\_engine/step][recipe_engine/recipe_modules/step], [recipe\_engine/version][recipe_engine/recipe_modules/version]
diff --git a/recipes/recipe_modules/gsutil/api.py b/recipes/recipe_modules/gsutil/api.py
index 33ab076fa..0202ba9b6 100644
--- a/recipes/recipe_modules/gsutil/api.py
+++ b/recipes/recipe_modules/gsutil/api.py
@@ -18,8 +18,15 @@ class GSUtilApi(recipe_api.RecipeApi):
def gsutil_py_path(self):
return self.repo_resource('gsutil.py')
- def __call__(self, cmd, name=None, use_retry_wrapper=True, version=None,
- parallel_upload=False, multithreaded=False, infra_step=True,
+ def __call__(self,
+ cmd,
+ name=None,
+ use_retry_wrapper=True,
+ version=None,
+ parallel_upload=False,
+ multithreaded=False,
+ infra_step=True,
+ dry_run=False,
**kwargs):
"""A step to run arbitrary gsutil commands.
@@ -36,6 +43,8 @@ class GSUtilApi(recipe_api.RecipeApi):
options first (see 'gsutil help options').
* name (str) - Name of the step to use. Defaults to the first non-flag
token in the cmd.
+ * dry_run (bool): If True, don't actually run the step; just log what
+ the step would have been.
"""
if name:
full_name = 'gsutil ' + name
@@ -79,6 +88,11 @@ class GSUtilApi(recipe_api.RecipeApi):
cmd_prefix.append('--')
exec_cmd = ['python3', '-u', gsutil_path] + cmd_prefix + cmd
+ if dry_run:
+ return self.m.step.empty(full_name,
+ step_text='Pretending to run gsutil command',
+ log_text=' '.join((str(i) for i in exec_cmd)),
+ log_name='command')
return self.m.step(full_name, exec_cmd, infra_step=infra_step, **kwargs)
def upload(self, source, bucket, dest, args=None, link_name='gsutil.upload',
diff --git a/recipes/recipe_modules/gsutil/examples/full.expected/basic.json b/recipes/recipe_modules/gsutil/examples/full.expected/basic.json
index af0ed5f0a..46573fdf4 100644
--- a/recipes/recipe_modules/gsutil/examples/full.expected/basic.json
+++ b/recipes/recipe_modules/gsutil/examples/full.expected/basic.json
@@ -241,6 +241,15 @@
"infra_step": true,
"name": "gsutil stat"
},
+ {
+ "cmd": [],
+ "name": "gsutil read remote file",
+ "~followup_annotations": [
+ "@@@STEP_TEXT@Pretending to run gsutil command@@@",
+ "@@@STEP_LOG_LINE@command@python3 -u RECIPE_MODULE[depot_tools::gsutil]/resources/gsutil_smart_retry.py -- RECIPE_REPO[depot_tools]/gsutil.py -m ---- cat gs://example/foo@@@",
+ "@@@STEP_LOG_END@command@@@"
+ ]
+ },
{
"name": "$result"
}
diff --git a/recipes/recipe_modules/gsutil/examples/full.py b/recipes/recipe_modules/gsutil/examples/full.py
index 501775034..9f524418b 100644
--- a/recipes/recipe_modules/gsutil/examples/full.py
+++ b/recipes/recipe_modules/gsutil/examples/full.py
@@ -90,5 +90,12 @@ def RunSteps(api):
api.gsutil.stat('gs://%s/foo' % bucket)
+ # Run in dry-run mode.
+ api.gsutil.cat('gs://%s/foo' % bucket,
+ name='read remote file',
+ multithreaded=True,
+ dry_run=True)
+
+
def GenTests(api):
yield api.test('basic')