Skip to content

organize part expects distinct subdirectories since 4.2.1 #2720

Description

@natalian98

Check existing issues

  • I've verified that this bug isn't described by any existing issues.

Bug description

Since the 4.2.1 charmcraft release, one of our charms (kratos-operator) started to fail when packing. Specifically this part:

parts:
  schemas:
    plugin: dump
    source: .
    organize:
      "claim_mappers/**": schemas/
      "identity_schemas/**": schemas/
    prime:
      - schemas/

It seems that we can't use the same schemas directory for more than one key anymore:

:: 2026-05-28 16:02:05.414 :: + cp --archive --link --no-dereference . /root/parts/schemas/install
:: 2026-05-28 16:02:05.426 Run pre-organize callbacks
:: 2026-05-28 16:02:05.429 Failed to organize part 'schemas': trying to organize 'identity_schemas/**' to 'schemas/', but 'schemas/' already exists.
:: 2026-05-28 16:02:05.438 Traceback (most recent call last):
:: 2026-05-28 16:02:05.438   File "/snap/charmcraft/7759/lib/python3.12/site-packages/craft_application/services/lifecycle.py", line 355, in run
:: 2026-05-28 16:02:05.438     self._exec(actions)
:: 2026-05-28 16:02:05.438   File "/snap/charmcraft/7759/lib/python3.12/site-packages/craft_application/services/lifecycle.py", line 379, in _exec
:: 2026-05-28 16:02:05.438     aex.execute(action, stdout=stream, stderr=stream)
:: 2026-05-28 16:02:05.438   File "/snap/charmcraft/7759/lib/python3.12/site-packages/craft_parts/executor/executor.py", line 353, in execute
:: 2026-05-28 16:02:05.438     self._executor.execute(actions, stdout=stdout, stderr=stderr)
:: 2026-05-28 16:02:05.438   File "/snap/charmcraft/7759/lib/python3.12/site-packages/craft_parts/executor/executor.py", line 153, in execute
:: 2026-05-28 16:02:05.438     self._run_action(act, stdout=stdout, stderr=stderr)
:: 2026-05-28 16:02:05.438   File "/snap/charmcraft/7759/lib/python3.12/site-packages/craft_parts/executor/executor.py", line 246, in _run_action
:: 2026-05-28 16:02:05.438     handler.run_action(action, stdout=stdout, stderr=stderr)
:: 2026-05-28 16:02:05.439   File "/snap/charmcraft/7759/lib/python3.12/site-packages/craft_parts/executor/part_handler.py", line 313, in run_action
:: 2026-05-28 16:02:05.439     state = handler(step_info, stdout=stdout, stderr=stderr)
:: 2026-05-28 16:02:05.439             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
:: 2026-05-28 16:02:05.439   File "/snap/charmcraft/7759/lib/python3.12/site-packages/craft_parts/executor/part_handler.py", line 487, in _run_build
:: 2026-05-28 16:02:05.439     organize_files(
:: 2026-05-28 16:02:05.439   File "/snap/charmcraft/7759/lib/python3.12/site-packages/craft_parts/executor/organize.py", line 129, in organize_files
:: 2026-05-28 16:02:05.439     raise errors.FileOrganizeError(
:: 2026-05-28 16:02:05.439 craft_parts.errors.FileOrganizeError: Failed to organize part 'schemas': trying to organize 'identity_schemas/**' to 'schemas/', but 'schemas/' already exists.

The charm was packing fine with 4.0.1.

With 4.2.1, it is packed successfully once switched to "claim_mappers/**": schemas/claim_mappers "identity_schemas/**": schemas/identity_schemas (distinct subdirectories) but I'm wondering if this was intentional.

Steps to reproduce

git clone https://github.com/canonical/kratos-operator.git && cd kratos-operator
charmcraft pack -v

Environment

ubuntu 24.04

charmcraft.yaml

# Copyright 2025 Canonical Ltd.
# See LICENSE file for licensing details.

name: kratos
type: charm
description: |
  Charmed Ory Kratos
summary: |
  Identity and user management system
links:
  documentation: https://canonical-identity.readthedocs-hosted.com/reference/charms/kratos
  source: https://github.com/canonical/kratos-operator
  issues: https://github.com/canonical/kratos-operator/issues
containers:
  kratos:
    resource: oci-image
resources:
  oci-image:
    type: oci-image
    description: Kratos oci-image
    upstream-source: ghcr.io/canonical/kratos:25.4.0
  verification-email-template:
    type: file
    filename: verification-email-template.gotmpl
    description: Custom email verification template to use with code method
requires:
  pg-database:
    interface: postgresql_client
    optional: false
  kratos-external-idp:
    interface: external_provider
    optional: true
  hydra-endpoint-info:
    interface: hydra_endpoints
    limit: 1
    optional: true
  ui-endpoint-info:
    interface: login_ui_endpoints
    optional: true
  kratos-registration-webhook:
    interface: kratos_registration_webhook
    optional: true
  kratos-login-webhook:
    interface: kratos_login_webhook
    optional: true
  logging:
    interface: loki_push_api
    optional: true
  tracing:
    interface: tracing
    limit: 1
    optional: true
    description: |
      Provides traces to COS Tempo instance
  smtp:
    interface: smtp
    limit: 1
    optional: true
  internal-route:
    interface: traefik_route
    limit: 1
    optional: true
    description: |
      Ingress used for cross-cluster communication where network topology is more complex
      than just one k8s cluster
  public-route:
    interface: traefik_route
    optional: true
    description: |
      Recommended way to expose the charm to the public internet
  receive-ca-cert:
    interface: certificate_transfer
    optional: true
    description: |
      Receive a CA cert.
      This relation can be used with a local CA to obtain the CA cert that was used to sign proxied
      endpoints.
peers:
  kratos-peers:
    interface: kratos-peers
provides:
  kratos-info:
    interface: kratos_info
    description: |
      Provides kratos deployment info to a related application
    optional: true
  metrics-endpoint:
    interface: prometheus_scrape
    description: |
      Provides application metrics to COS Prometheus instance
    optional: true
  grafana-dashboard:
    description: |
      Forwards the built-in grafana dashboard(s) for monitoring kratos.
    interface: grafana_dashboard
    optional: true
config:
  options:
    http_proxy:
      description: URL of the HTTP proxy eg http://proxy.internal:6666, it will set the HTTP_PROXY var in the workload environment
      type: string
      default: ''
    https_proxy:
      description: URL of the HTTPS proxy eg http://proxy.internal:6666, it will set the HTTPS_PROXY var in the workload environment
      type: string
      default: ''
    no_proxy:
      description: Domains that need to be excluded from proxying no_proxy="test.com,test.co.uk", it is a comma separate list
      type: string
      default: ''
    cpu:
      description: |
        K8s cpu resource limit, e.g. "1" or "500m". Default is unset (no limit). This value is used
        for the "limits" portion of the resource requirements (the "requests" portion is
        automatically deduced from it).
        See https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/
      type: string
    memory:
      description: |
        K8s memory resource limit, e.g. "1Gi". Default is unset (no limit). This value is used
        for the "limits" portion of the resource requirements (the "requests" portion is
        automatically deduced from it).
        See https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/
      type: string
    dev:
      description: Run Kratos on dev mode, it is needed if HTTPS is not set up. This should only be used for development purposes.
      type: boolean
      default: False
    enable_local_idp:
      description: Enable Kratos Identity Provider
      type: boolean
      default: True
    enforce_mfa:
      description: |
        Enforce users to set up and use multi factor authentication.
        Disabling this option will allow users to log in with password or webauthn without completing 2fa.
      type: boolean
      default: True
    enable_verification:
      description: |
        Enable and enforce users to verify their accounts.
        Enabling this option will make users verify their accounts after registration or on the next login.
        This option requires an SMTP server integration.
      type: boolean
      default: False
    sender_email:
      description: |
        The email address used as the sender for automated verification and recovery emails.
      type: string
      default: "identity@canonical.com"
    sender_name:
      description: |
        The display name that appears alongside the sender email address in recovery and verification emails.
      type: string
      default: "Canonical Identity Platform"
    enable_passwordless_login_method:
      description: |
        Enable passwordless authentication via webauthn. Requires `enable_local_idp=True`.
      type: boolean
      default: False
    enable_oidc_webauthn_sequencing:
      description: |
        Enforce setting up a WebAuthn key (e.g. with YubiKey or Google Password Manager on Android)
        after signing in with an external identity provider. Requires `enable_passwordless_login_method=False`.
        WARNING: Do not enable this option unless you are sure that this feature applies to your deployment.
      type: boolean
      default: False
    identity_schemas:
      description: |
        A mapping of schema_id to identity schemas. For example:
        {
          "user_v0": {
            "$id": "https://schemas.ory.sh/presets/kratos/quickstart/email-password/identity.schema.json",
            "$schema": "http://json-schema.org/draft-07/schema#",
            "title": "Person",
            "type": "object",
            "properties": {
              "traits": {
                "type": "object",
                "properties": {
                  "email": {
                    "type": "string",
                    "format": "email",
                    "title": "E-Mail",
                    "minLength": 3,
                    "ory.sh/kratos": {
                      "verification": {
                        "via": "email"
                      }
                    }
                  },
                  "name": {
                    "type": "string"
                  }
                }
              },
              "additionalProperties": true
            }
          }
        }
      type: string
    default_identity_schema_id:
      description: The default identity schema id, this option only works when `identity_schemas` is defined
      type: string
    recovery_email_template:
      description: |
        The custom html template used to send emails with recovery codes. For example:

        Hi,
        Please enter the following code to recover your account:
        {{ .RecoveryCode }}
      type: string
    log_level:
      description: |
        The verbosity of logs produced by Kratos.
        Available values are: panic, fatal, error, warn, info, debug, and trace.
      type: string
      default: "info"
actions:
  get-identity:
    description: Get a user using either the identity ID or the user email.
    params:
      identity-id:
        description: The Identity ID
        type: string
      email:
        description: The user's email
        type: string
  delete-identity:
    description: Delete a user using the identity ID or the user email.
    params:
      identity-id:
        description: The Identity ID
        type: string
      email:
        description: The user's email
        type: string
  create-admin-account:
    description: |
      Create an admin user. If no password was provided, the command will return a
      magic link where the user will be able to set their password.
    params:
      username:
        description: The admin username
        type: string
      email:
        description: |
          The admin's email, this email must not be associated with any other account
          (user or admin)
        type: string
      password-secret-id:
        description: The juju secret that contains the admin's password
        type: string
      name:
        description: The admin's name
        type: string
      phone-number:
        description: The admin's phone number
        type: string
    required:
      - username
      - email
  reset-password:
    description: |
      Reset password of an identity using the identity ID or the user email.
    params:
      identity-id:
        description: The Identity ID
        type: string
      email:
        description: The user's email
        type: string
      password-secret-id:
        description: |
          The juju secret that contains the password to set for an identity. If
          not provided, a self-service recovery link will be returned.
        type: string
  invalidate-identity-sessions:
    description: |
      Invalidate all user sessions using the identity ID or the user email.
    params:
      identity-id:
        description: The Identity ID
        type: string
      email:
        description: The user's email
        type: string
  reset-identity-mfa:
    description: |
      Reset identity's second authentication factor using the identity ID or the user email.
    params:
      identity-id:
        description: The Identity ID
        type: string
      email:
        description: The user's email
        type: string
      mfa-type:
        description: The type of credentials to be removed, one of `totp`, `lookup_secret` or `webauthn`.
        type: string
        enum:
          - totp
          - lookup_secret
          - webauthn
    required:
      - mfa-type
  list-oidc-accounts:
    description: |
      Retrieve the list of OIDC accounts identifiers linked to an identity using the identity ID or the user email.
    params:
      identity-id:
        description: The Identity ID
        type: string
      email:
        description: The user's email
        type: string
  unlink-oidc-account:
    description: |
      Unlink a user's external identity provider account from their identity using the identity ID or the user email.
    params:
      identity-id:
        description: The Identity ID
        type: string
      email:
        description: The user's email
        type: string
      credential-id:
        description: The OIDC credential ID to unlink. Run `list-identity-linked-oidc-accounts` to list available credentials.
        type: string
    required:
      - credential-id
  run-migration:
    description: |
      Run a migration, this is needed after upgrades. This is a non-reversible operation.
      Run this after backing up the database.
    params:
      timeout:
        description: Timeout after which the migration will be canceled
        type: number
        default: 120
########################### build ###########################
platforms:
  ubuntu@22.04:amd64:
parts:
  schemas:
    plugin: dump
    source: .
    organize:
      "claim_mappers/**": schemas/
      "identity_schemas/**": schemas/
    prime:
      - schemas/
  charm:
    charm-binary-python-packages:
      - jsonschema
      - bcrypt
      - "setuptools>=70.0.0"
    build-packages:
      - "rustc-1.85"
      - "cargo-1.85"

Log output

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions