Repository: aws/aws-cli

Issue:
Add ability to invalidate cloudfront assets
Using [s3cmd](http://s3tools.org/usage) I can write:

```
s3cmd -P --cf-invalidate put filename s3://bucket/path/filename
```

A similar option for `aws s3 cp/sync` would be helpful.



Discussion:
+1

+1

The workaround IIUC is `aws cloudfront create-invalidation`

```
aws cloudfront create-invalidation --distribution-id XXXXXXXXXXXXX \
--invalidation-batch "{ \"Paths\": { \"Quantity\": 1, \"Items\": [ \"/*\" ] }, \"CallerReference\": \"$(shell date +%s)\" }"
```

http://docs.aws.amazon.com/cli/latest/reference/cloudfront/create-invalidation.html

Which is _awkward_ to use.

I am now using the [cloudfront-invalidator command line](https://github.com/reidiculous/cloudfront-invalidator). It's crazy that this issue remains unresolved for aws-cli. No, I don't want to create JSON for a simple task.

+1

+1

:+1: 

Thanks for the feature request. Just want to let you know that we hear you and have added a note to our backlog to look into this.

+1

+1, that would be super awesome to use the official tool instead of s3cmd.


Resolving patch:
diff --git a/awscli/customizations/cloudfront.py b/awscli/customizations/cloudfront.py
new file mode 100644
--- /dev/null
+++ b/awscli/customizations/cloudfront.py
@@ -0,0 +1,51 @@
+# Copyright 2015 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"). You
+# may not use this file except in compliance with the License. A copy of
+# the License is located at
+#
+#     http://aws.amazon.com/apache2.0/
+#
+# or in the "license" file accompanying this file. This file is
+# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
+# ANY KIND, either express or implied. See the License for the specific
+# language governing permissions and limitations under the License.
+import time
+import random
+
+from awscli.arguments import CustomArgument
+from awscli.customizations.utils import validate_mutually_exclusive_handler
+
+
+def register(event_handler):
+    """Provides a simpler --paths for ``aws cloudfront create-invalidation``"""
+
+    event_handler.register(
+        'building-argument-table.cloudfront.create-invalidation', _add_paths)
+    event_handler.register(
+        'operation-args-parsed.cloudfront.create-invalidation',
+        validate_mutually_exclusive_handler(['invalidation_batch'], ['paths']))
+
+
+def _add_paths(argument_table, **kwargs):
+    argument_table['invalidation-batch'].required = False
+    argument_table['paths'] = PathsArgument()
+
+
+class PathsArgument(CustomArgument):
+
+    def __init__(self):
+        doc = (
+            'The space-separated paths to be invalidated.'
+            ' Note: --invalidation-batch and --paths are mututally exclusive.'
+        )
+        super(PathsArgument, self).__init__('paths', nargs='+', help_text=doc)
+
+    def add_to_params(self, parameters, value):
+        if value is not None:
+            caller_reference = 'cli-%s-%s' % (
+                int(time.time()), random.randint(1, 1000000))
+            parameters['InvalidationBatch'] = {
+                "CallerReference": caller_reference,
+                "Paths": {"Quantity": len(value), "Items": value},
+                }
diff --git a/awscli/handlers.py b/awscli/handlers.py
--- a/awscli/handlers.py
+++ b/awscli/handlers.py
@@ -33,6 +33,7 @@
 from awscli.customizations.rds import register_rds_modify_split
 from awscli.customizations.putmetricdata import register_put_metric_data
 from awscli.customizations.sessendemail import register_ses_send_email
+from awscli.customizations.cloudfront import register as cloudfront_register
 from awscli.customizations.iamvirtmfa import IAMVMFAWrapper
 from awscli.customizations.argrename import register_arg_renames
 from awscli.customizations.configure import register_configure_cmd
@@ -108,6 +109,7 @@ def awscli_initialize(event_handlers):
     register_rds_modify_split(event_handlers)
     register_put_metric_data(event_handlers)
     register_ses_send_email(event_handlers)
+    cloudfront_register(event_handlers)
     IAMVMFAWrapper(event_handlers)
     register_arg_renames(event_handlers)
     register_configure_cmd(event_handlers)


Test patch:
diff --git a/tests/functional/cloudfront/test_create_invalidation.py b/tests/functional/cloudfront/test_create_invalidation.py
new file mode 100644
--- /dev/null
+++ b/tests/functional/cloudfront/test_create_invalidation.py
@@ -0,0 +1,52 @@
+# Copyright 2015 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"). You
+# may not use this file except in compliance with the License. A copy of
+# the License is located at
+#
+#     http://aws.amazon.com/apache2.0/
+#
+# or in the "license" file accompanying this file. This file is
+# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
+# ANY KIND, either express or implied. See the License for the specific
+# language governing permissions and limitations under the License.
+import mock
+
+from awscli.testutils import BaseAWSPreviewCommandParamsTest as \
+    BaseAWSCommandParamsTest
+
+
+class TestCreateInvalidation(BaseAWSCommandParamsTest):
+
+    prefix = 'cloudfront create-invalidation --distribution-id my_id '
+
+    def test_invalidation_batch_only(self):
+        batch = "Paths={Quantity=2,Items=[foo.txt,bar.txt]},CallerReference=ab"
+        cmdline = self.prefix + '--invalidation-batch ' + batch
+        result = {
+            'DistributionId': 'my_id',
+            'InvalidationBatch': {
+                'Paths': {'Items': ['foo.txt', 'bar.txt'], 'Quantity': 2},
+                'CallerReference': 'ab',
+                },
+            }
+        self.assert_params_for_cmd(cmdline, result)
+
+    def test_paths_only(self):
+        cmdline = self.prefix + '--paths index.html foo.txt'
+        result = {
+            'DistributionId': 'my_id',
+            'InvalidationBatch': {
+                'Paths': {'Items': ['index.html', 'foo.txt'], 'Quantity': 2},
+                'CallerReference': mock.ANY,
+                },
+            }
+        self.run_cmd(cmdline)
+        self.assertEqual(self.last_kwargs, result)
+
+    def test_invalidation_batch_and_paths(self):
+        cmdline = self.prefix + '--invalidation-batch {} --paths foo'
+        self.run_cmd(cmdline, expected_rc=255)
+
+    def test_neither_invalidation_batch_or_paths(self):
+        self.run_cmd(self.prefix, expected_rc=255)