Last active
October 7, 2024 13:04
-
-
Save vlinevych/7174d7d491004bbcb83d99bb7f91b70f to your computer and use it in GitHub Desktop.
Import/export tool for consul key-value storage
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
""" | |
Import/Export tool for Consul Key-Value storage. | |
If your server has ACL enabled, | |
be sure to export CONSUL_HTTP_TOKEN env variable or use -t to provide a token | |
Usage: | |
Export KV storage decoding values | |
./consul-kv-tool.py export > file.json | |
Export a certain path, decoding values | |
./consul-kv-tool.py export --path "vault/staging" > file.json | |
Dump KV storage "as is", with base64 encoded values | |
./consul-kv-tool.py dump > file.json | |
Decode base64 in existing dump/backup: | |
./consul-kv-tool.py decode-dump -f file.json | |
Import KV from file | |
./consul-kv-tool.py import -f file.json | |
Import some part of a file | |
./consul-kv-tool.py import -f file.json --path "vault/staging" | |
""" | |
import json | |
import requests | |
import base64 | |
import sys | |
import argparse | |
import os | |
def download(server, token, consul_path=""): | |
url = server + "/v1/kv/" + consul_path | |
params = {"token": token, "recurse": 1} | |
try: | |
response = requests.get(url, params=params) | |
if response.status_code != 200: | |
raise Exception("HTTP code %s on GET %s" % (response.status_code, url)) | |
return response.json() | |
except Exception as e: | |
print("ERROR during download:", e, file=sys.stderr) | |
exit(1) | |
def process(downloaded_data): | |
# | |
# Some typical kv data probems are fixed here: | |
# | |
# - sometimes a 'directory' appears as a key with trailing "/" - we should just skip these keys, | |
# since 'directory' will be created once we put the keys in it. | |
# | |
# - sometimes a non-ascii chars like "eé" appear in values instrad of Null. | |
# These values are replaced with None. | |
# | |
result_obj = [] | |
for obj in downloaded_data: | |
try: | |
# Skip 'directory creation' keys | |
if obj['Key'][-1] == "/": | |
continue | |
# Decode only non-empty values | |
if obj['Value']: | |
obj['Value'] = base64.b64decode(obj['Value']) | |
obj['Value'] = str(obj['Value'].decode('utf-8')) | |
result_obj.append(obj) | |
# Replace values with bad chars with None (aka null) and continue | |
except UnicodeDecodeError as e: | |
print("WARNING: bad chars in", obj['Key'], ": ", e, file=sys.stderr) | |
obj['Value'] = None | |
result_obj.append(obj) | |
except Exception as e: | |
print ("ERROR during processing:", e, file=sys.stderr) | |
exit(1) | |
return result_obj | |
def read_from_file(file_path, consul_path=None): | |
try: | |
f = open(file_path, "r") | |
data = json.load(f) | |
f.close() | |
if consul_path: | |
result_obj = [] | |
for obj in data: | |
if consul_path in obj['Key']: | |
result_obj.append(obj) | |
data = result_obj | |
except Exception as e: | |
print ("ERROR reading file:", e, file=sys.stderr) | |
exit(1) | |
return data | |
def output(data): | |
print(json.dumps(data, sort_keys=True, indent=4, separators=(',', ': '))) | |
def upload(server, token, data): | |
params = {"token": token} | |
for obj in data: | |
try: | |
payload = obj['Value'] | |
url = server + "/v1/kv/" + obj['Key'] | |
response = requests.put(url, data=payload, params=params) | |
if response.status_code != 200: | |
raise Exception("HTTP code %s on PUT %s" % (response.status_code, url)) | |
except Exception as e: | |
print ("ERROR uploading:", e, file=sys.stderr) | |
exit(1) | |
if __name__ == "__main__": | |
parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) | |
parser.add_argument("action", type=str, choices=["export", "import", "dump", "decode-dump"], action="store", help="export or import") | |
parser.add_argument("--file", "-f", type=str, help="json file to read", metavar='') | |
parser.add_argument("--path", "-p", type=str, help="Consul kv path: 'vault/master/something/key'", default="", metavar='') | |
parser.add_argument("--server", "-s", type=str, help="Consul server, proto://url:port", default="http://localhost:8500", metavar='') | |
parser.add_argument("--token", "-t", type=str, help="Consul ACL token", metavar='') | |
args = parser.parse_args() | |
if args.token: | |
token = args.token | |
elif 'CONSUL_HTTP_TOKEN' in os.environ: | |
token = os.environ['CONSUL_HTTP_TOKEN'] | |
else: | |
print("Warning: no Consul token provided.", file=sys.stderr) | |
if args.action in ['import', 'decode-dump'] and not args.file: | |
print ("Provide a filename using -f parameter.", file=sys.stderr) | |
exit(1) | |
if args.action == 'export': | |
data = download(args.server, token, args.path) | |
data = process(data) | |
output(data) | |
elif args.action == 'import': | |
data = read_from_file(args.file, args.path) | |
upload(args.server, token, data) | |
elif args.action == 'dump': | |
data = download(args.server, token, args.path) | |
output(data) | |
elif args.action == 'decode-dump': | |
data = read_from_file(args.file, args.path) | |
data = process(data) | |
output(data) |
Thank you for the information and your help!!
Hi @vlinevich
I am getting some error while importing the KV as one of my value contains a special character ( ' ) and that's creating the issue. Can you please help to fix the issue?
Error:
ERROR uploading: 'latin-1' codec can't encode character '\u2019' in position 5735: Body ('â') is not valid Latin-1. Use body.encode('utf-8') if you want to send it encoded in UTF-8.
Hi,
Sorry for the late response.
The script expects your input file to be saved in UTF-8 encoding. Look up how to save it this way in your editor.
Hi @vlinevich
Thanks for your response. I managed to do that with following change into upload function.
payload = obj['Value'].encode('utf-8')
@vlinevych This helps lots, thanks
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
The script only works with key-value API. ACL is not supported.
Please note that recent consul releases have both kv import/export and ACL command-line commands:
https://www.consul.io/docs/commands/kv.html
https://www.consul.io/docs/commands/acl.html
.. so it does not make much sense to support this script in future.