ImportTechnicalAccountsFromCSVExecutor

Last modified by AntonĂ­n Otmar on 2026/09/17 16:43

Long Running Task (LRT) that bulk-creates or updates Technical Accounts in CzechIdM by reading a semicolon-separated CSV file. Each CSV row represents one technical account. The task maps configurable column names to entity fields, validates the whole row before it writes anything, supports dry-run mode, and never re-points an existing account to a different provisioning system.

Version

VersionCompatible with productNotes
15.3.015.15.1First implementation of the task.

How It Works

  • The task reads the attached CSV file line by line (first line = header).
  • For each data row it looks up an existing Technical Account by the value in the identifier column (matched against code) scoped to the system named in systemcolumn. The same code may legally exist on different systems - the lookup returns only the account linked to the row's system, if any.
  • Depending on whether the account exists and which behaviour flags are set, the account is created, updated or skipped.
  • Before anything is written, the whole row is validated in one gate. Every problem found in the row is reported together and the row is aborted without a partial write.
  • Optional columns assign identity and role guarantors, link the account to a provisioning system and associate it with a Technical Asset.
  • All item-level results are written to the LRT task log.

CSV Format

PropertyValue
EncodingUTF-8
Column separator; (configurable)
First rowHeader with column names
Multi-values| by default (configurable)
Date formatyyyy-MM-dd (ISO 8601)
Information

 Only the identifier column is mandatory. All other columns are optional - the task reads a column only when the corresponding parameter is configured and the column exists in the CSV header. Configuring a column name that is absent from the header aborts the whole task with an error listing the missing columns.

The header check runs on the first data row, so a header-only file is accepted with any column configuration, and it validates column-name parameters only - multivalueseparator and the behaviour flags are not column names. 

Task Parameters

File and Format

ParameterTypeRequiredDefaultDescription
importFileAttachmentYes-CSV file to import
separatorStringNo;Column separator character
encodingStringNoutf-8File encoding

Column Mapping

Each of the column parameters below is the exact name of a column in the CSV header (case-sensitive). multivalueseparator is the exception - it is a value, not a column name.

ParameterDescription
identifiercolumnRequired. Unique identifier of the account; stored as code and used as the uid of the AccAccount created for a new account. Used to look up existing accounts.
namecolumnTechnical account name (maps to externalCode - the Name field in the UI). Written unconditionally when the parameter is configured, so a blank cell clears the existing name on update. Leaving the parameter unconfigured keeps the current value.
descriptioncolumnFree-text description. Same blank-cell rule as namecolumn - a blank cell clears the value.
validfromcolumnValidity start date (yyyy-MM-dd). A blank cell leaves the field unchanged.
validtillcolumnValidity end date (yyyy-MM-dd). A blank cell leaves the field unchanged.
systemcolumnName (codeable identifier) of the provisioning system. Required when creating new accounts; for existing accounts it is a consistency check, see below.
mappingcolumnProvisioning mapping name. Must be a TECHNICAL_ACCOUNT PROVISIONING mapping on the system.
directidentitycolumnIdentity guarantors - list of usernames separated by the multi-value separator.
guaranteerolecolumnRole guarantors - list of role codes separated by the multi-value separator.
technicalassetcolumnCode of the Technical Asset to associate with the account.
tiercolumnTier written to the AccAccount linked to the account, for both new and existing accounts. A blank cell or an unconfigured column keeps the current value; it never clears it.
zonecolumnZone written to the AccAccount linked to the account, for both new and existing accounts. Same blank-cell rule as tiercolumn.
disabledcolumnDisabled status of the account. `1`, `true` and `yes` mean disabled, `0`, `false` and `no` mean enabled; matching is case-insensitive and the cell is trimmed. A blank cell leaves the value unchanged. Any other value fails the whole row.
multivalueseparatorCharacter separating values in multi-value columns. Default |.

Behaviour Flags

All flags are of type Boolean and default to false. All of the flags are required - setting them to blank is not valid and the import will not run.

ParameterTypeDefaultDescription
cancreateBooleanfalseAllow creating new Technical Accounts. When false, rows whose identifier is not found are logged as skipped.
canoverwriteBooleanfalseAllow updating existing accounts. When false, an existing account is skipped entirely - no field, no guarantor and no AccAccount change. When true, every configured column of the row is applied: externalCode, description, validFrom, validTill, disabled, the technical asset (still subject to canchangeta) and the tier and zone of the linked AccAccount; guarantors from the row are added as well.
canchangetaBooleanfalseGates a change of the technical asset on an already existing account. It does not switch the column off - see Technical Asset Assignment.

Processing Logic per Row

read identifier from identifiercolumn
read system name from systemcolumn (if configured)
 |- find existing TechnicalAccount by code = identifier
 |    (scoped to the system from systemcolumn - see Code and Name Uniqueness Rules)
 |- NOT FOUND + cancreate = false   ->  log CSV_IMPORT_ACCOUNT_SKIPPED (EXCEPTION), stop
 |- FOUND + canoverwrite = false    ->  log CSV_IMPORT_ACCOUNT_SKIPPED (EXCEPTION), stop

validatePreSave - one gate, nothing is written yet
 |- new account: resolve system and mapping (mandatory), check the code is free on that system
 |- existing account: check the linked AccAccount against systemcolumn / mappingcolumn
 |- resolve every identity guarantor and every role guarantor
 |- resolve the technical asset by code, apply the canchangeta rule
 |- parse the disabled cell
 |- run the asset assignment check script for the resulting (asset, account) pair
 |- any problem  ->  all problems joined into one CSV_IMPORT_ACCOUNT_ERROR, row aborted

write
 |- existing account: tier / zone written to the linked AccAccount and saved FIRST
 |- save the technical account (the technical asset is already set on it)
 |- new account: create the AccAccount (uid = identifier) and the TechnicalAccountAccount link
 |- add identity and role guarantors
Information

 Every reference in the row is resolved before the first write. An identity username, a role code or a technical asset code that cannot be resolved aborts the row with CSV_IMPORT_ACCOUNT_ERROR - the account is neither created nor updated and no guarantor is assigned. All problems found in the row are reported in a single message. The import continues with the following rows. 

Multi-Value Columns

directidentitycolumn and guaranteerolecolumn can contain multiple values separated by the multi-value separator (default `|`). Whitespace around each value is trimmed.

Example cell value:

jnovak|pdvorak|mmaly

Each value is resolved independently, but a value that cannot be resolved does not get skipped - it fails the whole row together with every other problem found in it.

Success

 Enter the separator as a plain character (`|`, `,`, `;`, `#`). The task escapes it for its internal regular expression itself, so a regex metacharacter is matched literally and must not be pre-escaped by the operator. An already escaped value would be escaped again on the next run and splitting would break. 

Warning

 The task only adds guarantors - it never removes them. Guarantors assigned previously, manually or by an earlier import, are left untouched regardless of what the current CSV contains. 

System Linking Rules

Every Technical Account is expected to have exactly one AccAccount. The link is maintained via the TechnicalAccountAccount join entity.

Creating a New Account

When cancreate = true and the identifier is not found:

  • systemcolumn is mandatory. If it is not configured or the system is not found in CzechIdM, the row fails with CSV_IMPORT_ACCOUNT_ERROR.
  • If mappingcolumn is configured, the named mapping must exist on the system and must be of type TECHNICAL_ACCOUNT PROVISIONING. Otherwise the row fails.
  • If mappingcolumn is not configured, the system must have exactly one TECHNICAL_ACCOUNT PROVISIONING mapping. Zero or more than one also fails the row.
  • Once system and mapping are validated, a new AccAccount is created (uid = identifier) and linked via TechnicalAccountAccount.
ScenarioBehaviour
systemcolumn not configuredError - row fails with CSV_IMPORT_ACCOUNT_ERROR.
System name not found in CzechIdMError - row fails with CSV_IMPORT_ACCOUNT_ERROR.
Mapping specified, not found on systemError - row fails with CSV_IMPORT_ACCOUNT_ERROR.
Mapping specified, found but not TECHNICAL_ACCOUNT typeError - row fails with CSV_IMPORT_ACCOUNT_ERROR.
Mapping not specified, no TECHNICAL_ACCOUNT mapping on systemError - row fails with CSV_IMPORT_ACCOUNT_ERROR.
Mapping not specified, multiple TECHNICAL_ACCOUNT mappings on systemError - specify the mapping explicitly; row fails with CSV_IMPORT_ACCOUNT_ERROR.
System and mapping resolved successfullyNew AccAccount (uid = identifier) created; TechnicalAccountAccount link saved.

Updating an Existing Account

The system, mapping and uid of the linked AccAccount are never changed and no new AccAccount or link is created. The tier and zone of the linked AccAccount are written when the corresponding columns are configured.

The systemcolumn and mappingcolumn values are a hard consistency check: a mismatch fails the row.

ScenarioBehaviour
systemcolumn not configured or the cell is blankSystem check skipped.
System name not found in CzechIdMThe lookup degrades to code-only, so the existing account is still found, and the row then fails on the system mismatch with CSV_IMPORT_ACCOUNT_ERROR.
AccAccount is on a different system than specifiedRow fails with CSV_IMPORT_ACCOUNT_ERROR; nothing is written.
mappingcolumn not configuredMapping check skipped.
AccAccount uses a different mapping than specifiedRow fails with CSV_IMPORT_ACCOUNT_ERROR; nothing is written.
The account has no linked AccAccount (orphan) or the link is danglingThe whole consistency check is skipped, tier and zone are not written and the row succeeds.
Warning

 Why is the AccAccount system and mapping never changed?
If a Technical Account already exists in CzechIdM, its provisioning links were set up previously, manually or by an earlier import run. Re-pointing the AccAccount to another system or mapping could make CzechIdM's provisioning engine act on a live system. Tier and zone are the deliberate exception - they are attributes of the account, not of its link to a system. 

Error

 A row that updates an existing account is written in two steps and they are not covered by one transaction.
The linked AccAccount (tier, zone) is saved first, the technical account second. The order is deliberate: saving the technical account triggers provisioning, which re-saves the AccAccount in its own event where the skip flag no longer applies, so the account has to carry the target state by then. A failure that the pre-save gate cannot predict - a duplicate externalCode is the realistic example, because it is validated by the framework at save time - therefore leaves the AccAccount already updated while the technical account is not. 

Technical Asset Assignment

Technical assets are looked up by their code (TechnicalAsset.code), not by the Name field. An asset code that cannot be resolved fails the row.

canchangeta does not switch the column off. The column is always read and the asset always resolved; the flag only gates a real change on an already existing account.

ScenarioBehaviour
New account, any value of canchangetaThe asset from the CSV is assigned. The flag does not apply - there is no current asset to change.
Existing account, cell emptyNo-op, the row succeeds and the current asset is kept.
Existing account, asset equals the currently assigned oneNo-op, the row succeeds.
Existing account, asset differs, canchangeta = falseRow fails with CSV_IMPORT_ACCOUNT_ERROR; nothing from the row is written.
Existing account, asset differs, canchangeta = trueThe asset is changed.
Asset code not foundRow fails with CSV_IMPORT_ACCOUNT_ERROR.

Assignment Check Script

Whenever a row ends up with a technical asset, the pair (asset, target AccAccount) is validated by a configurable Groovy script through TechnicalAssetService.isAssetAssignable. The script is named by the private configuration property idm.sec.tech.accountAssetAssignment.checkScript.

# Script deciding whether a technical asset may be assigned to an account.
# Default: nothing is enforced out of the box.
idm.sec.tech.accountAssetAssignment.checkScript=techAccountAssetAssignmentAllowAll
  • techAccountAssetAssignmentAllowAll - the shipped default, always returns true.
  • techAccountAssetAssignmentZoneMatch - requires the asset zone and the AccAccount zone to match; an asset without a zone is always accepted.

The check is evaluated against the effective asset (the one from the CSV, or the one already assigned when the CSV does not change it, so it also runs on rows that do not mention an asset) and the target AccAccount, that is the account as it will look once the row's tier and zone are applied. When the script rejects the pair, the row fails with CSV_IMPORT_ACCOUNT_ERROR before anything is written.

Information

 The task suppresses the runtime assignment-check processors on its own saves (event property SKIP_ACCOUNT_ASSET_ASSIGNMENT_CHECK). That is why it has to evaluate the script itself up front: the row is written in two steps and the intermediate state between them would otherwise be rejected. The verdict is the same one the processors would give, only it is known before the first write and it holds in dry-run as well. 

Code and Name Uniqueness Rules

Code (''identifiercolumn'' -> ''code'')

  • code is unique per system. The same code may exist on different systems - each combination (code, system) yields its own Technical Account.
  • The lookup combines code with the system named in systemcolumn. An account linked to a different system is invisible to the lookup, so a new account is created for the row's system.
  • The lookup degrades to code-only in three cases: systemcolumn is not configured, the cell is blank, or the system named in the cell does not exist in CzechIdM. Uniqueness is then not enforced either - there is no system context to validate against, so "orphan" accounts (those without an AccAccount link) are not constrained on code at all and several orphans with the same code may coexist.
  • For a new account the task checks up front that the code is free on the target system and fails the row otherwise. The product-side processors (TechnicalAccountCodeCheckProcessor, TechnicalAccountAccountCodeCheckProcessor) are the second line of defence; on the import's create path only the latter can still raise TECHNICAL_ACCOUNT_ALREADY_EXISTS_ON_SYSTEM, when the link is saved.

Name (''namecolumn'' -> ''externalCode'')

  • externalCode (FE label "Name") must be unique across all Technical Accounts in CzechIdM, regardless of which system the account is linked to.
  • A blank namecolumn cell (resulting in an empty externalCode) skips this validation.
  • Enforced by the framework in AbstractReadWriteDtoService.validateEntity. The conflict surfaces as DuplicateExternalCodeException at save time, which the task records on the item as CSV_IMPORT_ACCOUNT_ERROR.
