Skip to content

Commit 88ff92d

Browse files
committed
Add basic implementation
The 'setup-postgres' action has been developed by me for xsnippet/xsnippet-api [1] purpose where we want to run our tests on all major operating systems. Existing actions are built around Docker containers, and hence are available for Linux only. I figured that the work I have done there might be useful for someone else, and so I decided to create a standalone reusable action and publish it on GitHub Marketplace. [1] https://github.com/xsnippet/xsnippet-api/blob/40953d18b70e19d8535bf3aee88f28fe1407b36a/.github/actions/setup-postgres/action.yml
0 parents  commit 88ff92d

File tree

5 files changed

+278
-0
lines changed

5 files changed

+278
-0
lines changed

.github/workflows/ci.yml

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [ master ]
6+
pull_request:
7+
branches: [ master ]
8+
schedule:
9+
- cron: "0 8 * * *"
10+
11+
jobs:
12+
default:
13+
runs-on: ${{ matrix.os }}
14+
strategy:
15+
matrix:
16+
os:
17+
- ubuntu-latest
18+
- windows-latest
19+
- macos-latest
20+
steps:
21+
- uses: actions/checkout@v2
22+
23+
- name: Run setup-postgres
24+
uses: ./
25+
id: postgres
26+
27+
- name: Run tests
28+
run: |
29+
python3 -m pip install --upgrade pip pytest psycopg
30+
python3 -m pytest -vv test_action.py
31+
env:
32+
CONNECTION_URI: ${{ steps.postgres.outputs.connection-uri }}
33+
EXPECTED_CONNECTION_URI: postgresql://postgres:postgres@localhost/postgres
34+
shell: bash
35+
36+
parametrized:
37+
runs-on: ${{ matrix.os }}
38+
strategy:
39+
matrix:
40+
os:
41+
- ubuntu-latest
42+
- windows-latest
43+
- macos-latest
44+
steps:
45+
- uses: actions/checkout@v2
46+
47+
- name: Run setup-postgres
48+
uses: ./
49+
with:
50+
username: yoda
51+
password: GrandMaster
52+
database: jedi_order
53+
id: postgres
54+
55+
- name: Run tests
56+
run: |
57+
python3 -m pip install --upgrade pip pytest psycopg
58+
python3 -m pytest -vv test_action.py
59+
env:
60+
CONNECTION_URI: ${{ steps.postgres.outputs.connection-uri }}
61+
EXPECTED_CONNECTION_URI: postgresql://yoda:GrandMaster@localhost/jedi_order
62+
shell: bash

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2021 Ihor Kalnytskyi
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# setup-postgres
2+
3+
This action sets up a PostgreSQL server for the rest of the job. Here are some
4+
key features:
5+
6+
* Runs on Linux, macOS and Windows runners.
7+
* Adds PostgreSQL [binaries][1] (e.g. `psql`) to `PATH`.
8+
* Uses PostgreSQL installed in [GitHub Actions Virtual Environments][2].
9+
* [Easy to check][3] that IT DOES NOT contain malicious code.
10+
11+
[1]: https://www.postgresql.org/docs/current/reference-client.html
12+
[2]: https://github.com/actions/virtual-environments
13+
[3]: action.yml
14+
15+
## Usage
16+
17+
| Key | Value |
18+
|----------|-----------------------------------------------------|
19+
| URI | `postgresql://postgres:postgres@localhost/postgres` |
20+
| Host | `localhost` |
21+
| Port | `5432` |
22+
| Username | `postgres` |
23+
| Password | `postgres` |
24+
| Database | `postgres` |
25+
26+
#### Basic
27+
28+
```yaml
29+
steps:
30+
- uses: ikalnytskyi/action-setup-postgres@v1
31+
```
32+
33+
#### Advanced
34+
35+
```yaml
36+
steps:
37+
- uses: ikalnytskyi/action-setup-postgres@v1
38+
with:
39+
username: ci
40+
password: sw0rdfish
41+
database: test
42+
id: postgres
43+
44+
- run: pytest -vv tests/
45+
env:
46+
DATABASE_URI: ${{ steps.postgres.outputs.connection-uri }}
47+
```
48+
49+
## License
50+
51+
The scripts and documentation in this project are released under the
52+
[MIT License](LICENSE).

