Skip to content

Commit f19cd58

Browse files
authored
Merge pull request #13 from ikalnytskyi/pgservice
BREAKING CHANGE: don't set connection env vars
2 parents 3574bd5 + 75b3f77 commit f19cd58

File tree

4 files changed

+126
-26
lines changed

4 files changed

+126
-26
lines changed

.github/workflows/ci.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,9 @@ jobs:
3030
python3 -m pytest -vv test_action.py
3131
env:
3232
CONNECTION_URI: ${{ steps.postgres.outputs.connection-uri }}
33+
SERVICE_NAME: ${{ steps.postgres.outputs.service-name }}
3334
EXPECTED_CONNECTION_URI: postgresql://postgres:postgres@localhost:5432/postgres
35+
EXPECTED_SERVICE_NAME: postgres
3436
shell: bash
3537

3638
parametrized:
@@ -59,5 +61,7 @@ jobs:
5961
python3 -m pytest -vv test_action.py
6062
env:
6163
CONNECTION_URI: ${{ steps.postgres.outputs.connection-uri }}
64+
SERVICE_NAME: ${{ steps.postgres.outputs.service-name }}
6265
EXPECTED_CONNECTION_URI: postgresql://yoda:GrandMaster@localhost:34837/jedi_order
66+
EXPECTED_SERVICE_NAME: yoda
6367
shell: bash

README.md

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ key features:
2424
| Username | `postgres` |
2525
| Password | `postgres` |
2626
| Database | `postgres` |
27+
| Service | `postgres` |
2728

2829
#### User permissions
2930

@@ -53,7 +54,57 @@ steps:
5354

5455
- run: pytest -vv tests/
5556
env:
56-
DATABASE_URI: ${{ steps.postgres.outputs.connection-uri }}
57+
CONNECTION_STR: ${{ steps.postgres.outputs.connection-uri }}
58+
59+
- run: pytest -vv tests/
60+
env:
61+
CONNECTION_STR: service=${{ steps.postgres.outputs.service-name }}
62+
```
63+
64+
## Recipes
65+
66+
#### Create a new user w/ database via CLI
67+
68+
```yaml
69+
steps:
70+
- uses: ikalnytskyi/action-setup-postgres@v3
71+
72+
- run: |
73+
createuser myuser
74+
createdb --owner myuser mydatabase
75+
psql -c "ALTER USER myuser WITH PASSWORD 'mypassword'"
76+
77+
env:
78+
# This activates connection parameters for the superuser created by
79+
# the action in the step above. It's mandatory to set this before using
80+
# createuser/psql/etc tools.
81+
#
82+
# The service name is the same as the username (i.e. 'postgres') but
83+
# it's recommended to use action's output to get the name in order to
84+
# be forward compatible.
85+
PGSERVICE: ${{ steps.postgres.outputs.service-name }}
86+
shell: bash
87+
```
88+
89+
#### Create a new user w/ database via psycopg
90+
91+
```yaml
92+
steps:
93+
- uses: ikalnytskyi/action-setup-postgres@v3
94+
```
95+
96+
```python
97+
import psycopg
98+
99+
# 'postgres' is the username here, but it's recommended to use the
100+
# action's 'service-name' output parameter here.
101+
connection = psycopg.connect("service=postgres")
102+
103+
# CREATE/DROP USER statements don't work within transactions, and with
104+
# autocommit disabled transactions are created by psycopg automatically.
105+
connection.autocommit = True
106+
connection.execute(f"CREATE USER myuser WITH PASSWORD 'mypassword'")
107+
connection.execute(f"CREATE DATABASE mydatabase WITH OWNER 'myuser'")
57108
```
58109

59110
## Rationale

action.yml

Lines changed: 30 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,10 @@ inputs:
2424
outputs:
2525
connection-uri:
2626
description: The connection URI to connect to PostgreSQL.
27-
value: ${{ steps.connection-uri.outputs.value }}
27+
value: ${{ steps.set-outputs.outputs.connection-uri }}
28+
service-name:
29+
description: The service name with connection parameters.
30+
value: ${{ steps.set-outputs.outputs.service-name }}
2831
runs:
2932
using: composite
3033
steps:
@@ -71,21 +74,27 @@ runs:
7174
echo "port = ${{ inputs.port }}" >> "$PGDATA/postgresql.conf"
7275
pg_ctl start
7376
74-
# Set environment variables for PostgreSQL client applications [1] such
75-
# as 'psql' or 'createuser'.
77+
# Save required connection parameters for created superuser to the
78+
# connection service file [1]. This allows using these connection
79+
# parameters by setting 'PGSERVICE' environment variable or by
80+
# requesting them via connection string.
7681
#
77-
# PGHOST is required for Linux/macOS because we turned off unix sockets
78-
# and they use them by default.
82+
# HOST is required for Linux/macOS because these OS-es default to unix
83+
# sockets but we turned them off.
7984
#
80-
# PGPORT, PGUSER, PGPASSWORD and PGDATABASE are required because they
81-
# could be parametrized via action input parameters.
85+
# PORT, USER, PASSWORD and DBNAME are required because they could be
86+
# parametrized via action input parameters.
8287
#
83-
# [1] https://www.postgresql.org/docs/15/reference-client.html
84-
echo "PGHOST=localhost" >> $GITHUB_ENV
85-
echo "PGPORT=${{ inputs.port }}" >> $GITHUB_ENV
86-
echo "PGUSER=${{ inputs.username }}" >> $GITHUB_ENV
87-
echo "PGPASSWORD=${{ inputs.password }}" >> $GITHUB_ENV
88-
echo "PGDATABASE=${{ inputs.database }}" >> $GITHUB_ENV
88+
# [1] https://www.postgresql.org/docs/15/libpq-pgservice.html
89+
cat <<EOF > "$PGDATA/pg_service.conf"
90+
[${{ inputs.username }}]
91+
host=localhost
92+
port=${{ inputs.port }}
93+
user=${{ inputs.username }}
94+
password=${{ inputs.password }}
95+
dbname=${{ inputs.database }}
96+
EOF
97+
echo "PGSERVICEFILE=$PGDATA/pg_service.conf" >> $GITHUB_ENV
8998
shell: bash
9099

91100
- name: Setup PostgreSQL database
@@ -97,11 +106,15 @@ runs:
97106
if [ "${{ inputs.database }}" != "postgres" ]; then
98107
createdb -O "${{ inputs.username }}" "${{ inputs.database }}"
99108
fi
109+
env:
110+
PGSERVICE: ${{ inputs.username }}
100111
shell: bash
101112

102-
- name: Expose connection URI
113+
- name: Set action outputs
103114
run: |
104-
CONNECTION_URI="postgresql://${{ inputs.username }}:${{ inputs.password }}@localhost:${{inputs.port}}/${{ inputs.database }}"
105-
echo "value=$CONNECTION_URI" >> $GITHUB_OUTPUT
115+
CONNECTION_URI="postgresql://${{ inputs.username }}:${{ inputs.password }}@localhost:${{ inputs.port }}/${{ inputs.database }}"
116+
117+
echo "connection-uri=$CONNECTION_URI" >> $GITHUB_OUTPUT
118+
echo "service-name=${{ inputs.username }}" >> $GITHUB_OUTPUT
106119
shell: bash
107-
id: connection-uri
120+
id: set-outputs

test_action.py

Lines changed: 40 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,16 @@ def connection_uri() -> str:
2121
return connection_uri
2222

2323

24+
@pytest.fixture(scope="function")
25+
def service_name() -> str:
26+
"""Read and return connection URI from environment."""
27+
28+
service_name = os.getenv("SERVICE_NAME")
29+
if service_name is None:
30+
pytest.fail("SERVICE_NAME: environment variable is not set")
31+
return service_name
32+
33+
2434
@pytest.fixture(scope="function")
2535
def connection_factory() -> ConnectionFactory:
2636
"""Return 'psycopg.Connection' factory."""
@@ -30,19 +40,32 @@ def factory(connection_uri: str) -> psycopg.Connection:
3040
return factory
3141

3242

33-
@pytest.fixture(scope="function")
34-
def connection(connection_uri: str, connection_factory: ConnectionFactory) -> psycopg.Connection:
43+
@pytest.fixture(scope="function", params=["uri", "kv-string"])
44+
def connection(
45+
request: pytest.FixtureRequest,
46+
connection_factory: ConnectionFactory,
47+
connection_uri: str,
48+
service_name: str,
49+
) -> psycopg.Connection:
3550
"""Return 'psycopg.Connection' for connection URI set in environment."""
3651

37-
return connection_factory(connection_uri)
52+
if request.param == "uri":
53+
return connection_factory(connection_uri)
54+
elif request.param == "kv-string":
55+
return connection_factory(f"service={service_name}")
56+
raise RuntimeError("f{request.param}: unknown value")
3857

3958

40-
def test_connection_uri():
59+
def test_connection_uri(connection_uri):
4160
"""Test that CONNECTION_URI matches EXPECTED_CONNECTION_URI."""
4261

43-
connection_uri = os.getenv("CONNECTION_URI")
44-
expected_connection_uri = os.getenv("EXPECTED_CONNECTION_URI")
45-
assert connection_uri == expected_connection_uri
62+
assert connection_uri == os.getenv("EXPECTED_CONNECTION_URI")
63+
64+
65+
def test_service_name(service_name):
66+
"""Test that SERVICE_NAME matches EXPECTED_SERVICE_NAME."""
67+
68+
assert service_name == os.getenv("EXPECTED_SERVICE_NAME")
4669

4770

4871
def test_server_encoding(connection: psycopg.Connection):
@@ -146,9 +169,18 @@ def test_user_create_drop_user(
146169
connection.execute(f"DROP USER {username}")
147170

148171

149-
def test_client_applications(connection_factory: ConnectionFactory, connection_uri: str):
172+
def test_client_applications(
173+
connection_factory: ConnectionFactory,
174+
service_name: str,
175+
connection_uri: str,
176+
monkeypatch: pytest.MonkeyPatch,
177+
):
150178
"""Test that PostgreSQL client applications can be used."""
151179

180+
# Request connection parameters from the connection service file prepared
181+
# by our action.
182+
monkeypatch.setenv("PGSERVICE", service_name)
183+
152184
username = "us3rname"
153185
password = "passw0rd"
154186
database = "databas3"

0 commit comments

Comments
 (0)