Repository: StackStorm/st2

Issue:
request: option to skip duplicates when importing apikeys
It would be neat if I could import apikeys without being concerned that a key might already exist. That way using configuration management tools to deploy stackstorm idempotently would be less of a bother.

current result:
```
$ st2 apikey load apikeys.yaml
ERROR: 409 Client Error: Conflict
MESSAGE: Tried to save duplicate unique keys (E11000 duplicate key error collection: st2.api_key_d_b index: key_hash_1 dup key: { : "<snipped>" }) for url: http://127.0.0.1:9101/v1/apikeys
```

suggestion:
```
$ st2 apikey load apikeys.yaml --skip-duplicates
key 'xyz' already exists - skipped!
```

thanks for your consideration



Discussion:
Is there any temporary solution to this problem?
Load the keys one by one and ignore 409 errors.
Would also be worth looking into the behavior of the existing `st2 key load` command.

I believe `key load` doesn't throw on existing key (it just updates it), so if that's the case, we should update `apikey load` to behave the same, if possible.

@sammic30 

Edit: Now that I think of it - it might have something to do with `apikeys.yaml` file - can you please mask your private key and gist it? Also, how did you retrieve / create contents of this file? It's likely that it doesn't contain the ID / PK and that's why it throws.

It's the same story with `st2 key load` if file doesn't contain id for each key.
Update - I looked into it and yeah, it indeed looks like an issue with `st2 apikey load` command.

It throws on load if a duplicate entry is found while it shouldn't throw to be idempotent and consistent with `st2 key load`.

I will make the corresponding change to make it work. Thanks again for reporting this.

Resolving patch:
diff --git a/st2client/st2client/commands/auth.py b/st2client/st2client/commands/auth.py
--- a/st2client/st2client/commands/auth.py
+++ b/st2client/st2client/commands/auth.py
@@ -17,7 +17,9 @@
 import json
 import logging
 
+import requests
 from six.moves.configparser import ConfigParser
+from six.moves import http_client
 
 from st2client.base import BaseCLIApp
 from st2client import config_parser
@@ -339,14 +341,29 @@ def run(self, args, **kwargs):
         instances = []
         for res in resources:
             # pick only the meaningful properties.
-            instance = {
+            data = {
                 'user': res['user'],  # required
                 'key_hash': res['key_hash'],  # required
                 'metadata': res.get('metadata', {}),
                 'enabled': res.get('enabled', False)
             }
-            instance = self.resource.deserialize(instance)
-            instances.append(self.manager.create(instance, **kwargs))
+
+            if 'id' in res:
+                data['id'] = res['id']
+
+            instance = self.resource.deserialize(data)
+
+            try:
+                result = self.manager.update(instance, **kwargs)
+            except requests.exceptions.HTTPError as e:
+                if e.response.status_code == http_client.NOT_FOUND:
+                    instance = self.resource.deserialize(data)
+                    # Key doesn't exist yet, create it instead
+                    result = self.manager.create(instance, **kwargs)
+                else:
+                    raise e
+
+            instances.append(result)
         return instances
 
     def run_and_print(self, args, **kwargs):


Test patch: