Skip to content

Fix packaging/security issues in extracted GitLab module + GitLab user provisioning in dtaas-cli - #1750

Open
8ohamed wants to merge 12 commits into
INTO-CPS-Association:feature/distributed-demofrom
8ohamed:feature/distributed-demo
Open

Fix packaging/security issues in extracted GitLab module + GitLab user provisioning in dtaas-cli#1750
8ohamed wants to merge 12 commits into
INTO-CPS-Association:feature/distributed-demofrom
8ohamed:feature/distributed-demo

Conversation

@8ohamed

@8ohamed 8ohamed commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

Fix packaging/security issues in extracted GitLab module + GitLab user provisioning in dtaas-cli

Type of Change

  • New feature
  • Bug fix
  • Documentation update
  • Refactoring
  • Security patch
  • UI/UX improvement

Description

Fixes review findings on the earlier GitLab extraction (#1748). The shared GitLab code now lives in lib/gitlab_common/ as a standalone package and is copied into dtaas-services at build time, rather than published separately or embedded directly.

  • lib/gitlab_common/ standalone package (own pyproject.toml, own tests), single source of truth.
  • dtaas_services/pkg/build.py copies lib/gitlab_common/gitlab_common/ into dtaas_services/gitlab_common/
  • CI runs the build script automatically (run-build-script: true) before both tests and packaging; locally it must be run manually documented in DEVELOPER.md and README.md.
  • gitlab_common no longer mutates global warning state SSL-warning suppression moved back to the application entry point (_api.py).
  • ssl_verify now accepts a CA-bundle path, not just bool.
  • PAT scopes/name/lifetime are now caller-supplied (PatOptions), defaulting to least-privilege repository scopes; dtaas_services opts into api scope explicitly.
  • create_user returns a CreateOutcome enum (CREATED/ALREADY_EXISTS/FAILED) instead of encoding "already exists" as a nullable id.
  • Added py.typed, and a CI step that fails the build if wheel metadata contains a direct-URL dependency.

Uses gitlab_common in the DTaaS CLI, adding the capability of the gitlab_common. The CLI had no GitLab code before this (only OAuth URLs in dtaas.toml).

  • dtaas user add can now create each new user's GitLab account and a Personal Access Token, gated behind [gitlab].provision in dtaas.toml (default false no behaviour change unless explicitly enabled).
  • cli/src/pkg/gitlab/ (client.py + provisioner.py) is built entirely on gitlab_common's client and user/PAT primitives no GitLab logic is reimplemented in the CLI.
  • A password is required per user, via --password (prompted with hidden input if omitted, for a single-user add) or a new password column in users.csv. It is used once to create the account and is never written to dtaas.users.registry.json, .dtaas.state.json, or logs.
  • Issued PATs are saved to gitlab_user_tokens.json (mode 0600).
  • A GitLab failure for one user, or an unreachable instance, is reported and does not affect container provisioning or other users.

@8ohamed

8ohamed commented Aug 19, 2026

Copy link
Copy Markdown
Contributor Author

@prasadtalasila

@prasadtalasila

Copy link
Copy Markdown
Contributor

@8ohamed thanks for the PR. One correction about the file layout. The new code moved yesterday has to go into lib/gitlab_common directory. Even the pyproject.toml etc. have to go in there. You can always create a sub directory inside to separate pyproject feom src. Please add the migration in this PR.Thanks.

@8ohamed

8ohamed commented Aug 19, 2026

Copy link
Copy Markdown
Contributor Author

One correction about the file layout. The new code moved yesterday has to go into lib/gitlab_common directory. Even the pyproject.toml etc. have to go in there. You can always create a sub directory inside to separate pyproject feom src.

@prasadtalasila The issue was that if the dtaas-gitlab is outside the dtaas-services then publishing and packaging dtaas-services broke, that's why I moved the dtaas-gitlab inside dtaas-services, and later dtaas-cli would import/depend on dtaas-services to use dtaas-gitlab.

Should I move dtaas-gitlab out again, and then add in the build flow of dtaas-services a step that copies the dtaas-gitlab src code before packaging and publishing, and gitignoring it?
dtaas-cli would still depend on dtaas-services.

@prasadtalasila

Copy link
Copy Markdown
Contributor

One correction about the file layout. The new code moved yesterday has to go into lib/gitlab_common directory. Even the pyproject.toml etc. have to go in there. You can always create a sub directory inside to separate pyproject feom src.

@prasadtalasila The issue was that if the dtaas-gitlab is outside the dtaas-services then publishing and packaging dtaas-services broke, that's why I moved the dtaas-gitlab inside dtaas-services, and later dtaas-cli would import/depend on dtaas-services to use dtaas-gitlab.

Should I move dtaas-gitlab out again, and then add in the build flow of dtaas-services a step that copies the dtaas-gitlab src code before packaging and publishing, and gitignoring it?
dtaas-cli would still depend on dtaas-services.

Please add the step of copying and publishing. Don't make the two CLIs ddependent

@8ohamed

8ohamed commented Aug 19, 2026

Copy link
Copy Markdown
Contributor Author

@prasadtalasila I moved it back out, and added the build step "python -m dtaas_services.pkg.build" same style as the copying templates in dtaas-cli.

@prasadtalasila

Copy link
Copy Markdown
Contributor

@prasadtalasila I moved it back out, and added the build step "python -m dtaas_services.pkg.build" same style as the copying templates in dtaas-cli.

@8ohamed, it might be better to add a command (or hook) into poetry build step.

@8ohamed

8ohamed commented Aug 19, 2026

Copy link
Copy Markdown
Contributor Author

@prasadtalasila I moved it back out, and added the build step "python -m dtaas_services.pkg.build" same style as the copying templates in dtaas-cli.

@8ohamed, it might be better to add a command (or hook) into poetry build step.

@prasadtalasila we tried that with the dtaas-cli and it introduced the problem with platform specific pip package.

@8ohamed

8ohamed commented Aug 26, 2026

Copy link
Copy Markdown
Contributor Author

@prasadtalasila shoyld I also refactor the gitlab module at the top-level cli in this PR?

@prasadtalasila

Copy link
Copy Markdown
Contributor

@prasadtalasila shoyld I also refactor the gitlab module at the top-level cli in this PR?

makes sense. please do.

@8ohamed 8ohamed changed the title Fix packaging/security issues in extracted GitLab module Fix packaging/security issues in extracted GitLab module + GitLab user provisioning in dtaas-cli Aug 26, 2026
@codecov

codecov Bot commented Aug 26, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 98.55072% with 7 lines in your changes missing coverage. Please review.
✅ Project coverage is 98.18%. Comparing base (8d848c8) to head (d4f3c6d).
⚠️ Report is 2 commits behind head on feature/distributed-demo.

Files with missing lines Patch % Lines
lib/gitlab_common/gitlab_common/users.py 94.73% 3 Missing ⚠️
cli/src/pkg/build.py 92.00% 2 Missing ⚠️
cli/src/cmd.py 92.85% 1 Missing ⚠️
cli/src/cmd_utils.py 94.44% 1 Missing ⚠️
Additional details and impacted files
@@                     Coverage Diff                      @@
##           feature/distributed-demo    #1750      +/-   ##
============================================================
+ Coverage                     97.00%   98.18%   +1.18%     
============================================================
  Files                           185       42     -143     
  Lines                          6239     2595    -3644     
  Branches                       1037        0    -1037     
============================================================
- Hits                           6052     2548    -3504     
+ Misses                          184       47     -137     
+ Partials                          3        0       -3     
Files with missing lines Coverage Δ
cli/src/cmd_options.py 100.00% <100.00%> (ø)
cli/src/cmd_user.py 100.00% <100.00%> (ø)
cli/src/cmd_user_lifecycle.py 100.00% <100.00%> (ø)
cli/src/cmd_user_utils.py 96.10% <100.00%> (ø)
cli/src/pkg/config.py 100.00% <100.00%> (ø)
cli/src/pkg/config_validate.py 99.14% <100.00%> (ø)
cli/src/pkg/constants.py 100.00% <100.00%> (ø)
cli/src/pkg/gitlab/__init__.py 100.00% <100.00%> (ø)
cli/src/pkg/gitlab/client.py 100.00% <100.00%> (ø)
cli/src/pkg/gitlab/provisioner.py 100.00% <100.00%> (ø)
... and 13 more

... and 204 files with indirect coverage changes

Components Coverage Δ
Website ∅ <ø> (∅)
Lib Microservice ∅ <ø> (∅)
Logger Microservice ∅ <ø> (∅)
Execution Runner ∅ <ø> (∅)
DTaaS CLI 98.58% <99.04%> (∅)
DTaaS Services CLI ∅ <ø> (∅)
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@8ohamed

8ohamed commented Aug 26, 2026

Copy link
Copy Markdown
Contributor Author

Hi @prasadtalasila I added GitLab user provisioning in dtaas-cli dtaas user add can now create a GitLab account + PAT Token for each new user

@prasadtalasila

Copy link
Copy Markdown
Contributor

@8ohamed please see this review from Claude

Review — GitLab module packaging fixes and CLI user provisioning

Verdict: request changes. Three blockers, seven security findings, five lower items.

Scope

The change set has grown well beyond its stated scope. It now spans five commits:

Commit Subject
e8230ac Fixes GitLab pkg bugs
d62d7b5 version bump
ce1f049 moving the dtaas-gitlab out
98383fc GitLab user provisioning in dtaas-cli
1e68641 improves test cov

The last two are a new feature — end-to-end GitLab account and Personal Access
Token provisioning driven from dtaas user add — plus a test-suite rewrite.
The description covers neither, and still describes a layout
(dtaas_services/gitlab_core/) that ce1f049 reversed.

Verification performed

Cloned the head revision and built both consumers from source.

Check Result
cli unit tests 494 passed
cli pylint (repo .pylintrc) 10.00/10
cli coverage 98% overall; 100% on pkg/config.py, pkg/registry.py, pkg/utils.py, pkg/gitlab/provisioner.py
lib/gitlab_common tests 32 passed, 100% coverage
dtaas_services tests (after vendoring) 496 passed, 1 skipped; 5 docker-dependent system tests error for lack of a docker daemon
flake8 (127 cols) on all touched modules clean

Both vendoring scripts work: python -m src.pkg.build and
python -m dtaas_services.pkg.build each populate their target tree, and the
resulting gitlab_common package imports and passes tests from inside both
consumers.


Blockers

B1 — dtaas.toml carries a GitLab admin token and is still written mode 0644

The config template now ships:

[gitlab]
provision=false
api_url="https://gitlab.com"
pat="glpat-xxxxxxxxxxxxxxxxxxxx"
ssl_verify=true

That token must be able to create users, i.e. it is an administrator token.
Generating a project and stat-ing the result:

dtaas.toml 0o644

SECRET_FILENAMES in cli/src/pkg/constants.py is unchanged
({".env", "conf.server", "client.js", "forward-auth-conf"}), so dtaas.toml
receives neither the 0600 chmod applied in project.py and
deploy_config.py nor the wheel-packaging exclusion in build.py. Every
sibling file that holds a credential gets both.

This is the same failure mode found previously for config/dex-config.yaml: a
file's threat class changes, and the secret-handling machinery is not extended
to follow it.

Two files inherit the same gap in this change:

  • users.csv gains an optional plaintext password column (documented in the
    README) and is likewise absent from SECRET_FILENAMES.
  • gitlab_user_tokens.json, the new store of issued PATs, is written 0600 by
    write_secret_file — good — but is absent from SECRET_FILENAMES and from
    .gitignore.

.gitignore gains exactly one line in this change (cli/src/gitlab_common/).
None of the three credential-bearing files above are ignored.

Fix: add dtaas.toml, users.csv, and gitlab_user_tokens.json to
SECRET_FILENAMES, and add the latter two plus dtaas.toml to .gitignore.
Prefer sourcing the provisioning token from DTAAS_GITLAB_PAT and dropping the
pat= line from the shipped template entirely, so the default configuration
never invites an admin token onto disk.

B2 — 33 pre-existing tests deleted; 31 of them still pass against this code

Commit 1e68641 is labelled "improves test cov". Collected test counts across
the four files it touches:

Revision Tests collected
base 83
after 98383fc 93
after 1e68641 71

Restoring the base versions of those four files and running them unmodified
against the head source tree:

4 failed, 31 passed

The four failures are all the same trivial cause — stage_users_for_add now
returns (added, passwords) instead of added, so
test_add_single_user, test_add_users_with_file,
test_add_users_file_import_error, and test_stage_single_user_registers
assert against a tuple. Each needs a one-line update, not deletion.

The other 31 were untouched by this change and were deleted anyway. Among them:

  • test_add_single_user / test_add_users_with_file — the happy paths of the
    very command this change modifies
  • test_delete_user_success, test_lifecycle_command_success,
    test_stop_rejects_starting_user, test_user_pause_all_targets_registry,
    test_user_status_single_user
  • five resolve_usernames / reject_starting_users guards
  • six get_users / get_starting_users config guards
  • four utils guards including test_import_yaml_file_not_found

Line coverage stays at 98% because these paths are still reached indirectly,
so coverage tooling reports no regression. The behavioural assertions are
nonetheless gone.

Fix: restore all 33, updating the four tuple assertions.

B3 — lib/gitlab_common has no CI, and now two published wheels depend on it

No workflow path filter matches lib/**:

Workflow Filter
lib-ms.yml servers/lib/**
python-cli.yml cli/**
platform-services-cli.yml deploy/services/cli/**
lint-scripts.yml **.py, **.sh, **.yml (lint only, no tests)

Both src/pkg/build.py and dtaas_services/pkg/build.py copy
lib/gitlab_common/gitlab_common into their package tree at build time. A
change confined to lib/ therefore alters two published wheels while
triggering zero test runs — worse than the single-consumer situation this was
raised against previously. This is also the direct cause of the repeated 0%
new-code coverage report.

Fix: add lib/** to the paths: filters of both python-cli.yml and
platform-services-cli.yml, or give lib/gitlab_common its own workflow that
runs its tests and then runs both consumers' suites.


Security findings

S1 — CA-bundle support added to the library is discarded at the CLI call site

lib/gitlab_common/client.py now types the parameter correctly:

def get_gitlab_client(url, private_token, *, ssl_verify: bool | str = True)

with a docstring explaining that a CA bundle path is preferable to disabling
verification. cli/src/pkg/config.py then does:

return bool(section.get("ssl_verify", True)), None

Confirmed by parsing a config containing ssl_verify="/etc/ssl/certs/corp-ca.pem":
the value reaches Config intact as a string, and bool() turns it into True.

Consequence for the primary deployment target — a self-hosted GitLab behind an
internal CA: the operator configures the bundle path, it is silently ignored,
verification falls back to the system trust store and fails, and the only
remaining lever in the config surface is ssl_verify=false. That path carries
an administrator PAT and users' initial passwords, unverified.

Nothing warns when verification is disabled, either. The library deliberately
does not suppress InsecureRequestWarning (leaving it to the application), and
the CLI does not surface it.

Fix: in get_gitlab_ssl_verify, pass a non-empty string through unchanged
and coerce only genuine booleans; emit a warning when the resolved value is
False.

S2 — write_secret_file exposes the token at 0644 before chmod

tmp.write_text(content, encoding=encoding)
os.chmod(tmp, 0o600)
os.replace(tmp, path)

Instrumenting the write to stat the temp file at the moment content lands:

tmp file mode at moment of write: 0o644
final mode:                       0o600

The window is short but the content is a set of GitLab PATs, and DTaaS hosts
are multi-user by design — that is the product. The temp name is also
predictable (gitlab_user_tokens.json.tmp) and opened without O_EXCL, so an
existing symlink at that path is followed.

Fix:

fd = os.open(tmp, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600)
with os.fdopen(fd, "w", encoding=encoding) as handle:
    handle.write(content)
os.replace(tmp, path)

This closes both issues at once: the file never exists with any other mode, and
O_EXCL refuses a pre-existing symlink.

S3 — "already exists" is reported as success, discarding a documented warning

gitlab_common.create_user carries an explicit warning in its docstring: on
HTTP 409 the account belongs to whoever registered it, the supplied password is
not applied, and callers should not issue a token against an account they did
not create. CreateOutcome.ALREADY_EXISTS exists precisely to make that
visible.

The provisioner then flattens it:

if result.outcome is CreateOutcome.ALREADY_EXISTS:
    return ProvisionResult(username, True, "GitLab account already exists.")

ok=True, and the caller only echoes a message when ok is false. So on a
GitLab instance that permits self-registration, a pre-claimed username results
in: a provisioned workspace container, a success message, no token, and a
DTaaS account whose GitLab identity is controlled by a third party. The
operator sees nothing unusual.

Declining to issue a PAT is correct. Reporting it as an unremarkable success is
not.

Fix: surface ALREADY_EXISTS distinctly — echo it unconditionally, and word
it as a warning that the account was not created by this run and its
credentials are unknown.

S4 — --password is exposed via shell history and the process list

The interactive prompt covers exactly one case: a single-user add, provisioning
enabled, --password omitted. Every other path takes the password from the
command line or from a plaintext CSV column. The code comment in cmd_user.py
names the risk ("where it would be visible in shell history and the process
list") while the option is offered and documented without that caveat.

Fix: document the exposure alongside the --password row in the options
table, and recommend the CSV or an environment variable for non-interactive
use — with a note to chmod the CSV.

S5 — provisioning failures never reach the exit code

_provision_gitlab_users echoes each failure and returns None; add_users
returns None. Every user's GitLab provisioning can fail — unreachable
instance, expired admin token, rejected passwords — and the command still exits
0 with "Users added successfully".

Treating GitLab failure as non-fatal for container provisioning is a
reasonable design choice. Reporting overall success is not, and it makes the
feature unusable from a script.

Fix: track whether any user was targeted and failed, and return a non-zero
exit (or a distinct message) while still leaving container provisioning intact.

S6 — no retry path after a partial failure

If the account is created but PAT issuance fails, the user is already in the
registry. A re-run of user add skips registered usernames, so added excludes
them, so the password map is empty for them, so provisioning is never retried.
The account exists with a password the operator supplied and no token; recovery
is manual.

Fix: either issue the PAT under the same failure-handling umbrella as a
retryable step, or provide an explicit re-provision path for a
registry-resident user.

S7 — [gitlab] is not validated

config_validate.py has no entries for the new section. provision=true with
a malformed api_url or an empty pat surfaces only when provisioning runs,
after containers have been created.


Lower

  1. Direct-URL metadata guard is unsound on failure. The check is
    unzip -p dist/*.whl '*.dist-info/METADATA' | grep -E ... without
    set -o pipefail. If unzip fails, the pipeline's status is grep's, grep
    finds nothing, the if is false, and the step prints
    "OK: no direct-URL dependencies". It also inspects only the wheel; the sdist
    is built in the same step and never checked. Add pipefail, assert the
    METADATA extraction produced output, and check the sdist's PKG-INFO.

  2. Neither vendored copy carries provenance. src/gitlab_common/ and
    dtaas_services/gitlab_common/ are byte copies with no version or source
    commit recorded. With two consumers, silent divergence between an installed
    wheel and lib/ is now twice as likely. Stamp a __source_version__ during
    vendoring.

  3. No lib/gitlab_common/poetry.lock. Still outstanding.

  4. Token file location is undocumented. The README says issued tokens are
    saved to gitlab_user_tokens.json but not that the path is relative to the
    working directory, that it is mode 0600, that it accumulates across runs, or
    that it must be treated as a credential store. (The working-directory
    relativity is consistent with dtaas.users.registry.json, so it is a
    documentation gap rather than a behavioural inconsistency.)

  5. Description is stale. It describes dtaas_services/gitlab_core/, which
    no longer exists, and does not mention the CLI provisioning feature that
    accounts for most of the diff.


Confirmed resolved

The earlier packaging and library findings are all closed, verified rather than
read:

  • No @ file:// entry in built wheel metadata; both wheel and sdist carry the
    vendored source; a clean-venv install imports successfully.
  • Global warning-state mutation removed from the library and returned to the
    application entry point.
  • ssl_verify accepts a CA bundle path at the library layer (see S1 for the
    CLI-side regression).
  • PatOptions with least-privilege defaults (read_repository,
    write_repository) and UTC expiry; api scope now opted into explicitly by
    the one caller that needs it.
  • CreateOutcome enum replacing the nullable-id encoding, with a docstring
    warning about the already-exists case.
  • py.typed present; README signatures corrected; documentation updated at
    three levels.
  • Passwords are structurally kept out of the registry: _passwords_to_add and
    read_csv_passwords are deliberately independent of the registry-details
    path, and the registry write never sees a password field.

@8ohamed

8ohamed commented Aug 27, 2026

Copy link
Copy Markdown
Contributor Author

@prasadtalasila I have resolved the issues found by claude, and I fixed the qlty issues

@prasadtalasila

Copy link
Copy Markdown
Contributor

@8ohamed please see the revised review from Claude

Review — GitLab module packaging and CLI user provisioning (round 3)

Verdict: request changes. One blocker carried over unaddressed, one new
high-severity finding, four minor items. Everything else raised previously is
fixed, and fixed properly.

What changed since the last review

Three commits added: 5188625 ("claude review"), e1ea63f ("qlty issues"),
87f12cd ("minor issue"). 43 files, +2259/−643. cmd_user.py was also split,
with the pause/stop/resume verbs moving to a new cmd_user_lifecycle.py.

Verification performed

Rebuilt both consumers from the new head and re-ran every measurement from the
previous round.

Check Result
cli unit tests 515 passed (was 494)
cli coverage 98%; 100% on users_gitlab.py, pkg/gitlab/*, registry.py, utils.py, 99% on config_validate.py
cli pylint 9.97/10 (was 10.00)
lib/gitlab_common tests 32 passed, 100% coverage
dtaas_services tests 496 passed, 1 skipped
Generated dtaas.toml / users.csv mode 0600, including remediation of a pre-existing 0644 file on a subsequent run
write_secret_file temp file created 0600 with O_EXCL; a planted symlink at the temp path is refused with FileExistsError
ssl_verify="/etc/ssl/corp-ca.pem" passes through as the string, uncoerced

Blocker (carried over, unaddressed)

B1 — the 33 deleted tests are still gone

The previous round measured 33 pre-existing tests deleted by the "improves test
cov" commit, of which 31 still passed unmodified against the code that deleted
them. Re-checking against every test file in the suite, not just the four that
were edited:

still missing anywhere in suite: 33

All 33. None was renamed, parametrized, or relocated into
test_cmd_user_lifecycle.py. Total suite size rose from 494 to 515 because new
tests were added elsewhere — test_users_gitlab.py alone contributes 318 lines
— which masks the deletion in the aggregate count.

Still missing, among others: test_add_single_user, test_add_users_with_file,
test_delete_user_success, test_lifecycle_command_success,
test_stop_rejects_starting_user, test_user_pause_all_targets_registry,
test_user_status_single_user, the five resolve_usernames /
reject_starting_users guards, six get_users config guards, and four utils
guards.

Two of them are now more valuable than when they were deleted, not less:
test_resolve_usernames_uses_verb_in_message and
test_resolve_usernames_mentions_all_when_allowed covered exactly the
resolve_usernames signature that e1ea63f has since replaced (verb +
allow_all → a single missing_hint). That refactor now ships with no test
asserting the resulting error text.

Four of the 33 need a one-line update for the stage_users_for_add tuple
return; the rest apply unchanged.


New finding

H1 — repeated user add mints a new PAT every run, orphaning the previous ones

The retry path added for the prior round's finding — persist
gitlab_user_id in the registry, and on a later run with a password call
create_user_pat directly against it — has no check for whether a token was
already issued. Exercised with the GitLab calls stubbed:

run1 tokens file: {"alice": "glpat-token-1"}
run2 tokens file: {"alice": "glpat-token-2"}
run3 tokens file: {"alice": "glpat-token-3"}
PATs minted: 3   create_user calls: 1

Three invocations of dtaas user add alice --password … against a
fully-provisioned user produce three live tokens. _save_gitlab_tokens does
existing.update(tokens), so the file keeps only the newest; tokens 1 and 2
remain valid on GitLab for their full 365-day lifetime with no record of them
anywhere the operator can see. There is no revocation path, and nothing in the
output signals that a token was replaced rather than created.

The reachability is ordinary rather than exotic. stage_users_for_add now
deliberately returns passwords for already-registered users — that is the retry
mechanism — so any re-run of the same CSV, which is the natural response to a
partial failure, reissues for every row that already succeeded.

Fix options, roughly in order of preference:

  1. Query user.personal_access_tokens.list() for a live token named
    DEFAULT_PAT_NAME before issuing, and skip when one exists.
  2. Record issuance in the registry (a gitlab_pat_issued flag alongside
    gitlab_user_id) and retry only when it is absent — cheaper, no extra API
    call, but drifts if a token is revoked out of band.
  3. Gate the retry behind an explicit opt-in (--reissue-token) so a plain
    re-run is never a reissue.

Whichever is chosen, _save_gitlab_tokens should not silently overwrite a
user's existing entry. Either refuse, or record the supersession so the
orphaned token is at least traceable.

Note also that the README states the file "accumulates every token ever issued".
It accumulates across users; for a repeat run on the same user it overwrites.
The documentation should match whichever behaviour the fix settles on.


Minor

  1. lib/gitlab_common's own 32 tests still never run in CI. Adding lib/**
    to both workflows' paths: filters (correct, and it closes the previous
    blocker) makes a lib change trigger both pipelines, but each runs
    pytest in cli / deploy/services/cli against the vendored copy. The
    library's suite — the one with 100% coverage, and now with a poetry.lock
    backing it — is never executed. Coverage through the consumers is good
    enough that this is no longer a blocker, but the lock file added this round
    is currently decorative. A short job running pytest in
    lib/gitlab_common would make it real.

  2. pylint dropped from 10.00 to 9.97. Two R0801 duplicate-code hits from
    the identical --file / -f click option block now present in both
    cmd_user.py and cmd_user_lifecycle.py. The CI gate is --fail-under=9.0
    so this passes, but .pylintrc sets fail-under=10.0, so a local
    pylint src now reports failure. Extract the shared decorator into
    cmd_options.py alongside json_option.

  3. The published admin documentation was not updated.
    docs/admin/cli-config.md carries an annotated walkthrough of every
    dtaas.toml section and has no [gitlab] entry; docs/admin/cli.md
    documents user add with no --password and no mention of provisioning.
    cli/README.md and cli/DEVELOPER.md are thorough, but docs/ is the
    user-facing surface and is now out of sync with the shipped template.
    git diff --stat -- docs/ for this round is empty.

  4. "Type of Change" checkboxes. The description now correctly covers both
    halves of the work, but neither "New feature" nor "Security patch" is
    ticked, despite the change adding GitLab provisioning to the CLI and
    hardening three credential-bearing files.


Confirmed resolved

Each item verified by running the code, not by reading the diff.

Credential file handling (previously blocking). SECRET_FILENAMES now
contains dtaas.toml, users.csv, and gitlab_user_tokens.json, so all three
get the 0600 chmod and the wheel-packaging exclusion. _copy_template was
changed to chmod even on the skip path, which means a dtaas.toml generated
before this change stops being world-readable on the next run — a nice touch
that goes beyond what was asked. All three are gitignored, with the two shipped
templates correctly re-included by negation (git check-ignore confirms
cli/src/templates/dtaas.toml and users.csv remain tracked). The pat=
placeholder is gone from the template, which now steers to DTAAS_GITLAB_PAT
and explains why.

CI coverage of lib/ (previously blocking): lib/** added to both
workflows' push and pull_request filters.

CA-bundle passthrough. get_gitlab_ssl_verify returns a non-empty string
unchanged; verified end to end. A disabled-verification warning is now printed
to stderr naming what is exposed.

Temp-file exposure. os.open(..., O_CREAT|O_EXCL, 0o600) replaces
write-then-chmod. Verified: mode is 0600 at creation, and a pre-planted
symlink at the temp path is refused rather than followed.

Already-exists handling. ProvisionResult gained an already_exists flag,
the message explicitly states the account was not created by this run and its
credentials are unknown, and it is echoed unconditionally as a warning. No
token is issued.

Exit code. provision_gitlab_users returns the failed usernames and
gitlab_failure_exc turns them into an Exception that add_users propagates,
so a GitLab failure is non-zero while container work stands. A skipped
(password-less) user correctly does not count as a failure.

Retry path. set_gitlab_user_ids persists the GitLab numeric id, and a
retry issues the PAT directly against it rather than going through the
ambiguous 409. The mechanism is right; see H1 for the missing guard on top of
it.

--password exposure documented, with the CSV column and chmod 600
recommended for scripted use, and an explicit note that the CLI does not manage
permissions on a CSV path the operator supplies.

[gitlab] validation. _check_gitlab checks section shape,
provision type, ssl_verify type (bool or string), an empty-but-present
pat, and requires a valid api_url when provision is true — caught by
config validate before containers exist.

Direct-URL guard. set -o pipefail, explicit empty-output checks, and the
sdist's PKG-INFO now checked alongside the wheel's METADATA.

Vendoring provenance. Both build scripts append __source_version__ (the
git commit that last touched lib/gitlab_common) to the vendored
__init__.py, degrading to "unknown" outside a checkout, with a test.

lib/gitlab_common/poetry.lock added (792 lines).

Token file documented — location, 0600 mode, merge semantics, and that it
must be treated as a credential store.

@prasadtalasila

Copy link
Copy Markdown
Contributor

@prasadtalasila I have resolved the issues found by claude, and I fixed the qlty issues

please check the functionality of both dtaas and dtaas-services on the integration server.

@sonarqubecloud

Copy link
Copy Markdown

@8ohamed

8ohamed commented Aug 29, 2026

Copy link
Copy Markdown
Contributor Author

@prasadtalasila I fixed the issues, and tested the package on the server.
And for B1 (the removed tests) I did that because there's new test that already cover the code so I removed older tests.

@prasadtalasila

Copy link
Copy Markdown
Contributor

@8ohamed please keep the old tests as well. Are both packages tested on the server?
Thanks.

@8ohamed

8ohamed commented Aug 29, 2026

Copy link
Copy Markdown
Contributor Author

@8ohamed please keep the old tests as well. Are both packages tested on the server? Thanks.

Yes I have tested it for both

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants