tundri 1.3.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of tundri might be problematic. Click here for more details.
- tundri-1.3.3/.github/pull_request_template.md +16 -0
- tundri-1.3.3/.github/workflows/manual-release.yml +81 -0
- tundri-1.3.3/.github/workflows/on-release-merge.yml +84 -0
- tundri-1.3.3/.github/workflows/pr-tests.yml +53 -0
- tundri-1.3.3/.github/workflows/publish-on-release.yml +35 -0
- tundri-1.3.3/.github/workflows/publish-to-testpypi.yml +54 -0
- tundri-1.3.3/.gitignore +18 -0
- tundri-1.3.3/.python-version +1 -0
- tundri-1.3.3/PKG-INFO +147 -0
- tundri-1.3.3/README.md +120 -0
- tundri-1.3.3/docs/RELEASE_WORKFLOW.md +196 -0
- tundri-1.3.3/docs/images/logo.jpg +0 -0
- tundri-1.3.3/docs/images/run_example.png +0 -0
- tundri-1.3.3/examples/permifrost.yml +364 -0
- tundri-1.3.3/examples/permifrost_with_bob.yml +393 -0
- tundri-1.3.3/examples/permifrost_without_bob.yml +364 -0
- tundri-1.3.3/pyproject.toml +62 -0
- tundri-1.3.3/pytest.ini +3 -0
- tundri-1.3.3/tests/conftest.py +42 -0
- tundri-1.3.3/tests/data/correct_required_params_spec.yml +21 -0
- tundri-1.3.3/tests/data/incorrect_required_params_spec.yml +21 -0
- tundri-1.3.3/tests/data/uppercase_meta_params_spec.yml +22 -0
- tundri-1.3.3/tests/integration_tests/test_connection.py +34 -0
- tundri-1.3.3/tests/integration_tests/test_ddl.py +46 -0
- tundri-1.3.3/tests/test_core.py +46 -0
- tundri-1.3.3/tests/test_parser.py +84 -0
- tundri-1.3.3/tests/test_utils.py +47 -0
- tundri-1.3.3/tundri/__init__.py +3 -0
- tundri-1.3.3/tundri/cli.py +121 -0
- tundri-1.3.3/tundri/constants.py +47 -0
- tundri-1.3.3/tundri/core.py +307 -0
- tundri-1.3.3/tundri/inspector.py +174 -0
- tundri-1.3.3/tundri/objects.py +74 -0
- tundri-1.3.3/tundri/parser.py +112 -0
- tundri-1.3.3/tundri/utils.py +224 -0
- tundri-1.3.3/uv.lock +1785 -0
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
## Description
|
|
2
|
+
|
|
3
|
+
<!--- In this section, concisely describe what your Pull Request does, and why it is important (both in a technical and business value sense) -->
|
|
4
|
+
|
|
5
|
+
## Changes
|
|
6
|
+
|
|
7
|
+
<!--- Please document here, if relevant, what has changed, and in which specific models -->
|
|
8
|
+
|
|
9
|
+
## Checklist
|
|
10
|
+
|
|
11
|
+
<!--- Please make sure all of the below are checked off before submitting your PR ;) -->
|
|
12
|
+
|
|
13
|
+
- [ ] My pull request represents one logical piece of work
|
|
14
|
+
- [ ] My commits are related to the pull request and look clean
|
|
15
|
+
- [ ] I tested my changes locally with `uv run pytest -vv`
|
|
16
|
+
- [ ] I formatted my code with `uv run black .`
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
name: Manual Release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
workflow_dispatch:
|
|
5
|
+
inputs:
|
|
6
|
+
version_type:
|
|
7
|
+
description: "Type of version bump (major, minor, patch)"
|
|
8
|
+
required: true
|
|
9
|
+
default: "patch"
|
|
10
|
+
type: choice
|
|
11
|
+
options:
|
|
12
|
+
- major
|
|
13
|
+
- minor
|
|
14
|
+
- patch
|
|
15
|
+
|
|
16
|
+
jobs:
|
|
17
|
+
release:
|
|
18
|
+
runs-on: ubuntu-latest
|
|
19
|
+
|
|
20
|
+
steps:
|
|
21
|
+
- name: Checkout code
|
|
22
|
+
uses: actions/checkout@v3
|
|
23
|
+
|
|
24
|
+
- name: Set up Python
|
|
25
|
+
uses: actions/setup-python@v4
|
|
26
|
+
with:
|
|
27
|
+
python-version: '3.12'
|
|
28
|
+
|
|
29
|
+
- name: Bump version
|
|
30
|
+
id: bump_version
|
|
31
|
+
run: |
|
|
32
|
+
CURRENT_VERSION=$(grep '^version = ' pyproject.toml | sed -E "s/version = \"([0-9]+\.[0-9]+\.[0-9]+)\"/\1/")
|
|
33
|
+
echo "Current version: $CURRENT_VERSION"
|
|
34
|
+
|
|
35
|
+
# Determine the version type to bump (major, minor, patch)
|
|
36
|
+
VERSION_TYPE=${{ github.event.inputs.version_type }}
|
|
37
|
+
IFS='.' read -r MAJOR MINOR PATCH <<< "$CURRENT_VERSION"
|
|
38
|
+
|
|
39
|
+
if [ "$VERSION_TYPE" == "major" ]; then
|
|
40
|
+
MAJOR=$((MAJOR + 1))
|
|
41
|
+
MINOR=0
|
|
42
|
+
PATCH=0
|
|
43
|
+
elif [ "$VERSION_TYPE" == "minor" ]; then
|
|
44
|
+
MINOR=$((MINOR + 1))
|
|
45
|
+
PATCH=0
|
|
46
|
+
elif [ "$VERSION_TYPE" == "patch" ]; then
|
|
47
|
+
PATCH=$((PATCH + 1))
|
|
48
|
+
else
|
|
49
|
+
echo "Invalid version type: $VERSION_TYPE"
|
|
50
|
+
exit 1
|
|
51
|
+
fi
|
|
52
|
+
|
|
53
|
+
NEW_VERSION="$MAJOR.$MINOR.$PATCH"
|
|
54
|
+
echo "New version: $NEW_VERSION"
|
|
55
|
+
|
|
56
|
+
# Update pyproject.toml with the new version
|
|
57
|
+
sed -i "s/version = \"$CURRENT_VERSION\"/version = \"$NEW_VERSION\"/" pyproject.toml
|
|
58
|
+
|
|
59
|
+
echo "new_version=$NEW_VERSION" >> $GITHUB_ENV
|
|
60
|
+
|
|
61
|
+
- name: Create release branch
|
|
62
|
+
id: create_branch
|
|
63
|
+
run: |
|
|
64
|
+
NEW_VERSION=${{ env.new_version }}
|
|
65
|
+
BRANCH_NAME="release/v$NEW_VERSION"
|
|
66
|
+
git checkout -b "$BRANCH_NAME"
|
|
67
|
+
git push origin "$BRANCH_NAME"
|
|
68
|
+
echo "branch_name=$BRANCH_NAME" >> $GITHUB_ENV
|
|
69
|
+
|
|
70
|
+
- name: Create pull request
|
|
71
|
+
uses: peter-evans/create-pull-request@v7
|
|
72
|
+
with:
|
|
73
|
+
token: ${{ secrets.GITHUB_TOKEN }}
|
|
74
|
+
branch: ${{ env.branch_name }}
|
|
75
|
+
base: main
|
|
76
|
+
title: "Release ${{ env.new_version }}"
|
|
77
|
+
body: |
|
|
78
|
+
This PR bumps the version to ${{ env.new_version }} and prepares the release.
|
|
79
|
+
|
|
80
|
+
# Tag and release creation will be handled by on-release-merge.yml workflow
|
|
81
|
+
# when this PR is merged
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
name: On Release Merge
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request:
|
|
5
|
+
types: [closed]
|
|
6
|
+
branches:
|
|
7
|
+
- main
|
|
8
|
+
paths:
|
|
9
|
+
- 'pyproject.toml'
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
create-tag-and-release:
|
|
13
|
+
if: github.event.pull_request.merged == true && startsWith(github.event.pull_request.head.ref, 'release/')
|
|
14
|
+
runs-on: ubuntu-latest
|
|
15
|
+
|
|
16
|
+
steps:
|
|
17
|
+
- name: Checkout code
|
|
18
|
+
uses: actions/checkout@v3
|
|
19
|
+
with:
|
|
20
|
+
fetch-depth: 0
|
|
21
|
+
|
|
22
|
+
- name: Set up Python
|
|
23
|
+
uses: actions/setup-python@v4
|
|
24
|
+
with:
|
|
25
|
+
python-version: '3.12'
|
|
26
|
+
|
|
27
|
+
- name: Get version
|
|
28
|
+
id: get_version
|
|
29
|
+
run: |
|
|
30
|
+
VERSION=$(grep '^version = ' pyproject.toml | sed -E "s/version = \"([0-9]+\.[0-9]+\.[0-9]+)\"/\1/")
|
|
31
|
+
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
|
32
|
+
|
|
33
|
+
- name: Create tag
|
|
34
|
+
run: |
|
|
35
|
+
VERSION=${{ steps.get_version.outputs.version }}
|
|
36
|
+
TAG_NAME="v$VERSION"
|
|
37
|
+
git config user.name "github-actions"
|
|
38
|
+
git config user.email "github-actions@github.com"
|
|
39
|
+
git tag "$TAG_NAME"
|
|
40
|
+
git push origin "$TAG_NAME"
|
|
41
|
+
|
|
42
|
+
- name: Create release
|
|
43
|
+
uses: actions/create-release@v1
|
|
44
|
+
env:
|
|
45
|
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
46
|
+
with:
|
|
47
|
+
tag_name: v${{ steps.get_version.outputs.version }}
|
|
48
|
+
release_name: v${{ steps.get_version.outputs.version }}
|
|
49
|
+
body: |
|
|
50
|
+
This release includes the following changes:
|
|
51
|
+
- Version bump to ${{ steps.get_version.outputs.version }}
|
|
52
|
+
draft: false
|
|
53
|
+
prerelease: false
|
|
54
|
+
|
|
55
|
+
publish-to-pypi:
|
|
56
|
+
if: github.event.pull_request.merged == true && startsWith(github.event.pull_request.head.ref, 'release/')
|
|
57
|
+
runs-on: ubuntu-latest
|
|
58
|
+
needs: create-tag-and-release
|
|
59
|
+
|
|
60
|
+
steps:
|
|
61
|
+
- name: Checkout code
|
|
62
|
+
uses: actions/checkout@v3
|
|
63
|
+
|
|
64
|
+
- name: Set up Python
|
|
65
|
+
uses: actions/setup-python@v4
|
|
66
|
+
with:
|
|
67
|
+
python-version: '3.12'
|
|
68
|
+
|
|
69
|
+
- name: Install build dependencies
|
|
70
|
+
run: |
|
|
71
|
+
python -m pip install --upgrade pip
|
|
72
|
+
pip install build twine
|
|
73
|
+
|
|
74
|
+
- name: Build package
|
|
75
|
+
run: python -m build
|
|
76
|
+
|
|
77
|
+
- name: Check package
|
|
78
|
+
run: twine check dist/*
|
|
79
|
+
|
|
80
|
+
- name: Publish to PyPI
|
|
81
|
+
env:
|
|
82
|
+
TWINE_USERNAME: __token__
|
|
83
|
+
TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}
|
|
84
|
+
run: twine upload dist/*
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
name: PR Unit Tests
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request:
|
|
5
|
+
branches:
|
|
6
|
+
- main
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
test:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
|
|
12
|
+
env:
|
|
13
|
+
PERMISSION_BOT_USER: ${{ secrets.SNOWFLAKE_USER }}
|
|
14
|
+
PERMISSION_BOT_KEY_PATH: "./.ssh/private_key.p8"
|
|
15
|
+
PERMISSION_BOT_KEY_PASSPHRASE: ${{ secrets.SNOWFLAKE_KEY_PASSPHRASE }}
|
|
16
|
+
PERMISSION_BOT_ACCOUNT: ${{ secrets.SNOWFLAKE_ACCOUNT }}
|
|
17
|
+
PERMISSION_BOT_DATABASE: ${{ secrets.SNOWFLAKE_DATABASE }}
|
|
18
|
+
PERMISSION_BOT_ROLE: ${{ secrets.SNOWFLAKE_ROLE }}
|
|
19
|
+
PERMISSION_BOT_WAREHOUSE: ${{ secrets.SNOWFLAKE_WAREHOUSE }}
|
|
20
|
+
|
|
21
|
+
steps:
|
|
22
|
+
- name: Checkout code
|
|
23
|
+
uses: actions/checkout@v3
|
|
24
|
+
|
|
25
|
+
- name: Set up Python
|
|
26
|
+
uses: actions/setup-python@v4
|
|
27
|
+
with:
|
|
28
|
+
python-version: '3.12'
|
|
29
|
+
|
|
30
|
+
- name: Install uv
|
|
31
|
+
uses: astral-sh/setup-uv@v1
|
|
32
|
+
with:
|
|
33
|
+
version: "latest"
|
|
34
|
+
|
|
35
|
+
# tundri currently only supports connecting to Snowflake by providing
|
|
36
|
+
# the path to the user's private key, so we need to dump the key somewhere in
|
|
37
|
+
# the Ubuntu environment. While connecting via password is also possible, we are
|
|
38
|
+
# opting for key-pair to be future-proof
|
|
39
|
+
#
|
|
40
|
+
# TODO: extend get_snowflake_cursor() function to allow
|
|
41
|
+
# passing raw keys, so we can avoid exposing the private key
|
|
42
|
+
- name: Create folder for private key
|
|
43
|
+
run: mkdir -p ./.ssh
|
|
44
|
+
|
|
45
|
+
- name: Write private key to file
|
|
46
|
+
run: |
|
|
47
|
+
echo "${{ secrets.SNOWFLAKE_PRIVATE_KEY }}" > $PERMISSION_BOT_KEY_PATH
|
|
48
|
+
|
|
49
|
+
- name: Install dependencies
|
|
50
|
+
run: uv sync
|
|
51
|
+
|
|
52
|
+
- name: Run tests
|
|
53
|
+
run: uv run pytest -vv
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
name: Publish to PyPI on Release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [published]
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
publish-to-pypi:
|
|
9
|
+
runs-on: ubuntu-latest
|
|
10
|
+
|
|
11
|
+
steps:
|
|
12
|
+
- name: Checkout code
|
|
13
|
+
uses: actions/checkout@v3
|
|
14
|
+
|
|
15
|
+
- name: Set up Python
|
|
16
|
+
uses: actions/setup-python@v4
|
|
17
|
+
with:
|
|
18
|
+
python-version: '3.12'
|
|
19
|
+
|
|
20
|
+
- name: Install build dependencies
|
|
21
|
+
run: |
|
|
22
|
+
python -m pip install --upgrade pip
|
|
23
|
+
pip install build twine
|
|
24
|
+
|
|
25
|
+
- name: Build package
|
|
26
|
+
run: python -m build
|
|
27
|
+
|
|
28
|
+
- name: Check package
|
|
29
|
+
run: twine check dist/*
|
|
30
|
+
|
|
31
|
+
- name: Publish to PyPI
|
|
32
|
+
env:
|
|
33
|
+
TWINE_USERNAME: __token__
|
|
34
|
+
TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}
|
|
35
|
+
run: twine upload dist/*
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
name: Publish to TestPyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request:
|
|
5
|
+
branches:
|
|
6
|
+
- main
|
|
7
|
+
paths:
|
|
8
|
+
- 'pyproject.toml'
|
|
9
|
+
- 'tundri/**'
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
publish-to-testpypi:
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
if: github.event.pull_request.merged == false
|
|
15
|
+
|
|
16
|
+
steps:
|
|
17
|
+
- name: Checkout code
|
|
18
|
+
uses: actions/checkout@v3
|
|
19
|
+
|
|
20
|
+
- name: Set up Python
|
|
21
|
+
uses: actions/setup-python@v4
|
|
22
|
+
with:
|
|
23
|
+
python-version: '3.12'
|
|
24
|
+
|
|
25
|
+
- name: Install build dependencies
|
|
26
|
+
run: |
|
|
27
|
+
python -m pip install --upgrade pip
|
|
28
|
+
pip install build twine
|
|
29
|
+
|
|
30
|
+
- name: Build package
|
|
31
|
+
run: python -m build
|
|
32
|
+
|
|
33
|
+
- name: Check package
|
|
34
|
+
run: twine check dist/*
|
|
35
|
+
|
|
36
|
+
- name: Publish to TestPyPI
|
|
37
|
+
env:
|
|
38
|
+
TWINE_USERNAME: __token__
|
|
39
|
+
TWINE_PASSWORD: ${{ secrets.TEST_PYPI_API_TOKEN }}
|
|
40
|
+
run: twine upload --repository testpypi dist/*
|
|
41
|
+
|
|
42
|
+
- name: Comment PR with TestPyPI link
|
|
43
|
+
uses: actions/github-script@v6
|
|
44
|
+
with:
|
|
45
|
+
script: |
|
|
46
|
+
const version = require('fs').readFileSync('pyproject.toml', 'utf8')
|
|
47
|
+
.match(/version = "([^"]+)"/)[1];
|
|
48
|
+
const testpypiUrl = `https://test.pypi.org/project/tundri/${version}/`;
|
|
49
|
+
github.rest.issues.createComment({
|
|
50
|
+
issue_number: context.issue.number,
|
|
51
|
+
owner: context.repo.owner,
|
|
52
|
+
repo: context.repo.repo,
|
|
53
|
+
body: `🚀 Package published to TestPyPI!\n\nYou can test the installation with:\n\`\`\`bash\npip install --index-url https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple/ tundri==${version}\n\`\`\`\n\nView package: ${testpypiUrl}`
|
|
54
|
+
});
|
tundri-1.3.3/.gitignore
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.11.9
|
tundri-1.3.3/PKG-INFO
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: tundri
|
|
3
|
+
Version: 1.3.3
|
|
4
|
+
Summary: Drop, create and alter Snowflake objects and set permissions with Permifrost
|
|
5
|
+
Project-URL: Homepage, https://github.com/Gemma-Analytics/tundri
|
|
6
|
+
Project-URL: Repository, https://github.com/Gemma-Analytics/tundri
|
|
7
|
+
Project-URL: Issues, https://github.com/Gemma-Analytics/tundri/issues
|
|
8
|
+
Author-email: Gemma Analytics <bijan.soltani@gemmaanalytics.com>
|
|
9
|
+
License: MIT
|
|
10
|
+
Keywords: database,ddl,permifrost,snowflake
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Requires-Python: >=3.8
|
|
21
|
+
Requires-Dist: gemma-permifrost
|
|
22
|
+
Requires-Dist: python-dotenv
|
|
23
|
+
Requires-Dist: pyyaml
|
|
24
|
+
Requires-Dist: rich
|
|
25
|
+
Requires-Dist: snowflake-connector-python
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
|
|
28
|
+
<div align="center">
|
|
29
|
+
<img src="docs/images/logo.jpg" alt="tundri Logo" width="200">
|
|
30
|
+
</div>
|
|
31
|
+
|
|
32
|
+
**tundri** is a Python package to declaratively create, drop, and alter Snowflake objects and manage their permissions with [Permifrost](https://gitlab.com/gitlab-data/permifrost).
|
|
33
|
+
|
|
34
|
+
## Motivation
|
|
35
|
+
|
|
36
|
+
Permifrost is great at managing permissions, but it doesn't create or alter objects. As [GitLab's data team handbook](https://handbook.gitlab.com/handbook/enterprise-data/platform/permifrost/) states:
|
|
37
|
+
> Object creation and deletion is not managed by permifrost
|
|
38
|
+
|
|
39
|
+
With only Permifrost, one would have to manually create the objects and then run Permifrost to set the permissions. This is error prone and time consuming. That is where tundri comes in.
|
|
40
|
+
|
|
41
|
+
### In a nutshell
|
|
42
|
+
**tundri** reads the [Permifrost spec file](https://gitlab.com/gitlab-data/permifrost#spec_file) and compares with the current state of the Snowflake account. It then creates, drops, and alters the objects to match. It leverages Permifrost's YAML `meta` tags to set attributes like `default_role` for users and `warehouse_size` for warehouses. Once the objects are created, tundri runs Permifrost to set the permissions.
|
|
43
|
+
|
|
44
|
+
## Getting started
|
|
45
|
+
|
|
46
|
+
### Prerequisites
|
|
47
|
+
|
|
48
|
+
- Credentials to a Snowflake user account with the `securityadmin` role
|
|
49
|
+
- A Permifrost spec file
|
|
50
|
+
|
|
51
|
+
### Install
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
pip install tundri
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Configure
|
|
58
|
+
|
|
59
|
+
#### Permifrost
|
|
60
|
+
Add a valid [Permifrost spec file](https://gitlab.com/gitlab-data/permifrost#spec_file) to your repository. You can use the files in the `examples` folder as reference.
|
|
61
|
+
|
|
62
|
+
#### Snowflake
|
|
63
|
+
Set up your Snowflake connection details in the environment variables listed below.
|
|
64
|
+
|
|
65
|
+
> [!TIP]
|
|
66
|
+
> You can use a `.env` file to store your credentials. Place it in the same folder as the Permifrost spec file.
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
PERMISSION_BOT_ACCOUNT=abc134.west-europe.azure # Your account identifier
|
|
70
|
+
PERMISSION_BOT_USER=PERMIFROST
|
|
71
|
+
PERMISSION_BOT_PASSWORD=...
|
|
72
|
+
PERMISSION_BOT_ROLE=SECURITYADMIN # Permifrost requires it to be `SECURITYADMIN`
|
|
73
|
+
PERMISSION_BOT_DATABASE=PERMIFROST
|
|
74
|
+
PERMISSION_BOT_WAREHOUSE=ADMIN
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Usage
|
|
78
|
+
The `run` subcommand is going to drop/create objects and run Permifrost.
|
|
79
|
+
|
|
80
|
+
#### Dry run
|
|
81
|
+
```bash
|
|
82
|
+
tundri run --permifrost_spec_path examples/permifrost.yml --dry
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
#### Normal run
|
|
86
|
+
```bash
|
|
87
|
+
tundri run --permifrost_spec_path examples/permifrost.yml
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
#### Getting help
|
|
91
|
+
```bash
|
|
92
|
+
tundri --help
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Development
|
|
96
|
+
### Local setup
|
|
97
|
+
Install the development dependencies
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
uv sync
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### Run tests
|
|
104
|
+
Run the tests
|
|
105
|
+
```bash
|
|
106
|
+
uv run pytest -v
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Formatting
|
|
110
|
+
Run the command below to format the code
|
|
111
|
+
```bash
|
|
112
|
+
uv run black .
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### Testing locally
|
|
116
|
+
Dry run with the example spec file
|
|
117
|
+
```bash
|
|
118
|
+
uv run tundri run --dry -p examples/permifrost.yml
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## Contributing
|
|
122
|
+
|
|
123
|
+
### Release process
|
|
124
|
+
|
|
125
|
+
The release process is automated using GitHub Actions. Here's how it works:
|
|
126
|
+
|
|
127
|
+
1. **Adding new features or bug fixes**
|
|
128
|
+
- PR tests run automatically to verify the changes on each PR
|
|
129
|
+
- Multiple PRs can be merged to main until a release-ready state is reached
|
|
130
|
+
|
|
131
|
+
1. **Initiating a Release**
|
|
132
|
+
- A maintainer triggers the manual release workflow
|
|
133
|
+
- They specify the version bump type (`major`, `minor`, or `patch`)
|
|
134
|
+
- This creates a release branch and PR with updated version
|
|
135
|
+
|
|
136
|
+
1. **Release Creation**
|
|
137
|
+
- When the release PR is merged to main:
|
|
138
|
+
- A Git tag is created (e.g., `v1.2.3`)
|
|
139
|
+
- A GitHub release is created
|
|
140
|
+
- The package is published to PyPI
|
|
141
|
+
|
|
142
|
+
The process requires the following GitHub secrets to be configured:
|
|
143
|
+
- `PYPI_API_TOKEN`: For production PyPI publishing
|
|
144
|
+
- `TEST_PYPI_API_TOKEN`: For TestPyPI publishing
|
|
145
|
+
- `SNOWFLAKE_*`: Snowflake credentials for running tests
|
|
146
|
+
|
|
147
|
+
For full details on the release workflow, see [RELEASE_WORKFLOW.md](docs/RELEASE_WORKFLOW.md).
|
tundri-1.3.3/README.md
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
<img src="docs/images/logo.jpg" alt="tundri Logo" width="200">
|
|
3
|
+
</div>
|
|
4
|
+
|
|
5
|
+
**tundri** is a Python package to declaratively create, drop, and alter Snowflake objects and manage their permissions with [Permifrost](https://gitlab.com/gitlab-data/permifrost).
|
|
6
|
+
|
|
7
|
+
## Motivation
|
|
8
|
+
|
|
9
|
+
Permifrost is great at managing permissions, but it doesn't create or alter objects. As [GitLab's data team handbook](https://handbook.gitlab.com/handbook/enterprise-data/platform/permifrost/) states:
|
|
10
|
+
> Object creation and deletion is not managed by permifrost
|
|
11
|
+
|
|
12
|
+
With only Permifrost, one would have to manually create the objects and then run Permifrost to set the permissions. This is error prone and time consuming. That is where tundri comes in.
|
|
13
|
+
|
|
14
|
+
### In a nutshell
|
|
15
|
+
**tundri** reads the [Permifrost spec file](https://gitlab.com/gitlab-data/permifrost#spec_file) and compares with the current state of the Snowflake account. It then creates, drops, and alters the objects to match. It leverages Permifrost's YAML `meta` tags to set attributes like `default_role` for users and `warehouse_size` for warehouses. Once the objects are created, tundri runs Permifrost to set the permissions.
|
|
16
|
+
|
|
17
|
+
## Getting started
|
|
18
|
+
|
|
19
|
+
### Prerequisites
|
|
20
|
+
|
|
21
|
+
- Credentials to a Snowflake user account with the `securityadmin` role
|
|
22
|
+
- A Permifrost spec file
|
|
23
|
+
|
|
24
|
+
### Install
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
pip install tundri
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### Configure
|
|
31
|
+
|
|
32
|
+
#### Permifrost
|
|
33
|
+
Add a valid [Permifrost spec file](https://gitlab.com/gitlab-data/permifrost#spec_file) to your repository. You can use the files in the `examples` folder as reference.
|
|
34
|
+
|
|
35
|
+
#### Snowflake
|
|
36
|
+
Set up your Snowflake connection details in the environment variables listed below.
|
|
37
|
+
|
|
38
|
+
> [!TIP]
|
|
39
|
+
> You can use a `.env` file to store your credentials. Place it in the same folder as the Permifrost spec file.
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
PERMISSION_BOT_ACCOUNT=abc134.west-europe.azure # Your account identifier
|
|
43
|
+
PERMISSION_BOT_USER=PERMIFROST
|
|
44
|
+
PERMISSION_BOT_PASSWORD=...
|
|
45
|
+
PERMISSION_BOT_ROLE=SECURITYADMIN # Permifrost requires it to be `SECURITYADMIN`
|
|
46
|
+
PERMISSION_BOT_DATABASE=PERMIFROST
|
|
47
|
+
PERMISSION_BOT_WAREHOUSE=ADMIN
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### Usage
|
|
51
|
+
The `run` subcommand is going to drop/create objects and run Permifrost.
|
|
52
|
+
|
|
53
|
+
#### Dry run
|
|
54
|
+
```bash
|
|
55
|
+
tundri run --permifrost_spec_path examples/permifrost.yml --dry
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
#### Normal run
|
|
59
|
+
```bash
|
|
60
|
+
tundri run --permifrost_spec_path examples/permifrost.yml
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
#### Getting help
|
|
64
|
+
```bash
|
|
65
|
+
tundri --help
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Development
|
|
69
|
+
### Local setup
|
|
70
|
+
Install the development dependencies
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
uv sync
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Run tests
|
|
77
|
+
Run the tests
|
|
78
|
+
```bash
|
|
79
|
+
uv run pytest -v
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Formatting
|
|
83
|
+
Run the command below to format the code
|
|
84
|
+
```bash
|
|
85
|
+
uv run black .
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### Testing locally
|
|
89
|
+
Dry run with the example spec file
|
|
90
|
+
```bash
|
|
91
|
+
uv run tundri run --dry -p examples/permifrost.yml
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## Contributing
|
|
95
|
+
|
|
96
|
+
### Release process
|
|
97
|
+
|
|
98
|
+
The release process is automated using GitHub Actions. Here's how it works:
|
|
99
|
+
|
|
100
|
+
1. **Adding new features or bug fixes**
|
|
101
|
+
- PR tests run automatically to verify the changes on each PR
|
|
102
|
+
- Multiple PRs can be merged to main until a release-ready state is reached
|
|
103
|
+
|
|
104
|
+
1. **Initiating a Release**
|
|
105
|
+
- A maintainer triggers the manual release workflow
|
|
106
|
+
- They specify the version bump type (`major`, `minor`, or `patch`)
|
|
107
|
+
- This creates a release branch and PR with updated version
|
|
108
|
+
|
|
109
|
+
1. **Release Creation**
|
|
110
|
+
- When the release PR is merged to main:
|
|
111
|
+
- A Git tag is created (e.g., `v1.2.3`)
|
|
112
|
+
- A GitHub release is created
|
|
113
|
+
- The package is published to PyPI
|
|
114
|
+
|
|
115
|
+
The process requires the following GitHub secrets to be configured:
|
|
116
|
+
- `PYPI_API_TOKEN`: For production PyPI publishing
|
|
117
|
+
- `TEST_PYPI_API_TOKEN`: For TestPyPI publishing
|
|
118
|
+
- `SNOWFLAKE_*`: Snowflake credentials for running tests
|
|
119
|
+
|
|
120
|
+
For full details on the release workflow, see [RELEASE_WORKFLOW.md](docs/RELEASE_WORKFLOW.md).
|