Skip to content

Commit 6ab54e3

Browse files
authored
Add support for regen_derived command
* Add new command to help customers invalidate derived versions of transformations
1 parent c7edd2d commit 6ab54e3

File tree

3 files changed

+126
-1
lines changed

3 files changed

+126
-1
lines changed

cloudinary_cli/modules/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@
22
from .migrate import migrate
33
from .sync import sync
44
from .upload_dir import upload_dir
5+
from .regen_derived import regen_derived
56

67
commands = [
78
upload_dir,
89
make,
910
migrate,
1011
sync,
12+
regen_derived
1113
]
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
from click import command, argument, option
2+
from cloudinary_cli.utils.utils import print_help_and_exit
3+
from cloudinary_cli.utils.api_utils import handle_api_command, regen_derived_version
4+
from cloudinary import api
5+
from cloudinary_cli.utils.utils import confirm_action, run_tasks_concurrently
6+
from cloudinary_cli.defaults import logger
7+
8+
DEFAULT_MAX_RESULTS = 500
9+
10+
11+
@command("regen_derived",
12+
short_help="""Regenerate all derived assets pertaining \
13+
to a named transformation, or transformation string.""",
14+
help="""
15+
\b
16+
Regenerate all derived assets pertaining to a specific named transformation, or transformation string.
17+
Use this after updating a named transformation to invalidate and repopulate the cache with up-to-date versions of the assets.
18+
Format: cld regen_derived <transformation_name> <command options>
19+
e.g. cld regen_derived t_named -A -ea -enu http://mywebhook.com
20+
""")
21+
@argument("trans_str")
22+
@option("-enu", "--eager_notification_url", help="Webhook notification URL.")
23+
@option("-ea", "--eager_async", is_flag=True, default=False,
24+
help="Generate asynchronously.")
25+
@option("-A", "--auto_paginate", is_flag=True, default=False,
26+
help="Auto-paginate Admin API calls.")
27+
@option("-F", "--force", is_flag=True,
28+
help="Skip initial and auto-paginate confirmation.")
29+
@option("-n", "--max_results", nargs=1, default=10,
30+
help="""The maximum number of results to return.
31+
Default: 10, maximum: 500.""")
32+
@option("-w", "--concurrent_workers", type=int, default=30,
33+
help="Specify the number of concurrent network threads.")
34+
def regen_derived(trans_str, eager_notification_url,
35+
eager_async, auto_paginate, force,
36+
max_results, concurrent_workers):
37+
38+
if not any(trans_str):
39+
print_help_and_exit()
40+
41+
if not force:
42+
if not confirm_action(
43+
f"Running this module will explicity "
44+
f"re-generate all the related derived assets "
45+
f"which will cause an increase in your transformation costs "
46+
f"based on the number of derived assets re-generated.\n"
47+
f"If running in auto-paginate (-A) mode, "
48+
f"multiple Admin API (rate-limited) calls will be made.\n"
49+
f"Continue? (y/N)"):
50+
logger.info("Stopping.")
51+
exit()
52+
else:
53+
logger.info("Continuing. You may use the -F "
54+
"flag to skip confirmation.")
55+
56+
if auto_paginate:
57+
max_results = DEFAULT_MAX_RESULTS
58+
force = True
59+
60+
params = ('transformation', trans_str, f'max_results={max_results}')
61+
trans_details = handle_api_command(params, (), (), None, None, None,
62+
doc_url="", api_instance=api,
63+
api_name="admin",
64+
auto_paginate=auto_paginate,
65+
force=force, return_data=True)
66+
derived_resources = trans_details.get('derived')
67+
if not derived_resources:
68+
logger.info("No derived assets are using this transformation.")
69+
exit()
70+
71+
is_named = trans_details.get('named')
72+
eager_trans = normalise_trans_name(trans_str) if is_named else trans_str
73+
74+
progress_msg = f"Regenerating {len(derived_resources)} derived asset(s)"
75+
if eager_async:
76+
progress_msg += f" with eager_async={eager_async}"
77+
logger.info(f"{progress_msg}...")
78+
79+
regen_conc_list = []
80+
for derived in derived_resources:
81+
public_id = derived.get('public_id')
82+
delivery_type = derived.get('type')
83+
res_type = derived.get('resource_type')
84+
regen_conc_list.append((public_id, delivery_type, res_type,
85+
eager_trans, eager_async,
86+
eager_notification_url))
87+
88+
run_tasks_concurrently(regen_derived_version, regen_conc_list,
89+
concurrent_workers)
90+
complete_msg = ('Regeneration in progress'
91+
if eager_async else 'Regeneration complete')
92+
logger.info(f"{complete_msg}. It may take up to 10 mins "
93+
"to see the changes. Please contact support "
94+
"if you still see the old media.")
95+
return True
96+
97+
98+
def normalise_trans_name(trans_name):
99+
return trans_name if trans_name.startswith('t_') else 't_' + trans_name

cloudinary_cli/utils/api_utils.py

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,25 @@ def query_cld_folder(folder):
4848
return files
4949

5050

51+
def regen_derived_version(public_id, delivery_type, res_type,
52+
eager_trans, eager_async,
53+
eager_notification_url):
54+
options = {"type": delivery_type, "resource_type": res_type,
55+
"eager": eager_trans, "eager_async": eager_async,
56+
"eager_notification_url": eager_notification_url,
57+
"overwrite": True, "invalidate": True}
58+
try:
59+
exp_res = uploader.explicit(public_id, **options)
60+
derived_url = f'{exp_res.get("eager")[0].get("secure_url")}'
61+
msg = ('Processing' if options.get('eager_async') else 'Regenerated') + f' {derived_url}'
62+
logger.info(style(msg, fg="green"))
63+
except Exception as e:
64+
error_msg = (f"Failed to regenerate {public_id} of type: "
65+
f"{options.get('type')} and resource_type: "
66+
f"{options.get('resource_type')}")
67+
log_exception(e, error_msg)
68+
69+
5170
def upload_file(file_path, options, uploaded=None, failed=None):
5271
uploaded = uploaded if uploaded is not None else {}
5372
failed = failed if failed is not None else {}
@@ -157,10 +176,12 @@ def handle_api_command(
157176
api_name,
158177
auto_paginate=False,
159178
force=False,
160-
filter_fields=None):
179+
filter_fields=None,
180+
return_data=False):
161181
"""
162182
Used by Admin and Upload API commands
163183
"""
184+
164185
if doc:
165186
return launch(doc_url)
166187

@@ -185,6 +206,9 @@ def handle_api_command(
185206
if auto_paginate:
186207
res = handle_auto_pagination(res, func, args, kwargs, force, filter_fields)
187208

209+
if return_data:
210+
return res
211+
188212
print_json(res)
189213

190214
if save:

0 commit comments

Comments
 (0)