From 6e7f124550df2a732a67e7cbcce57b421e73966d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Douglas=20Mendiz=C3=A1bal?= <douglas@redrobot.io>
Date: Wed, 19 Oct 2016 22:16:05 -0500
Subject: [PATCH 2/2] Add --file flag for secrets

This patch adds a --file (-F) flag to both 'secret store' and 'secret
get' commands to work around a cliff formatter limitation when dealing
with binary data.  This flag can be used to store a payload directly
from a file so that a user doesn't have to do any base64 juggling of
their data.

The flag can also be used to save a payload directly to a file so we can
avoid UTF-8 and ASCII encoding errors thrown by the cliff formatters
that expect text data.

Change-Id: I2b1447534719c5796d185051198dc35d0b2c60f0
---
 barbicanclient/barbican_cli/secrets.py | 74 ++++++++++++++++++++--------------
 1 file changed, 44 insertions(+), 30 deletions(-)

diff --git a/barbicanclient/barbican_cli/secrets.py b/barbicanclient/barbican_cli/secrets.py
index c3bd3ea..fcdb0a6 100644
--- a/barbicanclient/barbican_cli/secrets.py
+++ b/barbicanclient/barbican_cli/secrets.py
@@ -13,9 +13,12 @@
 """
 Command-line interface sub-commands related to secrets.
 """
+import os
+
 from cliff import command
 from cliff import lister
 from cliff import show
+import six
 
 from barbicanclient import secrets
 
@@ -33,35 +36,30 @@ class DeleteSecret(command.Command):
 
 
 class GetSecret(show.ShowOne):
-    """Retrieve a secret by providing its URI."""
+    """Retrieve a secret by providing its URI"""
 
     def get_parser(self, prog_name):
         parser = super(GetSecret, self).get_parser(prog_name)
         parser.add_argument('URI', help='The URI reference for the secret.')
-        parser.add_argument('--decrypt', '-d',
-                            help='if specified, retrieve the '
-                                 'unencrypted secret data; '
-                                 'the data type can be specified with '
-                                 '--payload-content-type.',
-                            action='store_true')
-        parser.add_argument('--payload', '-p',
-                            help='if specified, retrieve the '
-                                 'unencrypted secret data; '
-                                 'the data type can be specified with '
-                                 '--payload-content-type. If the user'
-                                 ' wishes to only retrieve the value of'
-                                 ' the payload they must add '
-                                 '"-f value" to format returning only'
-                                 ' the value of the payload',
-                            action='store_true')
+        payload_params = parser.add_mutually_exclusive_group(required=False)
+        payload_params.add_argument('--decrypt', '-d',
+                                    help='if specified, retrieve the '
+                                    'unencrypted secret data.',
+                                    action='store_true')
+        payload_params.add_argument('--payload', '-p',
+                                    help='if specified, retrieve the '
+                                    'unencrypted secret data.',
+                                    action='store_true')
+        payload_params.add_argument('--file', '-F', metavar='<filename>',
+                                    help='if specified, save the payload to a '
+                                         'new file with the given filename.')
         parser.add_argument('--payload_content_type', '-t',
-                            default='text/plain',
-                            help='the content type of the decrypted'
-                                 ' secret (default: %(default)s.')
+                            help='DEPRECATED: the content type of the '
+                                 'decrypted secret.')
         return parser
 
     def take_action(self, args):
-        if args.decrypt or args.payload:
+        if args.decrypt or args.payload or args.file:
             entity = self.app.client_manager.key_manager.secrets.get(
                 args.URI, args.payload_content_type)
             return (('payload',),
@@ -71,6 +69,18 @@ class GetSecret(show.ShowOne):
                 secret_ref=args.URI)
             return entity._get_formatted_entity()
 
+    def produce_output(self, parsed_args, column_names, data):
+        if parsed_args.file:
+            if os.path.exists(parsed_args.file):
+                raise ValueError("ERROR: file already exists.")
+            with open(parsed_args.file, 'wb') as f:
+                f.write(data[0])
+
+        else:
+            super(GetSecret, self).produce_output(
+                parsed_args, column_names, data
+            )
+
 
 class UpdateSecret(command.Command):
     """Update a secret with no payload in Barbican."""
@@ -130,19 +140,13 @@ class StoreSecret(show.ShowOne):
         parser = super(StoreSecret, self).get_parser(prog_name)
         parser.add_argument('--name', '-n',
                             help='a human-friendly name.')
-        parser.add_argument('--payload', '-p',
-                            help='the unencrypted secret; if provided, '
-                                 'you must also provide a '
-                                 'payload_content_type')
         parser.add_argument('--secret-type', '-s', default='opaque',
                             help='the secret type; must be one of symmetric, '
                                  'public, private, certificate, passphrase, '
                                  'opaque (default)')
         parser.add_argument('--payload-content-type', '-t',
-                            help='the type/format of the provided '
-                                 'secret data; "text/plain" is assumed to be '
-                                 'UTF-8; required when --payload is '
-                                 'supplied.')
+                            help='DEPRECATED: the type/format of the provided '
+                                 'secret data.')
         parser.add_argument('--payload-content-encoding', '-e',
                             help='required if --payload-content-type is '
                                  '"application/octet-stream".')
@@ -159,11 +163,21 @@ class StoreSecret(show.ShowOne):
         parser.add_argument('--expiration', '-x',
                             help='the expiration time for the secret in '
                                  'ISO 8601 format.')
+        payload_params = parser.add_mutually_exclusive_group(required=False)
+        payload_params.add_argument('--payload', '-p',
+                                    help='the unencrypted secret data.')
+        payload_params.add_argument('--file', '-F', metavar='<filename>',
+                                    help='file containing the secret payload')
         return parser
 
     def take_action(self, args):
+        data = None
+        if args.file:
+            with open(args.file, 'rb') as f:
+                data = f.read()
+
         entity = self.app.client_manager.key_manager.secrets.create(
-            name=args.name, payload=args.payload,
+            name=args.name, payload=args.payload or data,
             payload_content_type=args.payload_content_type,
             payload_content_encoding=args.payload_content_encoding,
             algorithm=args.algorithm, bit_length=args.bit_length,
-- 
2.8.4 (Apple Git-73)