action.yml

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
name: Setup PostgreSQL for Linux/macOS/Windows
2+
author: Ihor Kalnytskyi
3+
description: Setup a preinstalled PostgreSQL server.
4+
branding:
5+
icon: database
6+
color: purple
7+
inputs:
8+
username:
9+
description: The username of the user to setup.
10+
default: postgres
11+
required: false
12+
password:
13+
description: The password of the user to setup.
14+
default: postgres
15+
required: false
16+
database:
17+
description: The database name to setup and grant permissions to created user.
18+
default: postgres
19+
required: false
20+
outputs:
21+
connection-uri:
22+
description: The connection URI to connect to PostgreSQL.
23+
value: ${{ steps.connection-uri.outputs.value }}
24+
runs:
25+
using: composite
26+
steps:
27+
- name: Prerequisites
28+
run: |
29+
if [ "$RUNNER_OS" == "Linux" ]; then
30+
echo "$(pg_config --bindir)" >> $GITHUB_PATH
31+
elif [ "$RUNNER_OS" == "Windows" ]; then
32+
echo "$PGBIN" >> $GITHUB_PATH
33+
echo "PQ_LIB_DIR=$PGROOT\lib" >> $GITHUB_ENV
34+
fi
35+
shell: bash
36+
37+
38+
- name: Setup and start PostgreSQL
39+
run: |
40+
export PGDATA="$RUNNER_TEMP/pgdata"
41+
pg_ctl init
42+
43+
# Forbid creating unix sockets since they are created by default in the
44+
# directory we don't have permissions to.
45+
echo "unix_socket_directories = ''" >> "$PGDATA/postgresql.conf"
46+
pg_ctl start
47+
48+
# Both PGHOST and PGUSER are used by PostgreSQL tooling such as 'psql'
49+
# or 'createuser'. Since PostgreSQL data has been resetup, we cannot
50+
# rely on defaults anymore.
51+
#
52+
# PGHOST is required for Linux and macOS since they default to unix
53+
# sockets, and we have turned them off.
54+
#
55+
# PGUSER is required for Windows since default the tooling's default
56+
# user is 'postgres', while 'pg_ctl init' creates one with the name of
57+
# the current user.
58+
echo "PGHOST=localhost" >> $GITHUB_ENV
59+
echo "PGUSER=${USER:-$USERNAME}" >> $GITHUB_ENV
60+
shell: bash
61+
62+
- name: Setup PostgreSQL user and database
63+
run: |
64+
createuser --createdb ${{ inputs.username }}
65+
66+
if [ "${{ inputs.database}}" != "postgres" ]; then
67+
createdb -O ${{ inputs.username }} ${{ inputs.database }}
68+
fi
69+
70+
psql -c "ALTER USER ${{ inputs.username }} PASSWORD '${{ inputs.password }}';" ${{ inputs.database }}
71+
shell: bash
72+
73+
- name: Expose connection URI
74+
run: |
75+
CONNECTION_URI="postgresql://${{ inputs.username }}:${{ inputs.password }}@localhost/${{ inputs.database }}"
76+
echo ::set-output name=value::$CONNECTION_URI
77+
shell: bash
78+
id: connection-uri

test_action.py

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import typing as t
2+
import os
3+
4+
import psycopg
5+
import pytest
6+
7+
8+
@pytest.fixture(scope="function")
9+
def connection_factory() -> t.Callable[[str], psycopg.Connection]:
10+
def factory(connection_uri: str) -> psycopg.Connection:
11+
return psycopg.connect(connection_uri)
12+
return factory
13+
14+
15+
@pytest.fixture(scope="function")
16+
def connection(connection_factory) -> psycopg.Connection:
17+
return connection_factory(os.getenv("CONNECTION_URI"))
18+
19+
20+
def test_connection_uri():
21+
"""Test that CONNECTION_URI matches EXPECTED_CONNECTION_URI."""
22+
23+
connection_uri = os.getenv("CONNECTION_URI")
24+
expected_connection_uri = os.getenv("EXPECTED_CONNECTION_URI")
25+
assert connection_uri == expected_connection_uri
26+
27+
28+
def test_user_permissions(connection: psycopg.Connection):
29+
"""Test that a user can create databases but is not a superuser."""
30+
31+
with connection:
32+
record = connection \
33+
.execute("SELECT usecreatedb, usesuper FROM pg_user WHERE usename = CURRENT_USER") \
34+
.fetchone()
35+
assert record
36+
37+
usecreatedb, usesuper = record
38+
assert usecreatedb
39+
assert not usesuper
40+
41+
42+
def test_user_create_insert_select(connection: psycopg.Connection):
43+
"""Test that a user has CRUD permissions in a database."""
44+
45+
table_name = "test_setup_postgres"
46+
47+
with connection, connection.transaction(force_rollback=True):
48+
records = connection \
49+
.execute(f"CREATE TABLE {table_name}(eggs INTEGER, rice VARCHAR)") \
50+
.execute(f"INSERT INTO {table_name}(eggs, rice) VALUES (1, '42')") \
51+
.execute(f"SELECT * FROM {table_name}") \
52+
.fetchall()
53+
assert records == [(1, "42")]
54+
55+
56+
def test_user_create_drop_database(connection: psycopg.Connection):
57+
"""Test that a user has no permissions to create databases."""
58+
59+
# CREATE/DROP DATABASE statements don't work within transactions, and with
60+
# autocommit disabled transactions are created by psycopg automatically.
61+
connection.autocommit = True
62+
63+
database_name = "foobar42"
64+
connection.execute(f"CREATE DATABASE {database_name}")
65+
connection.execute(f"DROP DATABASE {database_name}")

0 commit comments

Comments
 (0)