Merge ~cjwatson/charm-postfix-relay:sender-dependent-relayhost-maps into charm-postfix-relay:master

Proposed by Colin Watson
Status: Merged
Approved by: Erhan Sunar
Approved revision: 634c68573b80b62c34413f9151e4e7f6365d8a7f
Merged at revision: b237249f9853b98eb83bbd0fa3476c299ff37ad5
Proposed branch: ~cjwatson/charm-postfix-relay:sender-dependent-relayhost-maps
Merge into: charm-postfix-relay:master
Diff against target: 168 lines (+95/-3)
7 files modified
dev/null (+0/-3)
src/config.yaml (+7/-0)
src/lib/charm/postfix/postfix_relay.py (+12/-0)
src/templates/main.cf (+3/-0)
src/templates/relayhost_map (+3/-0)
src/tests/unit/requirements.txt (+1/-0)
src/tests/unit/test_postfix_relay.py (+69/-0)
Reviewer Review Type Date Requested Status
Erhan Sunar (community) Approve
Tianqi Xiao (community) Approve
Robert Gildein Approve
🤖 prod-jenkaas-bootstack (community) continuous-integration Approve
Canonical BootStack Charmers Pending
Review via email: mp+438972@code.launchpad.net

This proposal supersedes a proposal from 2022-10-12.

Commit message

Add sender_dependent_relayhost_maps option

Description of the change

I'd like to use this charm in Launchpad deployments. We use sender-dependent relay hosts on at least some Launchpad machines in order to ensure that only messages with expected envelope senders are sent out to our authenticated relay, and this will let us reproduce the necessary configuration. I imagine it might have other use cases as well.

(Resubmitting without the previous prerequisite, since that's now landed; and I believe I've fixed the issues previously raised in review. Please re-review.)

To post a comment you must log in.
Revision history for this message
🤖 Canonical IS Merge Bot (canonical-is-mergebot) wrote : Posted in a previous version of this proposal

This merge proposal is being monitored by mergebot. Change the status to Approved to merge.

Revision history for this message
Tianqi Xiao (txiao) wrote : Posted in a previous version of this proposal

When adding new functions, I suggest creating corresponding unit tests for the best practice.

review: Needs Fixing
Revision history for this message
🤖 prod-jenkaas-bootstack (prod-jenkaas-bootstack) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
Erhan Sunar (esunar) wrote : Posted in a previous version of this proposal

It is failing on bionic tests, because it is unable to install on Bionic. Possibly https://github.com/juju/charm-tools/issues/638 Can you fix it?

review: Needs Fixing
Revision history for this message
Robert Gildein (rgildein) wrote : Posted in a previous version of this proposal

I agree with Tianqi, you should add unit tests for these changes.

IMO, bionic failing tests should be handled in separated MP.

review: Needs Fixing
Revision history for this message
🤖 Canonical IS Merge Bot (canonical-is-mergebot) wrote :

This merge proposal is being monitored by mergebot. Change the status to Approved to merge.

Revision history for this message
🤖 prod-jenkaas-bootstack (prod-jenkaas-bootstack) wrote :
review: Approve (continuous-integration)
Revision history for this message
Robert Gildein (rgildein) :
review: Approve
Revision history for this message
Tianqi Xiao (txiao) wrote :

LGTM. Thanks for the MP.

review: Approve
Revision history for this message
Erhan Sunar (esunar) wrote :

LGTM

review: Approve
Revision history for this message
🤖 Canonical IS Merge Bot (canonical-is-mergebot) wrote :

Change successfully merged at revision b237249f9853b98eb83bbd0fa3476c299ff37ad5

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/src/config.yaml b/src/config.yaml
2index a28cf40..b9aea05 100644
3--- a/src/config.yaml
4+++ b/src/config.yaml
5@@ -31,6 +31,13 @@ options:
6 An upstream mail relay, sometimes called "smart host". Enclose it
7 in square brackets to skip MX lookup.
8 type: string
9+ sender_dependent_relayhost_maps:
10+ default: ''
11+ description:
12+ A YAML-encoded list of (sender, relay host) pairs. If set,
13+ messages from each given sender will be sent via the given relay
14+ host.
15+ type: string
16 smtpd_helo_required:
17 default: True
18 description:
19diff --git a/src/lib/charm/postfix/postfix_relay.py b/src/lib/charm/postfix/postfix_relay.py
20index 1cbd45b..2ec90f5 100644
21--- a/src/lib/charm/postfix/postfix_relay.py
22+++ b/src/lib/charm/postfix/postfix_relay.py
23@@ -25,6 +25,7 @@ from jinja2 import Template
24 TEMPLATES = "templates/"
25 MAIN_CFG = "/etc/postfix/main.cf"
26 AUTH_CONFIG = "/etc/postfix/sasl_passwd"
27+RELAYHOST_MAP = "/etc/postfix/relayhost_map"
28 POSTFIX_CERTIFICATE_DIR = "/etc/postfix/"
29
30
31@@ -41,6 +42,8 @@ def write_configs():
32 # create domain rewriting rules
33 filename = "/etc/postfix/smtp_generic_maps.pcre"
34 create_rewrite_map(config("domain_rewrite_map"), filename)
35+ if config("sender_dependent_relayhost_maps"):
36+ write_relayhost_map()
37 restart_postfix()
38
39
40@@ -75,6 +78,15 @@ def create_rewrite_map(rewrite, filename):
41 f.close()
42
43
44+def write_relayhost_map():
45+ """Render a relayhost map."""
46+ template_path = os.path.join(TEMPLATES, "relayhost_map")
47+ with open(template_path) as template_file, open(RELAYHOST_MAP, "w+") as file:
48+ template = Template(template_file.read())
49+ file.write(template.render(PostfixContext()()))
50+ subprocess.check_call(["postmap", "hash:{}".format(RELAYHOST_MAP)])
51+
52+
53 def restart_postfix():
54 """Restart postfix."""
55 service_restart("postfix")
56diff --git a/src/templates/main.cf b/src/templates/main.cf
57index a1df893..966fec4 100644
58--- a/src/templates/main.cf
59+++ b/src/templates/main.cf
60@@ -29,6 +29,9 @@ myhostname = {{ config.myhostname }}
61 mynetworks = {{ config.mynetworks }}
62 mydestination = $myhostname, localhost.$mydomain, localhost
63 relayhost = {{ config.relayhost }}
64+{%- if sender_dependent_relayhost_maps %}
65+sender_dependent_relayhost_maps = hash:/etc/postfix/relayhost_map
66+{%- endif %}
67 smtpd_helo_required = {{ smtpd_helo_required }}
68 smtpd_recipient_restrictions = {{ config.smtpd_recipient_restrictions }}
69 smtpd_relay_restrictions = permit_mynetworks permit_sasl_authenticated defer_unauth_destination
70diff --git a/src/templates/relayhost_map b/src/templates/relayhost_map
71new file mode 100644
72index 0000000..6d77537
73--- /dev/null
74+++ b/src/templates/relayhost_map
75@@ -0,0 +1,3 @@
76+{% for sender, relayhost in config.sender_dependent_relayhost_maps -%}
77+{{ sender }} {{ relayhost }}
78+{% endfor -%}
79diff --git a/src/tests/unit/requirements.txt b/src/tests/unit/requirements.txt
80index e69de29..2bcb017 100644
81--- a/src/tests/unit/requirements.txt
82+++ b/src/tests/unit/requirements.txt
83@@ -0,0 +1 @@
84+systemfixtures
85diff --git a/src/tests/unit/test_example.py b/src/tests/unit/test_example.py
86deleted file mode 100644
87index 5cbe63d..0000000
88--- a/src/tests/unit/test_example.py
89+++ /dev/null
90@@ -1,3 +0,0 @@
91-def test_example():
92- """This test is to avoid collected 0 items in pytest."""
93- assert True
94diff --git a/src/tests/unit/test_postfix_relay.py b/src/tests/unit/test_postfix_relay.py
95new file mode 100644
96index 0000000..74cf14c
97--- /dev/null
98+++ b/src/tests/unit/test_postfix_relay.py
99@@ -0,0 +1,69 @@
100+"""Unit tests for charm.postfix.postfix_relay."""
101+
102+import os
103+import subprocess
104+from unittest import TestCase, mock
105+
106+from systemfixtures import FakeFilesystem, FakeProcesses
107+
108+from charm.postfix import postfix_relay
109+
110+
111+class WriteRelayhostMapTests(TestCase):
112+ @mock.patch("charm.postfix.postfix_relay.config")
113+ def test_single_sender(self, mock_config):
114+ mock_config.return_value = {
115+ "sender_dependent_relayhost_maps": [
116+ ["user@example.com", "relay.example.com"]
117+ ]
118+ }
119+ with FakeFilesystem() as fs, FakeProcesses() as processes:
120+ fs.add("/etc")
121+ os.makedirs("/etc/postfix")
122+ processes.add(lambda _: {}, name="postmap")
123+ postfix_relay.write_relayhost_map()
124+ with open("/etc/postfix/relayhost_map") as rmap:
125+ self.assertEqual("user@example.com relay.example.com\n", rmap.read())
126+ self.assertEqual(
127+ [["postmap", "hash:/etc/postfix/relayhost_map"]],
128+ [proc._args["args"] for proc in processes.procs],
129+ )
130+
131+ @mock.patch("charm.postfix.postfix_relay.config")
132+ def test_multiple_senders(self, mock_config):
133+ mock_config.return_value = {
134+ "sender_dependent_relayhost_maps": [
135+ ["user1@example.com", "relay1.example.com"],
136+ ["user2@example.com", "relay2.example.com"],
137+ ]
138+ }
139+ with FakeFilesystem() as fs, FakeProcesses() as processes:
140+ fs.add("/etc")
141+ os.makedirs("/etc/postfix")
142+ processes.add(lambda _: {}, name="postmap")
143+ postfix_relay.write_relayhost_map()
144+ with open("/etc/postfix/relayhost_map") as rmap:
145+ self.assertEqual(
146+ "user1@example.com relay1.example.com\n"
147+ "user2@example.com relay2.example.com\n",
148+ rmap.read(),
149+ )
150+ self.assertEqual(
151+ [["postmap", "hash:/etc/postfix/relayhost_map"]],
152+ [proc._args["args"] for proc in processes.procs],
153+ )
154+
155+ @mock.patch("charm.postfix.postfix_relay.config")
156+ def test_postmap_fails(self, mock_config):
157+ mock_config.return_value = {
158+ "sender_dependent_relayhost_maps": [
159+ ["user@example.com", "relay.example.com"]
160+ ]
161+ }
162+ with FakeFilesystem() as fs, FakeProcesses() as processes:
163+ fs.add("/etc")
164+ os.makedirs("/etc/postfix")
165+ processes.add(lambda _: {"returncode": 1}, name="postmap")
166+ self.assertRaises(
167+ subprocess.CalledProcessError, postfix_relay.write_relayhost_map
168+ )

Subscribers

People subscribed via source and target branches

to all changes: