Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions .github/workflows/check.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
name: Check

on:
pull_request:
push:
branches-ignore:
- master

jobs:
lint-and-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: astral-sh/setup-uv@v6

- uses: actions/setup-python@v5
with:
python-version: "3.9"

- run: uv run --group dev ruff check .

- run: uv run --group dev pytest
2 changes: 0 additions & 2 deletions .github/workflows/release-please.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,3 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: googleapis/release-please-action@v4
with:
release-type: python
3 changes: 3 additions & 0 deletions .release-please-manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
".": "0.4.0"
}
31 changes: 31 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,31 @@
Populate Gitlab Project Variables from .env file
=================================================

## Overview

A command-line tool for managing a Gitlab project's CI/CD variables, scoped to a
Gitlab [environment](https://docs.gitlab.com/ee/ci/environments/) (e.g. `uat`,
`production`). It talks to the Gitlab API using a personal access token and lets
you move variables between a local `.env` file and Gitlab in both directions.

It provides four commands:

- `write` — read a local `.env` file and create or update the matching
project variables in the given environment scope. Supports `--include` /
`--exclude` filtering and `--mask` to mask values whose key contains the
substring `KEY`, `SECRET`, or `TOKEN` (e.g. `APP_KEY`, `PUBLIC_KEY`,
`AUTH_TOKEN` will all be masked). Masking is one-way: an already-masked
variable is never un-masked by this tool.
- `list` — print the variables for an environment in a table. Masked values are
hidden unless you pass `--sensitive`.
- `get` — print the variables for an environment, optionally appending them to a
`<scope>.env` file with `--export`.
- `download` — write an environment's variables to a `<environment>.env` file,
prompting before overwriting an existing file.

All commands target both the requested environment and globally-scoped (`*`)
variables. Requires a `GITLAB_TOKEN` environment variable.

## Install

Install as a global user tool (isolated environment, command on your PATH):
Expand Down Expand Up @@ -56,3 +81,9 @@ populate-secrets-gitlab write \
```shell
populate-secrets-gitlab get --environment uat --gitlab-host gitlab.example.com --project my-group/my-project --export
```

### Download variables to an .env file

```shell
populate-secrets-gitlab download --environment uat --gitlab-host gitlab.example.com --project my-group/my-project --output-dir .
```
3 changes: 3 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,8 @@ Issues = "https://github.com/deploymode/populate-secrets-gitlab/issues"
[project.scripts]
populate-secrets-gitlab = "populate_secrets_gitlab.__main__:main"

[dependency-groups]
dev = ["pytest>=8", "ruff>=0.8"]

[tool.setuptools.packages.find]
where = ["src"]
17 changes: 17 additions & 0 deletions release-please-config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"packages": {
".": {
"release-type": "python",
"changelog-sections": [
{"type": "feat", "section": "Features"},
{"type": "fix", "section": "Bug Fixes"},
{"type": "perf", "section": "Performance"},
{"type": "docs", "section": "Documentation", "hidden": true},
{"type": "test", "section": "Tests", "hidden": true},
{"type": "ci", "section": "CI", "hidden": true},
{"type": "refactor", "section": "Refactoring", "hidden": true},
{"type": "chore", "section": "Miscellaneous", "hidden": true}
]
}
}
}
15 changes: 9 additions & 6 deletions src/populate_secrets_gitlab/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s %(levelname)s\t%(message)s',
datefmt='%Y-%m-%d_%H:%M:%S.%s',
datefmt='%Y-%m-%d_%H:%M:%S',
handlers=[
logging.StreamHandler()
],
Expand Down Expand Up @@ -138,7 +138,7 @@ def write(env_file, environment, gitlab_host, project, include, exclude, mask, d

# Write to Gitlab API
try:
if key in gitlab_project_variable_keys_by_scope[environment]:
if key in gitlab_project_variable_keys_by_scope.get(environment, []):
is_update = True
# Update
project_var = [v for v in gl_project_vars if v.key == key][0]
Expand All @@ -164,8 +164,8 @@ def write(env_file, environment, gitlab_host, project, include, exclude, mask, d
logger.info("Failed to write {} due to error from Gitlab API".format(key))
print_exc()
continue
except Exception:
logger.info("Failed to write {} due to unexpected error".format(key))
except gitlab.exceptions.GitlabError:
logger.info("Failed to write {} due to unexpected Gitlab error".format(key))
print_exc()
continue

Expand Down Expand Up @@ -209,7 +209,7 @@ def get(environment, gitlab_host, project, export, debug):
try:
gitlab_token = os.environ["GITLAB_TOKEN"]
except KeyError:
raise Exception(
raise click.ClickException(
f"GITLAB_TOKEN must be set. Get token from https://{gitlab_host}/-/profile/personal_access_tokens"
)

Expand All @@ -232,15 +232,18 @@ def get(environment, gitlab_host, project, export, debug):


gitlabProjectVariables = gitlabProject.variables.list(get_all=True)
export_opened = set()
for variable in sorted(gitlabProjectVariables, key=lambda v: v.key):
scope = 'global' if variable.environment_scope == '*' else variable.environment_scope
if scope == environment or scope == 'global':
click.secho(f"[{variable.environment_scope}] {variable.key}={variable.value}", fg='yellow')

if export:
logger.debug(f"Writing {variable.key} to {scope}.env")
with open(f"{scope}.env", 'a+') as f:
mode = "a" if scope in export_opened else "w"
with open(f"{scope}.env", mode) as f:
f.write(f"{variable.key}={variable.value}\n")
export_opened.add(scope)

logger.info("Done")

Expand Down
2 changes: 0 additions & 2 deletions src/populate_secrets_gitlab/gitlab_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,3 @@

def gitlab_client(gitlab_host, gitlab_token):
return gitlab.Gitlab(util.prepare_gitlab_host(gitlab_host), private_token=gitlab_token)


Empty file added tests/__init__.py
Empty file.
Loading
Loading