infrahub-server 1.1.9__py3-none-any.whl → 1.2.0__py3-none-any.whl
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.
- infrahub/api/artifact.py +16 -4
- infrahub/api/dependencies.py +14 -6
- infrahub/api/diff/validation_models.py +7 -7
- infrahub/api/oauth2.py +0 -1
- infrahub/api/oidc.py +0 -1
- infrahub/api/query.py +18 -7
- infrahub/api/schema.py +33 -7
- infrahub/api/transformation.py +12 -5
- infrahub/{message_bus/messages/check_artifact_create.py → artifacts/models.py} +6 -6
- infrahub/{message_bus/operations/check/artifact.py → artifacts/tasks.py} +27 -28
- infrahub/cli/__init__.py +12 -10
- infrahub/cli/constants.py +3 -0
- infrahub/cli/db.py +166 -185
- infrahub/cli/events.py +8 -3
- infrahub/cli/git_agent.py +9 -7
- infrahub/cli/tasks.py +4 -6
- infrahub/cli/upgrade.py +146 -0
- infrahub/computed_attribute/gather.py +174 -0
- infrahub/computed_attribute/models.py +202 -11
- infrahub/computed_attribute/tasks.py +103 -421
- infrahub/computed_attribute/triggers.py +56 -0
- infrahub/config.py +33 -33
- infrahub/context.py +53 -0
- infrahub/core/account.py +9 -12
- infrahub/core/attribute.py +104 -75
- infrahub/core/branch/models.py +4 -4
- infrahub/core/branch/tasks.py +133 -125
- infrahub/core/changelog/__init__.py +0 -0
- infrahub/core/changelog/diff.py +291 -0
- infrahub/core/changelog/models.py +662 -0
- infrahub/core/constants/__init__.py +47 -2
- infrahub/core/constants/infrahubkind.py +3 -0
- infrahub/core/constants/schema.py +2 -0
- infrahub/core/constraint/node/runner.py +2 -2
- infrahub/core/diff/branch_differ.py +10 -10
- infrahub/core/diff/combiner.py +1 -1
- infrahub/core/diff/enricher/cardinality_one.py +1 -1
- infrahub/core/diff/enricher/hierarchy.py +5 -3
- infrahub/core/diff/enricher/labels.py +1 -1
- infrahub/core/diff/enricher/path_identifier.py +1 -2
- infrahub/core/diff/enricher/summary_counts.py +107 -0
- infrahub/core/diff/ipam_diff_parser.py +4 -5
- infrahub/core/diff/merger/merger.py +3 -1
- infrahub/core/diff/model/diff.py +27 -27
- infrahub/core/diff/model/path.py +13 -13
- infrahub/core/diff/query/all_conflicts.py +1 -1
- infrahub/core/diff/query/artifact.py +1 -1
- infrahub/core/diff/query/delete_query.py +1 -1
- infrahub/core/diff/query/diff_get.py +1 -1
- infrahub/core/diff/query/diff_summary.py +1 -1
- infrahub/core/diff/query/field_specifiers.py +1 -1
- infrahub/core/diff/query/field_summary.py +1 -1
- infrahub/core/diff/query/filters.py +2 -2
- infrahub/core/diff/query/get_conflict_query.py +1 -1
- infrahub/core/diff/query/has_conflicts_query.py +1 -1
- infrahub/core/diff/query/merge.py +3 -3
- infrahub/core/diff/query/merge_tracking_id.py +1 -1
- infrahub/core/diff/query/roots_metadata.py +1 -1
- infrahub/core/diff/query/save.py +3 -3
- infrahub/core/diff/query/summary_counts_enricher.py +2 -2
- infrahub/core/diff/query/time_range_query.py +1 -1
- infrahub/core/diff/query/update_conflict_query.py +1 -1
- infrahub/core/diff/query_parser.py +4 -4
- infrahub/core/diff/repository/deserializer.py +1 -1
- infrahub/core/diff/tasks.py +9 -8
- infrahub/core/enums.py +1 -1
- infrahub/core/initialization.py +1 -10
- infrahub/core/integrity/object_conflict/conflict_recorder.py +1 -1
- infrahub/core/ipam/constants.py +3 -4
- infrahub/core/ipam/reconciler.py +13 -13
- infrahub/core/ipam/tasks.py +2 -3
- infrahub/core/ipam/utilization.py +10 -13
- infrahub/core/manager.py +52 -47
- infrahub/core/merge.py +12 -9
- infrahub/core/migrations/__init__.py +1 -3
- infrahub/core/migrations/graph/__init__.py +5 -3
- infrahub/core/migrations/graph/m001_add_version_to_graph.py +1 -1
- infrahub/core/migrations/graph/m002_attribute_is_default.py +2 -2
- infrahub/core/migrations/graph/m003_relationship_parent_optional.py +2 -2
- infrahub/core/migrations/graph/m004_add_attr_documentation.py +1 -1
- infrahub/core/migrations/graph/m005_add_rel_read_only.py +1 -1
- infrahub/core/migrations/graph/m006_add_rel_on_delete.py +1 -1
- infrahub/core/migrations/graph/m007_add_rel_allow_override.py +1 -1
- infrahub/core/migrations/graph/m008_add_human_friendly_id.py +1 -1
- infrahub/core/migrations/graph/m009_add_generate_profile_attr.py +1 -1
- infrahub/core/migrations/graph/m010_add_generate_profile_attr_generic.py +1 -1
- infrahub/core/migrations/graph/m011_remove_profile_relationship_schema.py +2 -2
- infrahub/core/migrations/graph/m012_convert_account_generic.py +12 -23
- infrahub/core/migrations/graph/m013_convert_git_password_credential.py +7 -11
- infrahub/core/migrations/graph/m014_remove_index_attr_value.py +2 -2
- infrahub/core/migrations/graph/m015_diff_format_update.py +1 -1
- infrahub/core/migrations/graph/m016_diff_delete_bug_fix.py +1 -1
- infrahub/core/migrations/graph/m017_add_core_profile.py +2 -6
- infrahub/core/migrations/graph/m018_uniqueness_nulls.py +3 -3
- infrahub/core/migrations/graph/m019_restore_rels_to_time.py +4 -4
- infrahub/core/migrations/graph/m020_duplicate_edges.py +3 -3
- infrahub/core/migrations/graph/m021_missing_hierarchy_merge.py +2 -2
- infrahub/core/migrations/graph/m022_add_generate_template_attr.py +48 -0
- infrahub/core/migrations/query/attribute_add.py +3 -3
- infrahub/core/migrations/query/attribute_rename.py +1 -1
- infrahub/core/migrations/query/delete_element_in_schema.py +1 -1
- infrahub/core/migrations/query/node_duplicate.py +1 -1
- infrahub/core/migrations/query/relationship_duplicate.py +1 -1
- infrahub/core/migrations/query/schema_attribute_update.py +3 -3
- infrahub/core/migrations/schema/models.py +19 -4
- infrahub/core/migrations/schema/node_attribute_remove.py +1 -1
- infrahub/core/migrations/schema/node_remove.py +1 -1
- infrahub/core/migrations/schema/tasks.py +7 -7
- infrahub/core/migrations/shared.py +10 -12
- infrahub/core/models.py +13 -14
- infrahub/core/node/__init__.py +172 -57
- infrahub/core/node/base.py +3 -5
- infrahub/core/node/constraints/attribute_uniqueness.py +2 -2
- infrahub/core/node/constraints/grouped_uniqueness.py +5 -5
- infrahub/core/node/constraints/interface.py +1 -2
- infrahub/core/node/delete_validator.py +7 -9
- infrahub/core/node/ipam.py +10 -10
- infrahub/core/node/permissions.py +7 -7
- infrahub/core/node/resource_manager/ip_address_pool.py +6 -6
- infrahub/core/node/resource_manager/ip_prefix_pool.py +14 -11
- infrahub/core/node/resource_manager/number_pool.py +3 -3
- infrahub/core/node/standard.py +3 -5
- infrahub/core/path.py +12 -12
- infrahub/core/property.py +12 -12
- infrahub/core/protocols.py +11 -0
- infrahub/core/protocols_base.py +25 -23
- infrahub/core/query/__init__.py +35 -38
- infrahub/core/query/attribute.py +13 -13
- infrahub/core/query/branch.py +5 -5
- infrahub/core/query/delete.py +1 -1
- infrahub/core/query/diff.py +7 -7
- infrahub/core/query/ipam.py +4 -4
- infrahub/core/query/node.py +23 -24
- infrahub/core/query/relationship.py +143 -46
- infrahub/core/query/resource_manager.py +10 -10
- infrahub/core/query/standard_node.py +9 -9
- infrahub/core/query/subquery.py +9 -9
- infrahub/core/query/task.py +3 -3
- infrahub/core/query/task_log.py +1 -1
- infrahub/core/query/utils.py +5 -5
- infrahub/core/registry.py +13 -17
- infrahub/core/relationship/constraints/count.py +4 -5
- infrahub/core/relationship/constraints/peer_kind.py +4 -5
- infrahub/core/relationship/constraints/profiles_kind.py +2 -2
- infrahub/core/relationship/model.py +103 -67
- infrahub/core/schema/__init__.py +6 -4
- infrahub/core/schema/attribute_schema.py +16 -8
- infrahub/core/schema/basenode_schema.py +38 -26
- infrahub/core/schema/computed_attribute.py +3 -3
- infrahub/core/schema/definitions/core/__init__.py +147 -0
- infrahub/core/schema/definitions/core/account.py +171 -0
- infrahub/core/schema/definitions/core/artifact.py +136 -0
- infrahub/core/schema/definitions/core/builtin.py +24 -0
- infrahub/core/schema/definitions/core/check.py +68 -0
- infrahub/core/schema/definitions/core/core.py +17 -0
- infrahub/core/schema/definitions/core/generator.py +100 -0
- infrahub/core/schema/definitions/core/graphql_query.py +79 -0
- infrahub/core/schema/definitions/core/group.py +108 -0
- infrahub/core/schema/definitions/core/ipam.py +193 -0
- infrahub/core/schema/definitions/core/lineage.py +19 -0
- infrahub/core/schema/definitions/core/menu.py +48 -0
- infrahub/core/schema/definitions/core/permission.py +163 -0
- infrahub/core/schema/definitions/core/profile.py +18 -0
- infrahub/core/schema/definitions/core/propose_change.py +97 -0
- infrahub/core/schema/definitions/core/propose_change_comment.py +193 -0
- infrahub/core/schema/definitions/core/propose_change_validator.py +328 -0
- infrahub/core/schema/definitions/core/repository.py +286 -0
- infrahub/core/schema/definitions/core/resource_pool.py +170 -0
- infrahub/core/schema/definitions/core/template.py +27 -0
- infrahub/core/schema/definitions/core/transform.py +96 -0
- infrahub/core/schema/definitions/core/webhook.py +134 -0
- infrahub/core/schema/definitions/internal.py +32 -16
- infrahub/core/schema/dropdown.py +3 -4
- infrahub/core/schema/generated/attribute_schema.py +15 -18
- infrahub/core/schema/generated/base_node_schema.py +12 -14
- infrahub/core/schema/generated/genericnode_schema.py +5 -0
- infrahub/core/schema/generated/node_schema.py +8 -5
- infrahub/core/schema/generated/relationship_schema.py +9 -11
- infrahub/core/schema/generic_schema.py +6 -2
- infrahub/core/schema/manager.py +46 -43
- infrahub/core/schema/node_schema.py +6 -2
- infrahub/core/schema/profile_schema.py +4 -0
- infrahub/core/schema/relationship_schema.py +15 -7
- infrahub/core/schema/schema_branch.py +423 -89
- infrahub/core/schema/schema_branch_computed.py +41 -4
- infrahub/core/schema/template_schema.py +36 -0
- infrahub/core/task/task.py +3 -3
- infrahub/core/task/user_task.py +21 -19
- infrahub/core/timestamp.py +3 -3
- infrahub/core/utils.py +12 -12
- infrahub/core/validators/__init__.py +1 -3
- infrahub/core/validators/aggregated_checker.py +2 -2
- infrahub/core/validators/attribute/choices.py +3 -3
- infrahub/core/validators/attribute/enum.py +3 -3
- infrahub/core/validators/attribute/kind.py +3 -3
- infrahub/core/validators/attribute/length.py +3 -3
- infrahub/core/validators/attribute/optional.py +3 -3
- infrahub/core/validators/attribute/regex.py +3 -3
- infrahub/core/validators/attribute/unique.py +3 -3
- infrahub/core/validators/checks_runner.py +60 -0
- infrahub/core/validators/determiner.py +1 -3
- infrahub/core/validators/model.py +1 -3
- infrahub/core/validators/models/validate_migration.py +17 -4
- infrahub/core/validators/node/attribute.py +2 -2
- infrahub/core/validators/node/generate_profile.py +3 -3
- infrahub/core/validators/node/hierarchy.py +3 -3
- infrahub/core/validators/node/inherit_from.py +2 -2
- infrahub/core/validators/node/relationship.py +2 -2
- infrahub/core/validators/query.py +1 -1
- infrahub/core/validators/relationship/count.py +5 -5
- infrahub/core/validators/relationship/optional.py +3 -3
- infrahub/core/validators/relationship/peer.py +3 -3
- infrahub/core/validators/shared.py +2 -2
- infrahub/core/validators/tasks.py +8 -6
- infrahub/core/validators/uniqueness/checker.py +5 -6
- infrahub/core/validators/uniqueness/index.py +2 -2
- infrahub/core/validators/uniqueness/model.py +11 -11
- infrahub/core/validators/uniqueness/query.py +1 -1
- infrahub/database/__init__.py +19 -23
- infrahub/database/memgraph.py +1 -1
- infrahub/dependencies/builder/diff/combiner.py +1 -1
- infrahub/dependencies/builder/diff/conflicts_enricher.py +1 -1
- infrahub/dependencies/builder/diff/deserializer.py +1 -1
- infrahub/dependencies/builder/diff/enricher/summary_counts.py +8 -0
- infrahub/dependencies/builder/diff/parent_node_adder.py +1 -1
- infrahub/dependencies/component/registry.py +2 -2
- infrahub/events/__init__.py +25 -2
- infrahub/events/artifact_action.py +64 -0
- infrahub/events/branch_action.py +57 -20
- infrahub/events/generator.py +71 -0
- infrahub/events/group_action.py +103 -0
- infrahub/events/models.py +160 -53
- infrahub/events/node_action.py +140 -23
- infrahub/events/repository_action.py +7 -20
- infrahub/events/schema_action.py +18 -10
- infrahub/events/utils.py +16 -0
- infrahub/events/validator_action.py +55 -0
- infrahub/exceptions.py +12 -3
- infrahub/generators/models.py +2 -3
- infrahub/generators/tasks.py +34 -15
- infrahub/git/base.py +10 -12
- infrahub/git/constants.py +0 -1
- infrahub/git/integrator.py +82 -57
- infrahub/git/models.py +101 -9
- infrahub/git/repository.py +9 -10
- infrahub/git/tasks.py +450 -112
- infrahub/git/utils.py +48 -0
- infrahub/git/worktree.py +1 -2
- infrahub/git_credential/askpass.py +1 -2
- infrahub/git_credential/helper.py +2 -3
- infrahub/graphql/analyzer.py +572 -11
- infrahub/graphql/app.py +47 -41
- infrahub/graphql/auth/query_permission_checker/anonymous_checker.py +5 -5
- infrahub/graphql/auth/query_permission_checker/default_branch_checker.py +4 -4
- infrahub/graphql/auth/query_permission_checker/merge_operation_checker.py +4 -4
- infrahub/graphql/auth/query_permission_checker/object_permission_checker.py +28 -35
- infrahub/graphql/auth/query_permission_checker/super_admin_checker.py +5 -5
- infrahub/graphql/context.py +39 -0
- infrahub/graphql/enums.py +1 -1
- infrahub/graphql/initialization.py +5 -1
- infrahub/graphql/loaders/node.py +1 -1
- infrahub/graphql/loaders/shared.py +1 -1
- infrahub/graphql/manager.py +75 -72
- infrahub/graphql/mutations/account.py +20 -13
- infrahub/graphql/mutations/artifact_definition.py +18 -14
- infrahub/graphql/mutations/branch.py +86 -40
- infrahub/graphql/mutations/computed_attribute.py +26 -18
- infrahub/graphql/mutations/diff.py +17 -8
- infrahub/graphql/mutations/diff_conflict.py +14 -8
- infrahub/graphql/mutations/generator.py +83 -0
- infrahub/graphql/mutations/graphql_query.py +21 -13
- infrahub/graphql/mutations/ipam.py +41 -39
- infrahub/graphql/mutations/main.py +226 -66
- infrahub/graphql/mutations/menu.py +12 -12
- infrahub/graphql/mutations/models.py +2 -4
- infrahub/graphql/mutations/node_getter/by_default_filter.py +1 -3
- infrahub/graphql/mutations/node_getter/by_hfid.py +1 -3
- infrahub/graphql/mutations/node_getter/by_id.py +1 -3
- infrahub/graphql/mutations/node_getter/interface.py +1 -2
- infrahub/graphql/mutations/proposed_change.py +39 -31
- infrahub/graphql/mutations/relationship.py +372 -129
- infrahub/graphql/mutations/repository.py +46 -40
- infrahub/graphql/mutations/resource_manager.py +26 -26
- infrahub/graphql/mutations/schema.py +70 -37
- infrahub/graphql/mutations/tasks.py +10 -7
- infrahub/graphql/mutations/webhook.py +137 -0
- infrahub/graphql/parser.py +5 -5
- infrahub/graphql/permissions.py +3 -10
- infrahub/graphql/queries/account.py +22 -18
- infrahub/graphql/queries/branch.py +6 -4
- infrahub/graphql/queries/diff/tree.py +67 -56
- infrahub/graphql/queries/event.py +115 -0
- infrahub/graphql/queries/internal.py +3 -3
- infrahub/graphql/queries/ipam.py +25 -20
- infrahub/graphql/queries/relationship.py +13 -12
- infrahub/graphql/queries/resource_manager.py +37 -25
- infrahub/graphql/queries/search.py +11 -10
- infrahub/graphql/queries/status.py +12 -9
- infrahub/graphql/queries/task.py +11 -9
- infrahub/graphql/resolvers/many_relationship.py +15 -15
- infrahub/graphql/resolvers/resolver.py +58 -37
- infrahub/graphql/resolvers/single_relationship.py +16 -10
- infrahub/graphql/schema.py +4 -0
- infrahub/graphql/subscription/__init__.py +1 -1
- infrahub/graphql/subscription/events.py +1 -1
- infrahub/graphql/subscription/graphql_query.py +8 -8
- infrahub/graphql/types/branch.py +2 -2
- infrahub/graphql/types/common.py +6 -1
- infrahub/graphql/types/context.py +12 -0
- infrahub/graphql/types/enums.py +2 -0
- infrahub/graphql/types/event.py +167 -0
- infrahub/graphql/types/interface.py +2 -2
- infrahub/graphql/types/node.py +5 -5
- infrahub/graphql/types/permission.py +2 -2
- infrahub/graphql/types/relationship.py +3 -3
- infrahub/graphql/types/standard_node.py +9 -11
- infrahub/graphql/utils.py +30 -184
- infrahub/groups/ancestors.py +29 -0
- infrahub/groups/parsers.py +107 -0
- infrahub/groups/tasks.py +2 -3
- infrahub/lock.py +21 -21
- infrahub/menu/generator.py +7 -8
- infrahub/menu/menu.py +107 -139
- infrahub/menu/models.py +121 -20
- infrahub/menu/repository.py +111 -0
- infrahub/menu/utils.py +5 -8
- infrahub/message_bus/__init__.py +11 -13
- infrahub/message_bus/messages/__init__.py +1 -25
- infrahub/message_bus/messages/check_generator_run.py +3 -3
- infrahub/message_bus/messages/event_branch_merge.py +3 -0
- infrahub/message_bus/messages/finalize_validator_execution.py +3 -0
- infrahub/message_bus/messages/proposed_change/request_proposedchange_refreshartifacts.py +6 -0
- infrahub/message_bus/messages/request_generatordefinition_check.py +2 -0
- infrahub/message_bus/messages/request_proposedchange_pipeline.py +2 -0
- infrahub/message_bus/messages/send_echo_request.py +1 -1
- infrahub/message_bus/operations/__init__.py +4 -15
- infrahub/message_bus/operations/check/__init__.py +2 -2
- infrahub/message_bus/operations/check/generator.py +2 -3
- infrahub/message_bus/operations/event/__init__.py +2 -2
- infrahub/message_bus/operations/event/branch.py +7 -3
- infrahub/message_bus/operations/event/worker.py +0 -3
- infrahub/message_bus/operations/finalize/validator.py +52 -2
- infrahub/message_bus/operations/git/file.py +2 -2
- infrahub/message_bus/operations/git/repository.py +1 -1
- infrahub/message_bus/operations/requests/__init__.py +0 -4
- infrahub/message_bus/operations/requests/generator_definition.py +22 -24
- infrahub/message_bus/operations/requests/proposed_change.py +39 -20
- infrahub/message_bus/operations/send/echo.py +1 -1
- infrahub/message_bus/types.py +1 -1
- infrahub/permissions/globals.py +15 -0
- infrahub/pools/number.py +2 -4
- infrahub/pools/prefix.py +29 -165
- infrahub/prefect_server/__init__.py +0 -0
- infrahub/prefect_server/app.py +18 -0
- infrahub/prefect_server/database.py +20 -0
- infrahub/prefect_server/events.py +28 -0
- infrahub/prefect_server/models.py +46 -0
- infrahub/proposed_change/models.py +18 -1
- infrahub/proposed_change/tasks.py +204 -53
- infrahub/pytest_plugin.py +13 -10
- infrahub/server.py +13 -12
- infrahub/services/__init__.py +148 -63
- infrahub/services/adapters/cache/__init__.py +11 -11
- infrahub/services/adapters/cache/nats.py +42 -25
- infrahub/services/adapters/cache/redis.py +3 -11
- infrahub/services/adapters/event/__init__.py +11 -19
- infrahub/services/adapters/http/__init__.py +0 -5
- infrahub/services/adapters/http/httpx.py +22 -15
- infrahub/services/adapters/message_bus/__init__.py +25 -8
- infrahub/services/adapters/message_bus/local.py +9 -7
- infrahub/services/adapters/message_bus/nats.py +14 -8
- infrahub/services/adapters/message_bus/rabbitmq.py +23 -10
- infrahub/services/adapters/workflow/__init__.py +11 -8
- infrahub/services/adapters/workflow/local.py +27 -6
- infrahub/services/adapters/workflow/worker.py +23 -7
- infrahub/services/component.py +43 -40
- infrahub/services/protocols.py +7 -7
- infrahub/services/scheduler.py +30 -29
- infrahub/storage.py +2 -4
- infrahub/task_manager/constants.py +1 -1
- infrahub/task_manager/event.py +275 -0
- infrahub/task_manager/models.py +147 -3
- infrahub/task_manager/task.py +1 -1
- infrahub/tasks/artifact.py +20 -21
- infrahub/tasks/registry.py +1 -1
- infrahub/telemetry/__init__.py +0 -0
- infrahub/telemetry/constants.py +9 -0
- infrahub/telemetry/database.py +86 -0
- infrahub/telemetry/models.py +65 -0
- infrahub/telemetry/task_manager.py +77 -0
- infrahub/telemetry/tasks.py +119 -0
- infrahub/telemetry/utils.py +11 -0
- infrahub/transformations/tasks.py +5 -7
- infrahub/trigger/__init__.py +0 -0
- infrahub/trigger/catalogue.py +13 -0
- infrahub/trigger/constants.py +1 -0
- infrahub/trigger/models.py +118 -0
- infrahub/trigger/setup.py +90 -0
- infrahub/trigger/tasks.py +36 -0
- infrahub/types.py +1 -1
- infrahub/utils.py +12 -2
- infrahub/validators/__init__.py +0 -0
- infrahub/validators/events.py +42 -0
- infrahub/validators/tasks.py +41 -0
- infrahub/webhook/gather.py +17 -0
- infrahub/webhook/models.py +180 -42
- infrahub/webhook/tasks.py +149 -203
- infrahub/webhook/triggers.py +44 -0
- infrahub/workers/infrahub_async.py +38 -27
- infrahub/workers/utils.py +63 -0
- infrahub/workflows/catalogue.py +98 -71
- infrahub/workflows/initialization.py +12 -8
- infrahub/workflows/models.py +29 -5
- infrahub/workflows/utils.py +11 -2
- infrahub_sdk/client.py +19 -0
- infrahub_sdk/context.py +13 -0
- infrahub_sdk/ctl/branch.py +3 -2
- infrahub_sdk/ctl/utils.py +0 -16
- infrahub_sdk/exceptions.py +6 -0
- infrahub_sdk/generator.py +3 -0
- infrahub_sdk/graphql.py +45 -13
- infrahub_sdk/node.py +66 -20
- infrahub_sdk/protocols.py +21 -8
- infrahub_sdk/protocols_base.py +32 -11
- infrahub_sdk/schema/__init__.py +14 -2
- infrahub_sdk/schema/main.py +7 -0
- infrahub_sdk/task/__init__.py +11 -0
- infrahub_sdk/task/constants.py +3 -0
- infrahub_sdk/task/exceptions.py +25 -0
- infrahub_sdk/task/manager.py +551 -0
- infrahub_sdk/task/models.py +74 -0
- infrahub_sdk/timestamp.py +142 -33
- infrahub_sdk/utils.py +29 -1
- {infrahub_server-1.1.9.dist-info → infrahub_server-1.2.0.dist-info}/METADATA +8 -6
- infrahub_server-1.2.0.dist-info/RECORD +746 -0
- infrahub_testcontainers/container.py +5 -6
- infrahub_testcontainers/docker-compose.test.yml +2 -2
- infrahub_testcontainers/helpers.py +5 -1
- infrahub/core/branch/constants.py +0 -2
- infrahub/core/schema/definitions/core.py +0 -2275
- infrahub/graphql/query.py +0 -52
- infrahub/message_bus/messages/check_repository_checkdefinition.py +0 -20
- infrahub/message_bus/messages/check_repository_mergeconflicts.py +0 -16
- infrahub/message_bus/messages/check_repository_usercheck.py +0 -26
- infrahub/message_bus/messages/event_branch_create.py +0 -11
- infrahub/message_bus/messages/event_branch_delete.py +0 -11
- infrahub/message_bus/messages/event_branch_rebased.py +0 -9
- infrahub/message_bus/messages/event_node_mutated.py +0 -15
- infrahub/message_bus/messages/event_schema_update.py +0 -9
- infrahub/message_bus/messages/request_artifactdefinition_check.py +0 -17
- infrahub/message_bus/messages/request_repository_checks.py +0 -12
- infrahub/message_bus/messages/request_repository_userchecks.py +0 -18
- infrahub/message_bus/operations/check/repository.py +0 -293
- infrahub/message_bus/operations/event/node.py +0 -20
- infrahub/message_bus/operations/event/schema.py +0 -17
- infrahub/message_bus/operations/requests/artifact_definition.py +0 -148
- infrahub/message_bus/operations/requests/repository.py +0 -133
- infrahub/schema/constants.py +0 -1
- infrahub/schema/tasks.py +0 -76
- infrahub/services/adapters/database/__init__.py +0 -9
- infrahub/tasks/telemetry.py +0 -127
- infrahub/webhook/constants.py +0 -3
- infrahub_server-1.1.9.dist-info/RECORD +0 -688
- /infrahub/{schema → artifacts}/__init__.py +0 -0
- {infrahub_server-1.1.9.dist-info → infrahub_server-1.2.0.dist-info}/LICENSE.txt +0 -0
- {infrahub_server-1.1.9.dist-info → infrahub_server-1.2.0.dist-info}/WHEEL +0 -0
- {infrahub_server-1.1.9.dist-info → infrahub_server-1.2.0.dist-info}/entry_points.txt +0 -0
|
@@ -3,8 +3,8 @@ from __future__ import annotations
|
|
|
3
3
|
import copy
|
|
4
4
|
import hashlib
|
|
5
5
|
from collections import defaultdict
|
|
6
|
-
from itertools import chain
|
|
7
|
-
from typing import
|
|
6
|
+
from itertools import chain, combinations
|
|
7
|
+
from typing import Any
|
|
8
8
|
|
|
9
9
|
from infrahub_sdk.topological_sort import DependencyCycleExistsError, topological_sort
|
|
10
10
|
from infrahub_sdk.utils import compare_lists, deep_merge_dict, duplicates, intersection
|
|
@@ -12,6 +12,8 @@ from typing_extensions import Self
|
|
|
12
12
|
|
|
13
13
|
from infrahub.computed_attribute.constants import VALID_KINDS as VALID_COMPUTED_ATTRIBUTE_KINDS
|
|
14
14
|
from infrahub.core.constants import (
|
|
15
|
+
OBJECT_TEMPLATE_NAME_ATTR,
|
|
16
|
+
OBJECT_TEMPLATE_RELATIONSHIP_NAME,
|
|
15
17
|
RESERVED_ATTR_GEN_NAMES,
|
|
16
18
|
RESERVED_ATTR_REL_NAMES,
|
|
17
19
|
RESTRICTED_NAMESPACES,
|
|
@@ -43,6 +45,7 @@ from infrahub.core.schema import (
|
|
|
43
45
|
RelationshipSchema,
|
|
44
46
|
SchemaAttributePath,
|
|
45
47
|
SchemaRoot,
|
|
48
|
+
TemplateSchema,
|
|
46
49
|
)
|
|
47
50
|
from infrahub.core.schema.definitions.core import core_profile_schema_definition
|
|
48
51
|
from infrahub.core.validators import CONSTRAINT_VALIDATOR_MAP
|
|
@@ -53,17 +56,12 @@ from infrahub.types import ATTRIBUTE_TYPES
|
|
|
53
56
|
from infrahub.utils import format_label
|
|
54
57
|
from infrahub.visuals import select_color
|
|
55
58
|
|
|
59
|
+
from ..constants.schema import PARENT_CHILD_IDENTIFIER
|
|
56
60
|
from .constants import INTERNAL_SCHEMA_NODE_KINDS, SchemaNamespace
|
|
57
61
|
from .schema_branch_computed import ComputedAttributes
|
|
58
62
|
|
|
59
63
|
log = get_logger()
|
|
60
64
|
|
|
61
|
-
if TYPE_CHECKING:
|
|
62
|
-
from pydantic import ValidationInfo
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
# pylint: disable=redefined-builtin,too-many-public-methods,too-many-lines
|
|
66
|
-
|
|
67
65
|
|
|
68
66
|
class SchemaBranch:
|
|
69
67
|
def __init__(
|
|
@@ -73,28 +71,26 @@ class SchemaBranch:
|
|
|
73
71
|
data: dict[str, dict[str, str]] | None = None,
|
|
74
72
|
computed_attributes: ComputedAttributes | None = None,
|
|
75
73
|
):
|
|
76
|
-
self._cache: dict[str,
|
|
74
|
+
self._cache: dict[str, NodeSchema | GenericSchema] = cache
|
|
77
75
|
self.name: str | None = name
|
|
78
76
|
self.nodes: dict[str, str] = {}
|
|
79
77
|
self.generics: dict[str, str] = {}
|
|
80
78
|
self.profiles: dict[str, str] = {}
|
|
79
|
+
self.templates: dict[str, str] = {}
|
|
81
80
|
self.computed_attributes = computed_attributes or ComputedAttributes()
|
|
82
81
|
|
|
83
82
|
if data:
|
|
84
83
|
self.nodes = data.get("nodes", {})
|
|
85
84
|
self.generics = data.get("generics", {})
|
|
86
85
|
self.profiles = data.get("profiles", {})
|
|
86
|
+
self.templates = data.get("templates", {})
|
|
87
87
|
|
|
88
88
|
@classmethod
|
|
89
|
-
def
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
if isinstance(v, cls):
|
|
95
|
-
return v
|
|
96
|
-
if isinstance(v, dict):
|
|
97
|
-
return cls.from_dict_schema_object(data=v)
|
|
89
|
+
def validate(cls, data: Any) -> Self: # noqa: ARG003
|
|
90
|
+
if isinstance(data, cls):
|
|
91
|
+
return data
|
|
92
|
+
if isinstance(data, dict):
|
|
93
|
+
return cls.from_dict_schema_object(data=data)
|
|
98
94
|
raise ValueError("must be a class or a dict")
|
|
99
95
|
|
|
100
96
|
@property
|
|
@@ -105,14 +101,22 @@ class SchemaBranch:
|
|
|
105
101
|
def generic_names(self) -> list[str]:
|
|
106
102
|
return list(self.generics.keys())
|
|
107
103
|
|
|
104
|
+
@property
|
|
105
|
+
def generic_names_without_templates(self) -> list[str]:
|
|
106
|
+
return [g for g in self.generic_names if not g.startswith("Template")]
|
|
107
|
+
|
|
108
108
|
@property
|
|
109
109
|
def profile_names(self) -> list[str]:
|
|
110
110
|
return list(self.profiles.keys())
|
|
111
111
|
|
|
112
|
-
|
|
112
|
+
@property
|
|
113
|
+
def template_names(self) -> list[str]:
|
|
114
|
+
return list(self.templates.keys())
|
|
115
|
+
|
|
116
|
+
def get_all_kind_id_map(self, nodes_and_generics_only: bool = False) -> dict[str, str]:
|
|
113
117
|
kind_id_map = {}
|
|
114
|
-
if
|
|
115
|
-
names = self.node_names + self.
|
|
118
|
+
if nodes_and_generics_only:
|
|
119
|
+
names = self.node_names + self.generic_names_without_templates
|
|
116
120
|
else:
|
|
117
121
|
names = self.all_names
|
|
118
122
|
for name in names:
|
|
@@ -124,7 +128,7 @@ class SchemaBranch:
|
|
|
124
128
|
|
|
125
129
|
@property
|
|
126
130
|
def all_names(self) -> list[str]:
|
|
127
|
-
return self.node_names + self.generic_names + self.profile_names
|
|
131
|
+
return self.node_names + self.generic_names + self.profile_names + self.template_names
|
|
128
132
|
|
|
129
133
|
def get_hash(self) -> str:
|
|
130
134
|
"""Calculate the hash for this objects based on the content of nodes and generics.
|
|
@@ -142,13 +146,14 @@ class SchemaBranch:
|
|
|
142
146
|
return SchemaBranchHash(main=self.get_hash(), nodes=self.nodes, generics=self.generics)
|
|
143
147
|
|
|
144
148
|
def to_dict(self) -> dict[str, Any]:
|
|
145
|
-
return {"nodes": self.nodes, "generics": self.generics, "profiles": self.profiles}
|
|
149
|
+
return {"nodes": self.nodes, "generics": self.generics, "profiles": self.profiles, "templates": self.templates}
|
|
146
150
|
|
|
147
151
|
def to_dict_schema_object(self, duplicate: bool = False) -> dict[str, dict[str, MainSchemaTypes]]:
|
|
148
152
|
return {
|
|
149
153
|
"nodes": {name: self.get(name, duplicate=duplicate) for name in self.nodes},
|
|
150
154
|
"profiles": {name: self.get(name, duplicate=duplicate) for name in self.profiles},
|
|
151
155
|
"generics": {name: self.get(name, duplicate=duplicate) for name in self.generics},
|
|
156
|
+
"templates": {name: self.get(name, duplicate=duplicate) for name in self.templates},
|
|
152
157
|
}
|
|
153
158
|
|
|
154
159
|
@classmethod
|
|
@@ -157,10 +162,11 @@ class SchemaBranch:
|
|
|
157
162
|
"nodes": NodeSchema,
|
|
158
163
|
"generics": GenericSchema,
|
|
159
164
|
"profiles": ProfileSchema,
|
|
165
|
+
"templates": TemplateSchema,
|
|
160
166
|
}
|
|
161
167
|
|
|
162
168
|
cache: dict[str, MainSchemaTypes] = {}
|
|
163
|
-
nodes: dict[str, dict[str, str]] = {"nodes": {}, "generics": {}, "profiles": {}}
|
|
169
|
+
nodes: dict[str, dict[str, str]] = {"nodes": {}, "generics": {}, "profiles": {}, "templates": {}}
|
|
164
170
|
|
|
165
171
|
for node_type, node_class in type_mapping.items():
|
|
166
172
|
for node_name, node_data in data[node_type].items():
|
|
@@ -174,8 +180,8 @@ class SchemaBranch:
|
|
|
174
180
|
|
|
175
181
|
def diff(self, other: SchemaBranch) -> SchemaDiff:
|
|
176
182
|
# Identify the nodes or generics that have been added or removed
|
|
177
|
-
local_kind_id_map = self.get_all_kind_id_map(
|
|
178
|
-
other_kind_id_map = other.get_all_kind_id_map(
|
|
183
|
+
local_kind_id_map = self.get_all_kind_id_map(nodes_and_generics_only=True)
|
|
184
|
+
other_kind_id_map = other.get_all_kind_id_map(nodes_and_generics_only=True)
|
|
179
185
|
clean_local_ids = [id for id in local_kind_id_map.values() if id is not None]
|
|
180
186
|
clean_other_ids = [id for id in other_kind_id_map.values() if id is not None]
|
|
181
187
|
shared_ids = intersection(list1=clean_local_ids, list2=clean_other_ids)
|
|
@@ -229,12 +235,6 @@ class SchemaBranch:
|
|
|
229
235
|
other_item = schema.get(name=item_kind)
|
|
230
236
|
self.set(name=item_kind, schema=other_item)
|
|
231
237
|
|
|
232
|
-
# for item_kind in local_only:
|
|
233
|
-
# if item_kind in self.nodes:
|
|
234
|
-
# del self.nodes[item_kind]
|
|
235
|
-
# else:
|
|
236
|
-
# del self.generics[item_kind]
|
|
237
|
-
|
|
238
238
|
def validate_node_deletions(self, diff: SchemaDiff) -> None:
|
|
239
239
|
"""Given a diff, check if a deleted node is still used in relationships of other nodes."""
|
|
240
240
|
removed_schema_names = set(diff.removed.keys())
|
|
@@ -256,7 +256,7 @@ class SchemaBranch:
|
|
|
256
256
|
result.validate_all(migration_map=MIGRATION_MAP, validator_map=CONSTRAINT_VALIDATOR_MAP)
|
|
257
257
|
return result
|
|
258
258
|
|
|
259
|
-
def duplicate(self, name:
|
|
259
|
+
def duplicate(self, name: str | None = None) -> SchemaBranch:
|
|
260
260
|
"""Duplicate the current object but conserve the same cache."""
|
|
261
261
|
return self.__class__(
|
|
262
262
|
name=name,
|
|
@@ -281,6 +281,8 @@ class SchemaBranch:
|
|
|
281
281
|
self.generics[name] = schema_hash
|
|
282
282
|
elif "Profile" in schema.__class__.__name__:
|
|
283
283
|
self.profiles[name] = schema_hash
|
|
284
|
+
elif "Template" in schema.__class__.__name__:
|
|
285
|
+
self.templates[name] = schema_hash
|
|
284
286
|
|
|
285
287
|
return schema_hash
|
|
286
288
|
|
|
@@ -292,6 +294,7 @@ class SchemaBranch:
|
|
|
292
294
|
|
|
293
295
|
If duplicate is set to false, the real object will be returned.
|
|
294
296
|
"""
|
|
297
|
+
|
|
295
298
|
key = None
|
|
296
299
|
if name in self.nodes:
|
|
297
300
|
key = self.nodes[name]
|
|
@@ -299,6 +302,8 @@ class SchemaBranch:
|
|
|
299
302
|
key = self.generics[name]
|
|
300
303
|
elif name in self.profiles:
|
|
301
304
|
key = self.profiles[name]
|
|
305
|
+
elif name in self.templates:
|
|
306
|
+
key = self.templates[name]
|
|
302
307
|
|
|
303
308
|
if key and duplicate:
|
|
304
309
|
return self._cache[key].duplicate()
|
|
@@ -330,6 +335,13 @@ class SchemaBranch:
|
|
|
330
335
|
raise ValueError(f"{name!r} is not of type ProfileSchema")
|
|
331
336
|
return item
|
|
332
337
|
|
|
338
|
+
def get_template(self, name: str, duplicate: bool = True) -> TemplateSchema:
|
|
339
|
+
"""Access a specific TemplateSchema, defined by its kind."""
|
|
340
|
+
item = self.get(name=name, duplicate=duplicate)
|
|
341
|
+
if not isinstance(item, TemplateSchema):
|
|
342
|
+
raise ValueError(f"{name!r} is not of type TemplateSchema")
|
|
343
|
+
return item
|
|
344
|
+
|
|
333
345
|
def delete(self, name: str) -> None:
|
|
334
346
|
if name in self.nodes:
|
|
335
347
|
del self.nodes[name]
|
|
@@ -337,6 +349,8 @@ class SchemaBranch:
|
|
|
337
349
|
del self.generics[name]
|
|
338
350
|
elif name in self.profiles:
|
|
339
351
|
del self.profiles[name]
|
|
352
|
+
elif name in self.templates:
|
|
353
|
+
del self.templates[name]
|
|
340
354
|
else:
|
|
341
355
|
raise SchemaNotFoundError(
|
|
342
356
|
branch_name=self.name, identifier=name, message=f"Unable to find the schema {name!r} in the registry"
|
|
@@ -412,7 +426,7 @@ class SchemaBranch:
|
|
|
412
426
|
return list(namespaces.values())
|
|
413
427
|
|
|
414
428
|
def get_schemas_for_namespaces(
|
|
415
|
-
self, namespaces:
|
|
429
|
+
self, namespaces: list[str] | None = None, include_internal: bool = False
|
|
416
430
|
) -> list[MainSchemaTypes]:
|
|
417
431
|
"""Retrive everything in a single dictionary."""
|
|
418
432
|
all_schemas = self.get_all(include_internal=include_internal, duplicate=False)
|
|
@@ -429,12 +443,12 @@ class SchemaBranch:
|
|
|
429
443
|
nodes.append(self.get(name=node_name, duplicate=True))
|
|
430
444
|
return nodes
|
|
431
445
|
|
|
432
|
-
def generate_fields_for_display_label(self, name: str) ->
|
|
446
|
+
def generate_fields_for_display_label(self, name: str) -> dict | None:
|
|
433
447
|
node = self.get(name=name, duplicate=False)
|
|
434
|
-
if isinstance(node,
|
|
448
|
+
if isinstance(node, NodeSchema | ProfileSchema | TemplateSchema):
|
|
435
449
|
return node.generate_fields_for_display_label()
|
|
436
450
|
|
|
437
|
-
fields: dict[str,
|
|
451
|
+
fields: dict[str, str | None | dict[str, None]] = {}
|
|
438
452
|
if isinstance(node, GenericSchema):
|
|
439
453
|
for child_node_name in node.used_by:
|
|
440
454
|
child_node = self.get(name=child_node_name, duplicate=False)
|
|
@@ -490,6 +504,8 @@ class SchemaBranch:
|
|
|
490
504
|
self.process_inheritance()
|
|
491
505
|
self.process_hierarchy()
|
|
492
506
|
self.process_branch_support()
|
|
507
|
+
self.manage_object_template_schemas()
|
|
508
|
+
self.manage_object_template_relationships()
|
|
493
509
|
self.manage_profile_schemas()
|
|
494
510
|
self.manage_profile_relationships()
|
|
495
511
|
self.add_hierarchy_generic()
|
|
@@ -520,6 +536,9 @@ class SchemaBranch:
|
|
|
520
536
|
self.process_relationships()
|
|
521
537
|
self.process_human_friendly_id()
|
|
522
538
|
|
|
539
|
+
def _generate_identifier_string(self, node_kind: str, peer_kind: str) -> str:
|
|
540
|
+
return "__".join(sorted([node_kind, peer_kind])).lower()
|
|
541
|
+
|
|
523
542
|
def generate_identifiers(self) -> None:
|
|
524
543
|
"""Generate the identifier for all relationships if it's not already present."""
|
|
525
544
|
for name in self.all_names:
|
|
@@ -532,7 +551,7 @@ class SchemaBranch:
|
|
|
532
551
|
for rel in node.relationships:
|
|
533
552
|
if rel.identifier:
|
|
534
553
|
continue
|
|
535
|
-
rel.identifier =
|
|
554
|
+
rel.identifier = self._generate_identifier_string(node.kind, rel.peer)
|
|
536
555
|
self.set(name=name, schema=node)
|
|
537
556
|
|
|
538
557
|
def validate_identifiers(self) -> None:
|
|
@@ -594,7 +613,7 @@ class SchemaBranch:
|
|
|
594
613
|
node_schema: BaseNodeSchema,
|
|
595
614
|
path: str,
|
|
596
615
|
allowed_path_types: SchemaElementPathType,
|
|
597
|
-
element_name:
|
|
616
|
+
element_name: str | None = None,
|
|
598
617
|
) -> SchemaAttributePath:
|
|
599
618
|
error_header = f"{node_schema.kind}"
|
|
600
619
|
error_header += f".{element_name}" if element_name else ""
|
|
@@ -660,7 +679,7 @@ class SchemaBranch:
|
|
|
660
679
|
return schema_attribute_path
|
|
661
680
|
|
|
662
681
|
def sync_uniqueness_constraints_and_unique_attributes(self) -> None:
|
|
663
|
-
for name in self.
|
|
682
|
+
for name in self.generic_names_without_templates + self.node_names:
|
|
664
683
|
node_schema = self.get(name=name, duplicate=False)
|
|
665
684
|
|
|
666
685
|
if not node_schema.unique_attributes and not node_schema.uniqueness_constraints:
|
|
@@ -696,10 +715,12 @@ class SchemaBranch:
|
|
|
696
715
|
for attr_name in attrs_to_make_unique:
|
|
697
716
|
attr_schema = node_schema.get_attribute(name=attr_name)
|
|
698
717
|
attr_schema.unique = True
|
|
718
|
+
|
|
699
719
|
if attrs_to_add_to_constraints:
|
|
700
720
|
node_schema.uniqueness_constraints = (node_schema.uniqueness_constraints or []) + sorted(
|
|
701
721
|
[[f"{attr_name}__value"] for attr_name in attrs_to_add_to_constraints]
|
|
702
722
|
)
|
|
723
|
+
|
|
703
724
|
self.set(name=name, schema=node_schema)
|
|
704
725
|
|
|
705
726
|
def validate_uniqueness_constraints(self) -> None:
|
|
@@ -775,7 +796,7 @@ class SchemaBranch:
|
|
|
775
796
|
)
|
|
776
797
|
|
|
777
798
|
def validate_default_values(self) -> None:
|
|
778
|
-
for name in self.
|
|
799
|
+
for name in self.generic_names_without_templates + self.node_names:
|
|
779
800
|
node_schema = self.get(name=name, duplicate=False)
|
|
780
801
|
for node_attr in node_schema.local_attributes:
|
|
781
802
|
if node_attr.default_value is None:
|
|
@@ -794,15 +815,30 @@ class SchemaBranch:
|
|
|
794
815
|
f"{node_schema.namespace}{node_schema.name}: default value {exc.message}"
|
|
795
816
|
) from exc
|
|
796
817
|
|
|
818
|
+
def _is_attr_combination_unique(self, attrs_paths: list[str], uniqueness_constraints: list[list[str]]) -> bool:
|
|
819
|
+
"""
|
|
820
|
+
Return whether at least one combination of any length of `attrs_paths` is equal to a uniqueness constraint.
|
|
821
|
+
"""
|
|
822
|
+
|
|
823
|
+
unique_constraint_group_sets = [set(ucg) for ucg in uniqueness_constraints]
|
|
824
|
+
for i in range(1, len(attrs_paths) + 1):
|
|
825
|
+
for attr_combo in combinations(attrs_paths, i):
|
|
826
|
+
if any(ucg == set(attr_combo) for ucg in unique_constraint_group_sets):
|
|
827
|
+
return True
|
|
828
|
+
return False
|
|
829
|
+
|
|
797
830
|
def validate_human_friendly_id(self) -> None:
|
|
798
|
-
for name in self.
|
|
831
|
+
for name in self.generic_names_without_templates + self.node_names:
|
|
799
832
|
node_schema = self.get(name=name, duplicate=False)
|
|
800
|
-
hf_attr_names = set()
|
|
801
833
|
|
|
802
834
|
if not node_schema.human_friendly_id:
|
|
803
835
|
continue
|
|
804
836
|
|
|
805
837
|
allowed_types = SchemaElementPathType.ATTR_WITH_PROP | SchemaElementPathType.REL_ONE_MANDATORY_ATTR
|
|
838
|
+
|
|
839
|
+
# Mapping relationship identifiers -> list of attributes paths
|
|
840
|
+
rel_schemas_to_paths: dict[str, tuple[MainSchemaTypes, list[str]]] = {}
|
|
841
|
+
|
|
806
842
|
for hfid_path in node_schema.human_friendly_id:
|
|
807
843
|
schema_path = self.validate_schema_path(
|
|
808
844
|
node_schema=node_schema,
|
|
@@ -811,12 +847,23 @@ class SchemaBranch:
|
|
|
811
847
|
element_name="human_friendly_id",
|
|
812
848
|
)
|
|
813
849
|
|
|
814
|
-
if schema_path.
|
|
815
|
-
|
|
850
|
+
if schema_path.is_type_relationship:
|
|
851
|
+
# Construct the name without relationship prefix to match with how it would be defined in peer schema uniqueness constraint
|
|
852
|
+
rel_identifier = schema_path.relationship_schema.identifier
|
|
853
|
+
if rel_identifier not in rel_schemas_to_paths:
|
|
854
|
+
rel_schemas_to_paths[rel_identifier] = (schema_path.related_schema, [])
|
|
855
|
+
rel_schemas_to_paths[rel_identifier][1].append(schema_path.attribute_path_as_str)
|
|
856
|
+
|
|
857
|
+
# For every relationship referred within hfid, check whether the combination of attributes is unique is the peer schema node
|
|
858
|
+
for related_schema, attrs_paths in rel_schemas_to_paths.values():
|
|
859
|
+
if not self._is_attr_combination_unique(attrs_paths, related_schema.uniqueness_constraints):
|
|
860
|
+
raise ValidationError(
|
|
861
|
+
f"HFID of {node_schema.kind} refers peer {related_schema.kind} with a non-unique combination of attributes {attrs_paths}"
|
|
862
|
+
)
|
|
816
863
|
|
|
817
864
|
def validate_required_relationships(self) -> None:
|
|
818
865
|
reverse_dependency_map: dict[str, set[str]] = {}
|
|
819
|
-
for name in self.node_names + self.
|
|
866
|
+
for name in self.node_names + self.generic_names_without_templates:
|
|
820
867
|
node_schema = self.get(name=name, duplicate=False)
|
|
821
868
|
for relationship_schema in node_schema.relationships:
|
|
822
869
|
if relationship_schema.optional:
|
|
@@ -834,7 +881,7 @@ class SchemaBranch:
|
|
|
834
881
|
def validate_parent_component(self) -> None:
|
|
835
882
|
# {parent_kind: {component_kind_1, component_kind_2, ...}}
|
|
836
883
|
dependency_map: dict[str, set[str]] = defaultdict(set)
|
|
837
|
-
for name in self.
|
|
884
|
+
for name in self.generic_names_without_templates + self.node_names:
|
|
838
885
|
node_schema = self.get(name=name, duplicate=False)
|
|
839
886
|
|
|
840
887
|
parent_relationships: list[RelationshipSchema] = []
|
|
@@ -873,7 +920,7 @@ class SchemaBranch:
|
|
|
873
920
|
raise ValueError(f"Cycles exist among parents and components in schema: {exc.get_cycle_strings()}") from exc
|
|
874
921
|
|
|
875
922
|
def _validate_parents_one_schema(
|
|
876
|
-
self, node_schema:
|
|
923
|
+
self, node_schema: NodeSchema | GenericSchema, parent_relationships: list[RelationshipSchema]
|
|
877
924
|
) -> None:
|
|
878
925
|
if not parent_relationships:
|
|
879
926
|
return
|
|
@@ -934,9 +981,9 @@ class SchemaBranch:
|
|
|
934
981
|
for rel in node.relationships:
|
|
935
982
|
if rel.peer in [InfrahubKind.GENERICGROUP]:
|
|
936
983
|
continue
|
|
937
|
-
if not self.has(rel.peer):
|
|
984
|
+
if not self.has(rel.peer) or self.get(rel.peer, duplicate=False).state == HashableModelState.ABSENT:
|
|
938
985
|
raise ValueError(
|
|
939
|
-
f"{node.kind}: Relationship {rel.name!r} is
|
|
986
|
+
f"{node.kind}: Relationship {rel.name!r} is referring an invalid peer {rel.peer!r}"
|
|
940
987
|
) from None
|
|
941
988
|
|
|
942
989
|
def validate_computed_attributes(self) -> None:
|
|
@@ -1103,7 +1150,7 @@ class SchemaBranch:
|
|
|
1103
1150
|
for name in self.all_names:
|
|
1104
1151
|
node = self.get(name=name, duplicate=False)
|
|
1105
1152
|
|
|
1106
|
-
schema_to_update:
|
|
1153
|
+
schema_to_update: NodeSchema | GenericSchema | None = None
|
|
1107
1154
|
for relationship in node.relationships:
|
|
1108
1155
|
if relationship.on_delete is not None:
|
|
1109
1156
|
continue
|
|
@@ -1120,49 +1167,57 @@ class SchemaBranch:
|
|
|
1120
1167
|
self.set(name=schema_to_update.kind, schema=schema_to_update)
|
|
1121
1168
|
|
|
1122
1169
|
def process_human_friendly_id(self) -> None:
|
|
1123
|
-
for name in self.
|
|
1170
|
+
for name in self.generic_names_without_templates + self.node_names:
|
|
1124
1171
|
node = self.get(name=name, duplicate=False)
|
|
1125
1172
|
|
|
1126
1173
|
# If human_friendly_id IS NOT defined
|
|
1127
1174
|
# but some the model has some unique attribute, we generate a human_friendly_id
|
|
1128
1175
|
# If human_friendly_id IS defined
|
|
1129
1176
|
# but no unique attributes and no uniquess constraints, we add a uniqueness_constraint
|
|
1130
|
-
if not node.human_friendly_id
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
node.human_friendly_id = [f"{attr.name}__value"]
|
|
1134
|
-
self.set(name=node.kind, schema=node)
|
|
1135
|
-
break
|
|
1136
|
-
continue
|
|
1137
|
-
|
|
1138
|
-
# if no human_friendly_id and a uniqueness_constraint with a single attribute exists
|
|
1139
|
-
# then use that attribute as the human_friendly_id
|
|
1140
|
-
if not node.human_friendly_id and node.uniqueness_constraints:
|
|
1141
|
-
for constraint_paths in node.uniqueness_constraints:
|
|
1142
|
-
if len(constraint_paths) > 1:
|
|
1143
|
-
continue
|
|
1144
|
-
constraint_path = constraint_paths[0]
|
|
1145
|
-
schema_path = node.parse_schema_path(path=constraint_path, schema=node)
|
|
1146
|
-
if (
|
|
1147
|
-
schema_path.is_type_attribute
|
|
1148
|
-
and schema_path.attribute_property_name == "value"
|
|
1149
|
-
and schema_path.attribute_schema
|
|
1150
|
-
):
|
|
1177
|
+
if not node.human_friendly_id:
|
|
1178
|
+
if node.unique_attributes:
|
|
1179
|
+
for attr in node.unique_attributes:
|
|
1151
1180
|
node = self.get(name=name, duplicate=True)
|
|
1152
|
-
node.human_friendly_id = [f"{
|
|
1181
|
+
node.human_friendly_id = [f"{attr.name}__value"]
|
|
1153
1182
|
self.set(name=node.kind, schema=node)
|
|
1154
1183
|
break
|
|
1184
|
+
continue
|
|
1155
1185
|
|
|
1156
|
-
|
|
1157
|
-
|
|
1186
|
+
# if no human_friendly_id and a uniqueness_constraint with a single attribute exists
|
|
1187
|
+
# then use that attribute as the human_friendly_id
|
|
1188
|
+
if node.uniqueness_constraints:
|
|
1189
|
+
for constraint_paths in node.uniqueness_constraints:
|
|
1190
|
+
if len(constraint_paths) > 1:
|
|
1191
|
+
continue
|
|
1192
|
+
constraint_path = constraint_paths[0]
|
|
1193
|
+
schema_path = node.parse_schema_path(path=constraint_path, schema=node)
|
|
1194
|
+
if (
|
|
1195
|
+
schema_path.is_type_attribute
|
|
1196
|
+
and schema_path.attribute_property_name == "value"
|
|
1197
|
+
and schema_path.attribute_schema
|
|
1198
|
+
):
|
|
1199
|
+
node = self.get(name=name, duplicate=True)
|
|
1200
|
+
node.human_friendly_id = [f"{schema_path.attribute_schema.name}__value"]
|
|
1201
|
+
self.set(name=node.kind, schema=node)
|
|
1202
|
+
break
|
|
1203
|
+
|
|
1204
|
+
# Add hfid to uniqueness constraint
|
|
1205
|
+
if node.human_friendly_id:
|
|
1206
|
+
uniqueness_constraint: list[str] = []
|
|
1158
1207
|
for item in node.human_friendly_id:
|
|
1159
1208
|
schema_attribute_path = node.parse_schema_path(path=item, schema=self)
|
|
1160
1209
|
if schema_attribute_path.is_type_attribute:
|
|
1161
|
-
|
|
1210
|
+
uniqueness_constraint.append(item)
|
|
1162
1211
|
elif schema_attribute_path.is_type_relationship:
|
|
1163
|
-
|
|
1212
|
+
uniqueness_constraint.append(schema_attribute_path.relationship_schema.name)
|
|
1164
1213
|
|
|
1165
|
-
node.
|
|
1214
|
+
node = self.get(name=name, duplicate=True)
|
|
1215
|
+
# Make sure there is no duplicate regarding generics values.
|
|
1216
|
+
if node.uniqueness_constraints:
|
|
1217
|
+
if uniqueness_constraint not in node.uniqueness_constraints:
|
|
1218
|
+
node.uniqueness_constraints.append(uniqueness_constraint)
|
|
1219
|
+
else:
|
|
1220
|
+
node.uniqueness_constraints = [uniqueness_constraint]
|
|
1166
1221
|
self.set(name=node.kind, schema=node)
|
|
1167
1222
|
|
|
1168
1223
|
def process_hierarchy(self) -> None:
|
|
@@ -1267,7 +1322,6 @@ class SchemaBranch:
|
|
|
1267
1322
|
|
|
1268
1323
|
if either node on a relationship support branch, the relationship must be branch aware.
|
|
1269
1324
|
"""
|
|
1270
|
-
# pylint: disable=too-many-branches
|
|
1271
1325
|
|
|
1272
1326
|
for name in self.all_names:
|
|
1273
1327
|
node = self.get(name=name, duplicate=False)
|
|
@@ -1329,7 +1383,6 @@ class SchemaBranch:
|
|
|
1329
1383
|
|
|
1330
1384
|
def process_cardinality_counts(self) -> None:
|
|
1331
1385
|
"""Ensure that all relationships with a cardinality of ONE have a min_count and max_count of 1."""
|
|
1332
|
-
# pylint: disable=too-many-branches
|
|
1333
1386
|
|
|
1334
1387
|
for name in self.all_names:
|
|
1335
1388
|
node = self.get(name=name, duplicate=False)
|
|
@@ -1416,7 +1469,6 @@ class SchemaBranch:
|
|
|
1416
1469
|
self.set(name=name, schema=node)
|
|
1417
1470
|
|
|
1418
1471
|
def cleanup_inherited_elements(self) -> None:
|
|
1419
|
-
# pylint: disable=too-many-branches
|
|
1420
1472
|
for name in self.node_names:
|
|
1421
1473
|
node = self.get_node(name=name, duplicate=False)
|
|
1422
1474
|
|
|
@@ -1512,7 +1564,7 @@ class SchemaBranch:
|
|
|
1512
1564
|
def _get_hierarchy_child_rel(self, peer: str, hierarchical: str | None, read_only: bool) -> RelationshipSchema:
|
|
1513
1565
|
return RelationshipSchema(
|
|
1514
1566
|
name="children",
|
|
1515
|
-
identifier=
|
|
1567
|
+
identifier=PARENT_CHILD_IDENTIFIER,
|
|
1516
1568
|
peer=peer,
|
|
1517
1569
|
kind=RelationshipKind.HIERARCHY,
|
|
1518
1570
|
cardinality=RelationshipCardinality.MANY,
|
|
@@ -1527,7 +1579,7 @@ class SchemaBranch:
|
|
|
1527
1579
|
) -> RelationshipSchema:
|
|
1528
1580
|
return RelationshipSchema(
|
|
1529
1581
|
name="parent",
|
|
1530
|
-
identifier=
|
|
1582
|
+
identifier=PARENT_CHILD_IDENTIFIER,
|
|
1531
1583
|
peer=peer,
|
|
1532
1584
|
kind=RelationshipKind.HIERARCHY,
|
|
1533
1585
|
cardinality=RelationshipCardinality.ONE,
|
|
@@ -1606,11 +1658,10 @@ class SchemaBranch:
|
|
|
1606
1658
|
if not self.has(name=InfrahubKind.PROFILE):
|
|
1607
1659
|
# TODO: This logic is actually only for testing purposes as since 1.0.9 CoreProfile is loaded in db.
|
|
1608
1660
|
# Ideally, we would remove this and instead load CoreProfile properly within tests.
|
|
1609
|
-
|
|
1610
|
-
self.set(name=core_profile_schema.kind, schema=core_profile_schema)
|
|
1661
|
+
self.set(name=core_profile_schema_definition.kind, schema=core_profile_schema_definition)
|
|
1611
1662
|
|
|
1612
1663
|
profile_schema_kinds = set()
|
|
1613
|
-
for node_name in self.node_names + self.
|
|
1664
|
+
for node_name in self.node_names + self.generic_names_without_templates:
|
|
1614
1665
|
node = self.get(name=node_name, duplicate=False)
|
|
1615
1666
|
if (
|
|
1616
1667
|
node.namespace in RESTRICTED_NAMESPACES
|
|
@@ -1642,7 +1693,7 @@ class SchemaBranch:
|
|
|
1642
1693
|
|
|
1643
1694
|
if new_used_by_profile:
|
|
1644
1695
|
core_profile_schema = self.get(name=InfrahubKind.PROFILE, duplicate=True)
|
|
1645
|
-
core_profile_schema.used_by = sorted(
|
|
1696
|
+
core_profile_schema.used_by = sorted(profile_schema_kinds)
|
|
1646
1697
|
self.set(name=InfrahubKind.PROFILE, schema=core_profile_schema)
|
|
1647
1698
|
|
|
1648
1699
|
if self.has(name=InfrahubKind.NODE):
|
|
@@ -1653,7 +1704,7 @@ class SchemaBranch:
|
|
|
1653
1704
|
if new_used_by_node:
|
|
1654
1705
|
core_node_schema = self.get(name=InfrahubKind.NODE, duplicate=True)
|
|
1655
1706
|
updated_used_by_node = set(chain(profile_schema_kinds, set(core_node_schema.used_by)))
|
|
1656
|
-
core_node_schema.used_by = sorted(
|
|
1707
|
+
core_node_schema.used_by = sorted(updated_used_by_node)
|
|
1657
1708
|
self.set(name=InfrahubKind.NODE, schema=core_node_schema)
|
|
1658
1709
|
|
|
1659
1710
|
def manage_profile_relationships(self) -> None:
|
|
@@ -1746,3 +1797,286 @@ class SchemaBranch:
|
|
|
1746
1797
|
profile.attributes.append(attr)
|
|
1747
1798
|
|
|
1748
1799
|
return profile
|
|
1800
|
+
|
|
1801
|
+
def _get_object_template_kind(self, node_kind: str) -> str:
|
|
1802
|
+
return f"Template{node_kind}"
|
|
1803
|
+
|
|
1804
|
+
def manage_object_template_relationships(self) -> None:
|
|
1805
|
+
"""Add an `object_template` relationship to all nodes that can be created from object templates.
|
|
1806
|
+
|
|
1807
|
+
This relationship allows to record from which template an object has been created.
|
|
1808
|
+
"""
|
|
1809
|
+
for node_name in self.node_names + self.generic_names:
|
|
1810
|
+
node = self.get(name=node_name, duplicate=False)
|
|
1811
|
+
|
|
1812
|
+
if (
|
|
1813
|
+
node.namespace in RESTRICTED_NAMESPACES
|
|
1814
|
+
or not node.generate_template
|
|
1815
|
+
or node.state == HashableModelState.ABSENT
|
|
1816
|
+
):
|
|
1817
|
+
continue
|
|
1818
|
+
|
|
1819
|
+
template_rel_settings: dict[str, Any] = {
|
|
1820
|
+
"name": OBJECT_TEMPLATE_RELATIONSHIP_NAME,
|
|
1821
|
+
"identifier": "node__objecttemplate",
|
|
1822
|
+
"peer": self._get_object_template_kind(node.kind),
|
|
1823
|
+
"kind": RelationshipKind.TEMPLATE,
|
|
1824
|
+
"cardinality": RelationshipCardinality.ONE,
|
|
1825
|
+
"branch": BranchSupportType.AWARE,
|
|
1826
|
+
"order_weight": 1,
|
|
1827
|
+
}
|
|
1828
|
+
|
|
1829
|
+
# Add relationship between node and template
|
|
1830
|
+
if OBJECT_TEMPLATE_RELATIONSHIP_NAME not in node.relationship_names:
|
|
1831
|
+
node_schema = self.get(name=node_name, duplicate=True)
|
|
1832
|
+
|
|
1833
|
+
node_schema.relationships.append(RelationshipSchema(**template_rel_settings))
|
|
1834
|
+
self.set(name=node_name, schema=node_schema)
|
|
1835
|
+
else:
|
|
1836
|
+
has_changes: bool = False
|
|
1837
|
+
rel_template = node.get_relationship(name=OBJECT_TEMPLATE_RELATIONSHIP_NAME)
|
|
1838
|
+
for name, value in template_rel_settings.items():
|
|
1839
|
+
if getattr(rel_template, name) != value:
|
|
1840
|
+
has_changes = True
|
|
1841
|
+
|
|
1842
|
+
if not has_changes:
|
|
1843
|
+
continue
|
|
1844
|
+
|
|
1845
|
+
node_schema = self.get(name=node_name, duplicate=True)
|
|
1846
|
+
rel_template = node_schema.get_relationship(name=OBJECT_TEMPLATE_RELATIONSHIP_NAME)
|
|
1847
|
+
for name, value in template_rel_settings.items():
|
|
1848
|
+
if getattr(rel_template, name) != value:
|
|
1849
|
+
setattr(rel_template, name, value)
|
|
1850
|
+
|
|
1851
|
+
self.set(name=node_name, schema=node_schema)
|
|
1852
|
+
|
|
1853
|
+
def add_relationships_to_template(self, node: NodeSchema) -> None:
|
|
1854
|
+
template_schema = self.get(name=self._get_object_template_kind(node_kind=node.kind), duplicate=False)
|
|
1855
|
+
if template_schema.is_generic_schema:
|
|
1856
|
+
return
|
|
1857
|
+
|
|
1858
|
+
# Remove previous relationships to account for new ones
|
|
1859
|
+
template_schema.relationships = [
|
|
1860
|
+
r for r in template_schema.relationships if r.kind == RelationshipKind.TEMPLATE
|
|
1861
|
+
]
|
|
1862
|
+
# Tell if the user explicitely requested this template
|
|
1863
|
+
is_autogenerated_subtemplate = node.generate_template is False
|
|
1864
|
+
|
|
1865
|
+
for relationship in node.relationships:
|
|
1866
|
+
if relationship.peer in [InfrahubKind.GENERICGROUP, InfrahubKind.PROFILE] or relationship.kind not in [
|
|
1867
|
+
RelationshipKind.COMPONENT,
|
|
1868
|
+
RelationshipKind.PARENT,
|
|
1869
|
+
RelationshipKind.ATTRIBUTE,
|
|
1870
|
+
RelationshipKind.GENERIC,
|
|
1871
|
+
]:
|
|
1872
|
+
continue
|
|
1873
|
+
|
|
1874
|
+
rel_template_peer = (
|
|
1875
|
+
self._get_object_template_kind(node_kind=relationship.peer)
|
|
1876
|
+
if relationship.kind not in [RelationshipKind.ATTRIBUTE, RelationshipKind.GENERIC]
|
|
1877
|
+
else relationship.peer
|
|
1878
|
+
)
|
|
1879
|
+
template_schema.relationships.append(
|
|
1880
|
+
RelationshipSchema(
|
|
1881
|
+
name=relationship.name,
|
|
1882
|
+
peer=rel_template_peer,
|
|
1883
|
+
kind=relationship.kind,
|
|
1884
|
+
optional=relationship.optional
|
|
1885
|
+
if is_autogenerated_subtemplate
|
|
1886
|
+
else relationship.kind != RelationshipKind.PARENT,
|
|
1887
|
+
cardinality=relationship.cardinality,
|
|
1888
|
+
direction=relationship.direction,
|
|
1889
|
+
branch=relationship.branch,
|
|
1890
|
+
identifier=f"template_{relationship.identifier}"
|
|
1891
|
+
if relationship.identifier
|
|
1892
|
+
else self._generate_identifier_string(template_schema.kind, rel_template_peer),
|
|
1893
|
+
min_count=relationship.min_count,
|
|
1894
|
+
max_count=relationship.max_count,
|
|
1895
|
+
label=f"{relationship.name} template".title()
|
|
1896
|
+
if relationship.kind in [RelationshipKind.COMPONENT, RelationshipKind.PARENT]
|
|
1897
|
+
else relationship.name.title(),
|
|
1898
|
+
)
|
|
1899
|
+
)
|
|
1900
|
+
|
|
1901
|
+
if relationship.kind == RelationshipKind.PARENT:
|
|
1902
|
+
template_schema.human_friendly_id = [
|
|
1903
|
+
f"{relationship.name}__template_name__value"
|
|
1904
|
+
] + template_schema.human_friendly_id
|
|
1905
|
+
template_schema.uniqueness_constraints[0].append(relationship.name)
|
|
1906
|
+
|
|
1907
|
+
def generate_object_template_from_node(
|
|
1908
|
+
self, node: NodeSchema | GenericSchema, need_templates: set[NodeSchema | GenericSchema]
|
|
1909
|
+
) -> TemplateSchema | GenericSchema:
|
|
1910
|
+
# Tell if the user explicitely requested this template
|
|
1911
|
+
is_autogenerated_subtemplate = node.generate_template is False
|
|
1912
|
+
|
|
1913
|
+
core_template_schema = (
|
|
1914
|
+
self.get(name=InfrahubKind.OBJECTCOMPONENTTEMPLATE, duplicate=False)
|
|
1915
|
+
if is_autogenerated_subtemplate
|
|
1916
|
+
else self.get(name=InfrahubKind.OBJECTTEMPLATE, duplicate=False)
|
|
1917
|
+
)
|
|
1918
|
+
core_name_attr = core_template_schema.get_attribute(name=OBJECT_TEMPLATE_NAME_ATTR)
|
|
1919
|
+
template_name_attr = AttributeSchema(
|
|
1920
|
+
**core_name_attr.model_dump(exclude=["id", "inherited"]),
|
|
1921
|
+
)
|
|
1922
|
+
template_name_attr.branch = node.branch
|
|
1923
|
+
|
|
1924
|
+
template: TemplateSchema | GenericSchema
|
|
1925
|
+
need_template_kinds = [n.kind for n in need_templates]
|
|
1926
|
+
|
|
1927
|
+
if node.is_generic_schema:
|
|
1928
|
+
# When needing a template for a generic, we generate an empty shell mostly to make sure that schemas (including the GraphQL one) will
|
|
1929
|
+
# look right. We don't really care about applying inheritance of fields as it was already processed and actual templates will have the
|
|
1930
|
+
# correct attributes and relationships
|
|
1931
|
+
template = GenericSchema(
|
|
1932
|
+
name=node.kind,
|
|
1933
|
+
namespace="Template",
|
|
1934
|
+
label=f"Generic object template {node.label}",
|
|
1935
|
+
description=f"Generic object template for generic {node.kind}",
|
|
1936
|
+
generate_profile=False,
|
|
1937
|
+
branch=node.branch,
|
|
1938
|
+
include_in_menu=False,
|
|
1939
|
+
attributes=[template_name_attr],
|
|
1940
|
+
)
|
|
1941
|
+
|
|
1942
|
+
for used in node.used_by:
|
|
1943
|
+
if used in need_template_kinds:
|
|
1944
|
+
template.used_by.append(self._get_object_template_kind(node_kind=used))
|
|
1945
|
+
|
|
1946
|
+
return template
|
|
1947
|
+
|
|
1948
|
+
template = TemplateSchema(
|
|
1949
|
+
name=node.kind,
|
|
1950
|
+
namespace="Template",
|
|
1951
|
+
label=f"Object template {node.label}",
|
|
1952
|
+
description=f"Object template for {node.kind}",
|
|
1953
|
+
branch=node.branch,
|
|
1954
|
+
include_in_menu=False,
|
|
1955
|
+
display_labels=["template_name__value"],
|
|
1956
|
+
human_friendly_id=["template_name__value"],
|
|
1957
|
+
uniqueness_constraints=[["template_name__value"]],
|
|
1958
|
+
inherit_from=[InfrahubKind.LINEAGESOURCE, InfrahubKind.NODE, core_template_schema.kind],
|
|
1959
|
+
default_filter="template_name__value",
|
|
1960
|
+
attributes=[template_name_attr],
|
|
1961
|
+
relationships=[
|
|
1962
|
+
RelationshipSchema(
|
|
1963
|
+
name="related_nodes",
|
|
1964
|
+
identifier="node__objecttemplate",
|
|
1965
|
+
peer=node.kind,
|
|
1966
|
+
kind=RelationshipKind.TEMPLATE,
|
|
1967
|
+
cardinality=RelationshipCardinality.MANY,
|
|
1968
|
+
branch=BranchSupportType.AWARE,
|
|
1969
|
+
)
|
|
1970
|
+
],
|
|
1971
|
+
)
|
|
1972
|
+
|
|
1973
|
+
for inherited in node.inherit_from:
|
|
1974
|
+
if inherited in need_template_kinds:
|
|
1975
|
+
template.inherit_from.append(self._get_object_template_kind(node_kind=inherited))
|
|
1976
|
+
|
|
1977
|
+
for node_attr in node.attributes:
|
|
1978
|
+
if node_attr.unique or node_attr.read_only:
|
|
1979
|
+
continue
|
|
1980
|
+
|
|
1981
|
+
attr = AttributeSchema(
|
|
1982
|
+
optional=node_attr.optional if is_autogenerated_subtemplate else True,
|
|
1983
|
+
**node_attr.model_dump(exclude=["id", "unique", "optional", "read_only", "inherited"]),
|
|
1984
|
+
)
|
|
1985
|
+
template.attributes.append(attr)
|
|
1986
|
+
|
|
1987
|
+
return template
|
|
1988
|
+
|
|
1989
|
+
def identify_required_object_templates(
|
|
1990
|
+
self, node_schema: NodeSchema | GenericSchema, identified: set[NodeSchema | GenericSchema]
|
|
1991
|
+
) -> set[NodeSchema]:
|
|
1992
|
+
"""Identify all templates required to turn a given node into a template."""
|
|
1993
|
+
if node_schema in identified or node_schema.state == HashableModelState.ABSENT:
|
|
1994
|
+
return identified
|
|
1995
|
+
|
|
1996
|
+
identified.add(node_schema)
|
|
1997
|
+
|
|
1998
|
+
for relationship in node_schema.relationships:
|
|
1999
|
+
if (
|
|
2000
|
+
relationship.peer in [InfrahubKind.GENERICGROUP, InfrahubKind.PROFILE]
|
|
2001
|
+
or (relationship.kind == RelationshipKind.PARENT and node_schema.generate_template)
|
|
2002
|
+
or relationship.kind not in [RelationshipKind.PARENT, RelationshipKind.COMPONENT]
|
|
2003
|
+
):
|
|
2004
|
+
continue
|
|
2005
|
+
|
|
2006
|
+
peer_schema = self.get(name=relationship.peer, duplicate=False)
|
|
2007
|
+
if not isinstance(peer_schema, NodeSchema | GenericSchema) or peer_schema in identified:
|
|
2008
|
+
continue
|
|
2009
|
+
# In a context of a generic, we won't be able to create objects out of it, so any kind of nodes implementing the generic is a valid
|
|
2010
|
+
# option, we therefore need to have a template for each of those nodes
|
|
2011
|
+
if isinstance(peer_schema, GenericSchema) and peer_schema.used_by:
|
|
2012
|
+
if relationship.kind != RelationshipKind.PARENT or not any(
|
|
2013
|
+
u in [i.kind for i in identified] for u in peer_schema.used_by
|
|
2014
|
+
):
|
|
2015
|
+
for used_by in peer_schema.used_by:
|
|
2016
|
+
identified |= self.identify_required_object_templates(
|
|
2017
|
+
node_schema=self.get(name=used_by, duplicate=False), identified=identified
|
|
2018
|
+
)
|
|
2019
|
+
|
|
2020
|
+
identified |= self.identify_required_object_templates(node_schema=peer_schema, identified=identified)
|
|
2021
|
+
|
|
2022
|
+
return identified
|
|
2023
|
+
|
|
2024
|
+
def manage_object_template_schemas(self) -> None:
|
|
2025
|
+
need_templates: set[NodeSchema | GenericSchema] = set()
|
|
2026
|
+
template_schema_kinds: set[str] = set()
|
|
2027
|
+
|
|
2028
|
+
for node_name in self.node_names + self.generic_names_without_templates:
|
|
2029
|
+
node = self.get(name=node_name, duplicate=False)
|
|
2030
|
+
|
|
2031
|
+
# Delete old object templates if schemas were removed
|
|
2032
|
+
if (
|
|
2033
|
+
node.namespace in RESTRICTED_NAMESPACES
|
|
2034
|
+
or not node.generate_template
|
|
2035
|
+
or node.state == HashableModelState.ABSENT
|
|
2036
|
+
):
|
|
2037
|
+
try:
|
|
2038
|
+
node.relationships = [r for r in node.relationships if r.name != OBJECT_TEMPLATE_RELATIONSHIP_NAME]
|
|
2039
|
+
self.delete(name=self._get_object_template_kind(node_kind=node.kind))
|
|
2040
|
+
except SchemaNotFoundError:
|
|
2041
|
+
...
|
|
2042
|
+
continue
|
|
2043
|
+
|
|
2044
|
+
need_templates |= self.identify_required_object_templates(node_schema=node, identified=need_templates)
|
|
2045
|
+
|
|
2046
|
+
# Generate templates with their attributes
|
|
2047
|
+
for node in need_templates:
|
|
2048
|
+
template = self.generate_object_template_from_node(node=node, need_templates=need_templates)
|
|
2049
|
+
self.set(name=template.kind, schema=template)
|
|
2050
|
+
template_schema_kinds.add(template.kind)
|
|
2051
|
+
|
|
2052
|
+
# Go back on templates and add relationships to them
|
|
2053
|
+
for node in need_templates:
|
|
2054
|
+
self.add_relationships_to_template(node=node)
|
|
2055
|
+
|
|
2056
|
+
for previous_template in list(self.templates.keys()):
|
|
2057
|
+
# Ensure that we remove previous object template schemas if a node has been renamed
|
|
2058
|
+
if previous_template not in template_schema_kinds:
|
|
2059
|
+
self.delete(name=previous_template)
|
|
2060
|
+
|
|
2061
|
+
if not template_schema_kinds:
|
|
2062
|
+
return
|
|
2063
|
+
|
|
2064
|
+
core_template_schema = self.get(name=InfrahubKind.OBJECTTEMPLATE, duplicate=False)
|
|
2065
|
+
current_used_by_template = set(core_template_schema.used_by)
|
|
2066
|
+
new_used_by_template = template_schema_kinds - current_used_by_template
|
|
2067
|
+
|
|
2068
|
+
if new_used_by_template:
|
|
2069
|
+
core_template_schema = self.get(name=InfrahubKind.OBJECTTEMPLATE, duplicate=True)
|
|
2070
|
+
core_template_schema.used_by = sorted(template_schema_kinds)
|
|
2071
|
+
self.set(name=InfrahubKind.OBJECTTEMPLATE, schema=core_template_schema)
|
|
2072
|
+
|
|
2073
|
+
if self.has(name=InfrahubKind.NODE):
|
|
2074
|
+
core_node_schema = self.get(name=InfrahubKind.NODE, duplicate=False)
|
|
2075
|
+
current_used_by_node = set(core_node_schema.used_by)
|
|
2076
|
+
new_used_by_node = template_schema_kinds - current_used_by_node
|
|
2077
|
+
|
|
2078
|
+
if new_used_by_node:
|
|
2079
|
+
core_node_schema = self.get(name=InfrahubKind.NODE, duplicate=True)
|
|
2080
|
+
updated_used_by_node = set(chain(template_schema_kinds, set(core_node_schema.used_by)))
|
|
2081
|
+
core_node_schema.used_by = sorted(updated_used_by_node)
|
|
2082
|
+
self.set(name=InfrahubKind.NODE, schema=core_node_schema)
|