ScenarioOutcome
Same code on two different systems in the CSVOK - two separate Technical Accounts are created, one per system.
Same code on the same system in the CSV, canoverwrite = falseSecond row is skipped - the first row's account is found and not overwritten.
Same code on the same system in the CSV, canoverwrite = trueSecond row updates the first row's account.
Same name on two rows (any systems)Second row fails - externalCode is globally unique.
systemcolumn blank, identifier matches an orphan accountThe lookup finds the orphan and proceeds with the update.
Warning

 Practical impact for CSV imports: the same code may appear on several rows targeting different systems and each row creates its own account.
Each row must use a globally unique name, or leave namecolumn blank. Re-running the import with names already present in the database fails those rows as well. 

Dry-Run Mode

The task supports CzechIdM's built-in dry-run flag. When dry-run is enabled:

  • No Technical Accounts are created or updated.
  • No guarantors are assigned.
  • No AccAccounts or TechnicalAccountAccount links are created and no tier or zone is written.
  • Rows that would be created or updated are logged with CSV_IMPORT_ACCOUNT_WOULD_BE_CREATED or CSV_IMPORT_ACCOUNT_WOULD_BE_UPDATED and state NOT_EXECUTED.
  • Skipped and failing rows are logged exactly as in a real run, with state EXCEPTION - a dry run therefore reports the same failures a real run would hit, including a rejection by the assignment check script.
  • Would-be operations are written to the application log at INFO level (prefix [DRY-RUN]).
  • The task counter still increments, so the row count can be verified.
Success

 To execute a dry run, enable the Dry run toggle when scheduling the task in the UI, or set dryRun = true on the IdmLongRunningTaskDto before calling LongRunningTaskManager.executeSync() in code. 

Result Codes

CodeStateCounted asDescription
CSV_IMPORT_ACCOUNT_CREATEDEXECUTEDsuccessItemCountTechnical account was created.
CSV_IMPORT_ACCOUNT_UPDATEDEXECUTEDsuccessItemCountTechnical account was updated.
CSV_IMPORT_ACCOUNT_WOULD_BE_CREATEDNOT_EXECUTEDwarningItemCountDry-run: the account would be created.
CSV_IMPORT_ACCOUNT_WOULD_BE_UPDATEDNOT_EXECUTEDwarningItemCountDry-run: the account would be updated.
CSV_IMPORT_ACCOUNT_SKIPPEDEXCEPTIONfailedItemCountRow skipped (cancreate = false or canoverwrite = false).
CSV_IMPORT_ACCOUNT_ERROREXCEPTIONfailedItemCountRow failed. This is the single code for every row-level failure - unresolved guarantors or asset, an unknown or mismatched system or mapping, a non-boolean disabled value, a would-be asset change with canchangeta = false, a duplicate code, a rejection by the assignment check script, and any exception raised while the row was being written. The original message is carried as the reason.
Information

 The counters follow the operation state, not the HTTP status: EXECUTED and CREATED count as success, EXCEPTION as failed and everything else, including NOT_EXECUTED, as a warning. 

Examples

Minimal CSV Example

Only identifier and name columns - no system, guarantors or asset.

identifiercolumn;namecolumn
svc-api-gateway;API Gateway
svc-db-reader;DB Reader
svc-batch-runner;Batch Runner

Task configuration:

ParameterValue
identifiercolumnidentifiercolumn
namecolumnnamecolumn
cancreatetrue
canoverwritefalse

Full CSV Example

identifiercolumn;namecolumn;descriptioncolumn;validfromcolumn;validtillcolumn;systemcolumn;mappingcolumn;directidentitycolumn;guaranteerolecolumn;technicalassetcolumn
svc-ldap-reader;LDAP Reader;LDAP read-only service account;2024-01-01;;CMS AD - Users;AD users provisioning mapping tech.;jnovak|pdvorak;role-ldap-admin|role-infra-team;CORE-INFRA
svc-db-backup;DB Backup;Database backup service account;2024-01-01;2026-12-31;CMS AD - Users;AD users provisioning mapping tech.;mpolak;role-dba;DATABASES

Task configuration:

ParameterValue
identifiercolumnidentifiercolumn
namecolumnnamecolumn
descriptioncolumndescriptioncolumn
validfromcolumnvalidfromcolumn
validtillcolumnvalidtillcolumn
systemcolumnsystemcolumn
mappingcolumnmappingcolumn
directidentitycolumndirectidentitycolumn
guaranteerolecolumnguaranteerolecolumn
technicalassetcolumntechnicalassetcolumn
multivalueseparator|
cancreatetrue
canoverwritetrue
canchangetatrue
Success

 The technicalassetcolumn cell holds the asset code, not its name. A blank validtillcolumn cell (like the first row above) leaves the field unchanged - it does not clear an existing validTill date. 

See Also