Page MenuHomeDevCentral

D2249.id5698.diff
No OneTemporary

D2249.id5698.diff

diff --git a/_tests/Makefile b/_tests/Makefile
--- a/_tests/Makefile
+++ b/_tests/Makefile
@@ -1,4 +1,9 @@
-test:
+test: test-python test-bats
+
+test-python:
python -m unittest discover modules
python -m unittest discover pillar
python -m unittest discover scripts/python
+
+test-bats:
+ bats scripts/bats/test_edit_acme_dns_accounts.sh
diff --git a/_tests/data/acmedns-merged.json b/_tests/data/acmedns-merged.json
new file mode 100644
--- /dev/null
+++ b/_tests/data/acmedns-merged.json
@@ -0,0 +1,29 @@
+{
+ "nasqueron.org": {
+ "username": "082e20b6-83e2-4844-9dc8-95d625c6c8bb",
+ "password": "password1",
+ "fulldomain": "f65af502-9d34-402d-9a7e-c11529509b84.acme.nasqueron.org",
+ "subdomain": "f65af502-9d34-402d-9a7e-c11529509b84",
+ "allowFrom": [
+ "127.0.0.1"
+ ]
+ },
+ "foo.tld": {
+ "username": "9ec73be3-5556-11ea-9c51-0007cb03f249",
+ "password": "password2",
+ "fulldomain": "9f44bf1f-5556-11ea-9c51-0007cb03f249.acme.nasqueron.org",
+ "subdomain": "9f44bf1f-5556-11ea-9c51-0007cb03f249",
+ "allowFrom": [
+ "127.0.0.1"
+ ]
+ },
+ "bar.tld": {
+ "username": "c60f2111-5556-11ea-9c51-0007cb03f249",
+ "password": "password3",
+ "fulldomain": "c66a1b4c-5556-11ea-9c51-0007cb03f249.acme.nasqueron.org",
+ "subdomain": "c66a1b4c-5556-11ea-9c51-0007cb03f249",
+ "allowFrom": [
+ "127.0.0.1"
+ ]
+ }
+}
diff --git a/_tests/data/acmedns-toimport.json b/_tests/data/acmedns-toimport.json
new file mode 100644
--- /dev/null
+++ b/_tests/data/acmedns-toimport.json
@@ -0,0 +1,11 @@
+{
+ "bar.tld": {
+ "username": "c60f2111-5556-11ea-9c51-0007cb03f249",
+ "password": "password3",
+ "fulldomain": "c66a1b4c-5556-11ea-9c51-0007cb03f249.acme.nasqueron.org",
+ "subdomain": "c66a1b4c-5556-11ea-9c51-0007cb03f249",
+ "allowFrom": [
+ "127.0.0.1"
+ ]
+ }
+}
diff --git a/_tests/data/acmedns.json b/_tests/data/acmedns.json
new file mode 100644
--- /dev/null
+++ b/_tests/data/acmedns.json
@@ -0,0 +1,20 @@
+{
+ "nasqueron.org": {
+ "username": "082e20b6-83e2-4844-9dc8-95d625c6c8bb",
+ "password": "password1",
+ "fulldomain": "f65af502-9d34-402d-9a7e-c11529509b84.acme.nasqueron.org",
+ "subdomain": "f65af502-9d34-402d-9a7e-c11529509b84",
+ "allowFrom": [
+ "127.0.0.1"
+ ]
+ },
+ "foo.tld": {
+ "username": "9ec73be3-5556-11ea-9c51-0007cb03f249",
+ "password": "password2",
+ "fulldomain": "9f44bf1f-5556-11ea-9c51-0007cb03f249.acme.nasqueron.org",
+ "subdomain": "9f44bf1f-5556-11ea-9c51-0007cb03f249",
+ "allowFrom": [
+ "127.0.0.1"
+ ]
+ }
+}
diff --git a/_tests/scripts/bats/test_edit_acme_dns_accounts.sh b/_tests/scripts/bats/test_edit_acme_dns_accounts.sh
new file mode 100755
--- /dev/null
+++ b/_tests/scripts/bats/test_edit_acme_dns_accounts.sh
@@ -0,0 +1,55 @@
+#!/usr/bin/env bats
+
+SCRIPT="../roles/paas-docker/letsencrypt/files/edit-acme-dns-accounts.py"
+
+# -------------------------------------------------------------
+# Arguments parsing
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+@test "exit with error code if no arg provided" {
+ run $SCRIPT
+ [ "$status" -ne 0 ]
+}
+
+@test "exit with error code if command doesn't exist" {
+ run $SCRIPT somenonexistingcommand
+ [ "$status" -ne 0 ]
+}
+
+@test "exit with error code if no enough arg" {
+ run $SCRIPT import
+ [ "$status" -ne 0 ]
+}
+
+# -------------------------------------------------------------
+# Import
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+@test "can't import a file into itself" {
+ export ACME_ACCOUNTS=/dev/null
+ run $SCRIPT import /dev/null
+ [ "$output" = "You're trying to import /dev/null to itself" ]
+ [ "$status" -eq 2 ]
+}
+
+@test "can merge correctly two credentials files" {
+ ACME_ACCOUNTS=$(mktemp)
+ export ACME_ACCOUNTS
+ cp data/acmedns.json "$ACME_ACCOUNTS"
+
+ run $SCRIPT import data/acmedns-toimport.json
+ [ "$status" -eq 0 ]
+
+ isValid=0
+ run jsondiff "$ACME_ACCOUNTS" data/acmedns-merged.json
+ rm "$ACME_ACCOUNTS"
+ [ "$status" -eq 0 ]
+ [ "$output" = "{}" ] || isValid=1
+
+ if [ $isValid -ne 0 ]; then
+ echo "Non matching part according jsondiff:"
+ echo "$output"
+ fi
+
+ return $isValid
+}
diff --git a/_tests/scripts/python/test_edit_acme_dns_accounts.py b/_tests/scripts/python/test_edit_acme_dns_accounts.py
new file mode 100644
--- /dev/null
+++ b/_tests/scripts/python/test_edit_acme_dns_accounts.py
@@ -0,0 +1,48 @@
+from importlib.machinery import SourceFileLoader
+import os
+import unittest
+
+
+os.environ["ACME_ACCOUNTS"] = "/path/to/acmedns.json"
+
+path = "roles/paas-docker/letsencrypt/files/edit-acme-dns-accounts.py"
+script = SourceFileLoader('script', "../" + path).load_module()
+
+
+class TestInstance(unittest.TestCase):
+ def setUp(self):
+ self.testAccounts = script.AcmeAccounts("/dev/null")
+ pass
+
+ def test_read_path_from_environment(self):
+ self.assertEqual("/path/to/acmedns.json", script.get_acme_accounts_path())
+
+ def test_accounts_are_empty_on_init(self):
+ self.assertEqual({}, self.testAccounts.accounts)
+
+ def test_add(self):
+ self.testAccounts.add("foo.tld", {})
+ self.assertEqual(1, len(self.testAccounts.accounts))
+ self.assertIn("foo.tld", self.testAccounts.accounts)
+
+ def test_remove_existing(self):
+ self.testAccounts.add("foo.tld", {})
+ self.assertTrue(self.testAccounts.remove("foo.tld"))
+ self.assertEqual(0, len(self.testAccounts.accounts))
+
+ def test_remove_non_existing(self):
+ self.assertFalse(self.testAccounts.remove("not-existing.tld"))
+
+ def test_merge(self):
+ accounts_to_merge = script.AcmeAccounts("/dev/null") \
+ .add("bar.tld", {})
+
+ self.testAccounts \
+ .add("foo.tld", {}) \
+ .merge_with(accounts_to_merge)
+
+ self.assertEqual(2, len(self.testAccounts.accounts))
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/requirements.txt b/requirements.txt
--- a/requirements.txt
+++ b/requirements.txt
@@ -9,3 +9,4 @@
# Tests
mock>=2.0.0,<3.0
salt==2019.2.2
+jsondiff==1.2.0
diff --git a/roles/paas-docker/letsencrypt/files/edit-acme-dns-accounts.py b/roles/paas-docker/letsencrypt/files/edit-acme-dns-accounts.py
new file mode 100755
--- /dev/null
+++ b/roles/paas-docker/letsencrypt/files/edit-acme-dns-accounts.py
@@ -0,0 +1,111 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+# -------------------------------------------------------------
+# Let's encrypt — ACME DNS server accounts editor
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+# Project: Nasqueron
+# Created: 2020-02-22
+# Description: Edit /srv/letsencrypt/etc/acmedns.json to import
+# credentials for a specific subdomain to verify.
+# License: BSD-2-Clause
+# -------------------------------------------------------------
+
+
+import json
+import os
+import sys
+
+
+def get_acme_accounts_path():
+ try:
+ return os.environ["ACME_ACCOUNTS"]
+ except KeyError:
+ return "/srv/letsencrypt/etc/acmedns.json"
+
+
+ACME_ACCOUNTS_PATH = get_acme_accounts_path()
+
+
+class AcmeAccounts:
+ def __init__(self, path):
+ self.path = path
+ self.accounts = {}
+
+ def read_from_file(self):
+ with open(self.path) as fd:
+ self.accounts = json.load(fd)
+
+ return self
+
+ def write_to_file(self):
+ with open(self.path, "w") as fd:
+ json.dump(self.accounts, fd)
+
+ return self
+
+ def add(self, domain, account_parameters):
+ self.accounts[domain] = account_parameters
+
+ return self
+
+ def remove(self, domain):
+ try:
+ del self.accounts[domain]
+ return True
+ except KeyError:
+ return False
+
+ def merge_with(self, other_accounts: 'AcmeAccounts'):
+ self.accounts.update(other_accounts.accounts)
+
+ return self
+
+
+def usage():
+ print(f"Usage: {sys.argv[0]} <command> [parameters]", file=sys.stderr)
+ exit(1)
+
+
+def import_other_file(file_to_import):
+ if file_to_import == ACME_ACCOUNTS_PATH:
+ print(f"You're trying to import {ACME_ACCOUNTS_PATH} to itself")
+ exit(2)
+
+ accounts_to_import = AcmeAccounts(file_to_import).read_from_file()
+
+ AcmeAccounts(ACME_ACCOUNTS_PATH)\
+ .read_from_file()\
+ .merge_with(accounts_to_import)\
+ .write_to_file()
+
+
+commands = {
+ "import": {
+ "required_argc": 3,
+ "command_usage": "import <file>",
+ "callable": import_other_file
+ },
+},
+
+
+if __name__ == "__main__":
+ argc = len(sys.argv)
+
+ if argc < 2 or sys.argv[1] in ["-h", "--help", "/?", "/help"]:
+ usage()
+
+ command = sys.argv[1]
+
+ if command not in commands:
+ print(f"Unknown command: {command}", file=sys.stderr)
+ usage()
+
+ command = commands[command]
+
+ if argc < command["required_argc"]:
+ print(f"Usage: {sys.argv[0]} {command['command_usage']}", file=sys.stderr)
+ exit(1)
+
+ # We're good, time to invoke our command
+ command["callable"](*sys.argv[2:])
diff --git a/roles/paas-docker/letsencrypt/init.sls b/roles/paas-docker/letsencrypt/init.sls
--- a/roles/paas-docker/letsencrypt/init.sls
+++ b/roles/paas-docker/letsencrypt/init.sls
@@ -48,3 +48,8 @@
file.managed:
- source: salt://roles/paas-docker/letsencrypt/files/acme-dns-auth.py
- mode: 755
+
+/usr/local/bin/edit-acme-dns-accounts:
+ file.managed:
+ - source: salt://roles/paas-docker/letsencrypt/files/edit-acme-dns-accounts.py
+ - mode: 755

File Metadata

Mime Type
text/plain
Expires
Sun, Nov 24, 03:54 (4 h, 8 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
2259486
Default Alt Text
D2249.id5698.diff (9 KB)

Event Timeline