katello 4.12.1 → 4.13.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of katello might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/app/assets/javascripts/katello/locale/bn/katello.js +3365 -3350
- data/app/assets/javascripts/katello/locale/bn_IN/katello.js +3136 -3121
- data/app/assets/javascripts/katello/locale/ca/katello.js +3588 -3576
- data/app/assets/javascripts/katello/locale/cs/katello.js +3499 -3487
- data/app/assets/javascripts/katello/locale/cs_CZ/katello.js +4186 -4186
- data/app/assets/javascripts/katello/locale/de/katello.js +5553 -5562
- data/app/assets/javascripts/katello/locale/de_AT/katello.js +3008 -2993
- data/app/assets/javascripts/katello/locale/de_DE/katello.js +3066 -3051
- data/app/assets/javascripts/katello/locale/el/katello.js +3376 -3370
- data/app/assets/javascripts/katello/locale/en/katello.js +3008 -2993
- data/app/assets/javascripts/katello/locale/en_GB/katello.js +3076 -3073
- data/app/assets/javascripts/katello/locale/en_US/katello.js +3008 -2993
- data/app/assets/javascripts/katello/locale/es/katello.js +5366 -5372
- data/app/assets/javascripts/katello/locale/et_EE/katello.js +3008 -2993
- data/app/assets/javascripts/katello/locale/fr/katello.js +5975 -5984
- data/app/assets/javascripts/katello/locale/gl/katello.js +3125 -3113
- data/app/assets/javascripts/katello/locale/gu/katello.js +3119 -3104
- data/app/assets/javascripts/katello/locale/he_IL/katello.js +3020 -3005
- data/app/assets/javascripts/katello/locale/hi/katello.js +3137 -3122
- data/app/assets/javascripts/katello/locale/id/katello.js +3008 -2993
- data/app/assets/javascripts/katello/locale/it/katello.js +4469 -4466
- data/app/assets/javascripts/katello/locale/ja/katello.js +5969 -5978
- data/app/assets/javascripts/katello/locale/ka/katello.js +5649 -5652
- data/app/assets/javascripts/katello/locale/kn/katello.js +3136 -3121
- data/app/assets/javascripts/katello/locale/ko/katello.js +4717 -4720
- data/app/assets/javascripts/katello/locale/locale/katello.js +1050 -1084
- data/app/assets/javascripts/katello/locale/ml_IN/katello.js +3008 -2993
- data/app/assets/javascripts/katello/locale/mr/katello.js +3136 -3121
- data/app/assets/javascripts/katello/locale/nl_NL/katello.js +3116 -3101
- data/app/assets/javascripts/katello/locale/or/katello.js +3137 -3122
- data/app/assets/javascripts/katello/locale/pa/katello.js +3136 -3121
- data/app/assets/javascripts/katello/locale/pl/katello.js +3210 -3195
- data/app/assets/javascripts/katello/locale/pl_PL/katello.js +3008 -2993
- data/app/assets/javascripts/katello/locale/pt/katello.js +3009 -2994
- data/app/assets/javascripts/katello/locale/pt_BR/katello.js +5362 -5368
- data/app/assets/javascripts/katello/locale/ro/katello.js +3008 -2993
- data/app/assets/javascripts/katello/locale/ro_RO/katello.js +3008 -2993
- data/app/assets/javascripts/katello/locale/ru/katello.js +4638 -4641
- data/app/assets/javascripts/katello/locale/sl/katello.js +3051 -3036
- data/app/assets/javascripts/katello/locale/sv_SE/katello.js +3156 -3144
- data/app/assets/javascripts/katello/locale/ta/katello.js +3365 -3350
- data/app/assets/javascripts/katello/locale/ta_IN/katello.js +3121 -3106
- data/app/assets/javascripts/katello/locale/te/katello.js +3136 -3121
- data/app/assets/javascripts/katello/locale/tr/katello.js +3025 -3010
- data/app/assets/javascripts/katello/locale/vi/katello.js +3008 -2993
- data/app/assets/javascripts/katello/locale/vi_VN/katello.js +3008 -2993
- data/app/assets/javascripts/katello/locale/zh/katello.js +3008 -2993
- data/app/assets/javascripts/katello/locale/zh_CN/katello.js +5968 -5977
- data/app/assets/javascripts/katello/locale/zh_TW/katello.js +4694 -4697
- data/app/controllers/katello/api/registry/registry_proxies_controller.rb +370 -132
- data/app/controllers/katello/api/rhsm/candlepin_dynflow_proxy_controller.rb +12 -20
- data/app/controllers/katello/api/v2/activation_keys_controller.rb +10 -4
- data/app/controllers/katello/api/v2/capsule_content_controller.rb +24 -0
- data/app/controllers/katello/api/v2/content_view_versions_controller.rb +9 -2
- data/app/controllers/katello/api/v2/debs_controller.rb +1 -1
- data/app/controllers/katello/api/v2/errata_controller.rb +1 -1
- data/app/controllers/katello/api/v2/host_subscriptions_controller.rb +12 -4
- data/app/controllers/katello/api/v2/hosts_bulk_actions_controller.rb +3 -3
- data/app/controllers/katello/api/v2/organizations_controller.rb +0 -11
- data/app/controllers/katello/api/v2/packages_controller.rb +1 -1
- data/app/controllers/katello/api/v2/repositories_controller.rb +19 -13
- data/app/controllers/katello/api/v2/repository_sets_controller.rb +2 -1
- data/app/controllers/katello/api/v2/simple_content_access_controller.rb +9 -22
- data/app/controllers/katello/concerns/api/v2/authorization.rb +1 -1
- data/app/helpers/katello/subscription_mailer_helper.rb +1 -1
- data/app/jobs/create_manifest_expire_soon_warning_notifications.rb +11 -0
- data/app/lib/actions/candlepin/owner/regenerate_upstream_identity_cert.rb +21 -0
- data/app/lib/actions/katello/capsule_content/sync.rb +1 -1
- data/app/lib/actions/katello/capsule_content/sync_capsule.rb +7 -2
- data/app/lib/actions/katello/capsule_content/verify_checksum.rb +75 -0
- data/app/lib/actions/katello/content_view/promote.rb +1 -1
- data/app/lib/actions/katello/content_view/publish.rb +1 -1
- data/app/lib/actions/katello/content_view_version/verify_checksum.rb +29 -0
- data/app/lib/actions/katello/host/hypervisors_update.rb +1 -0
- data/app/lib/actions/katello/host/update_content_view.rb +2 -2
- data/app/lib/actions/katello/organization/manifest_delete.rb +6 -1
- data/app/lib/actions/katello/organization/manifest_import.rb +5 -0
- data/app/lib/actions/katello/organization/manifest_refresh.rb +3 -0
- data/app/lib/actions/katello/repository/create.rb +17 -11
- data/app/lib/actions/katello/repository/create_root.rb +4 -2
- data/app/lib/actions/katello/repository/metadata_generate.rb +7 -1
- data/app/lib/actions/katello/repository/remove_content.rb +1 -0
- data/app/lib/actions/katello/repository/sync.rb +2 -1
- data/app/lib/actions/katello/repository/upload_files.rb +1 -0
- data/app/lib/actions/katello/upstream_subscriptions/bind_entitlement.rb +1 -1
- data/app/lib/actions/pulp3/capsule_content/verify_checksum.rb +27 -0
- data/app/lib/actions/pulp3/orchestration/content_view_version/export_repository.rb +7 -9
- data/app/lib/actions/pulp3/orchestration/content_view_version/syncable_export.rb +5 -4
- data/app/lib/actions/pulp3/orchestration/orphan_cleanup/remove_orphans.rb +1 -0
- data/app/lib/actions/pulp3/orphan_cleanup/purge_completed_tasks.rb +15 -0
- data/app/lib/katello/concerns/base_template_scope_extensions.rb +7 -2
- data/app/lib/katello/http_resource.rb +6 -1
- data/app/lib/katello/resources/candlepin/consumer.rb +1 -1
- data/app/lib/katello/resources/candlepin/upstream_consumer.rb +18 -6
- data/app/lib/katello/resources/candlepin/upstream_job.rb +1 -1
- data/app/lib/katello/resources/registry.rb +25 -0
- data/app/mailers/katello/subscription_mailer.rb +3 -6
- data/app/models/katello/concerns/organization_extensions.rb +42 -3
- data/app/models/katello/content_view.rb +30 -0
- data/app/models/katello/content_view_environment_content_facet.rb +4 -2
- data/app/models/katello/glue/provider.rb +19 -12
- data/app/models/katello/glue/pulp/repos.rb +11 -3
- data/app/models/katello/host/content_facet.rb +1 -1
- data/app/models/katello/host/subscription_facet.rb +1 -1
- data/app/models/katello/ping.rb +1 -1
- data/app/models/katello/repository.rb +32 -1
- data/app/models/katello/root_repository.rb +4 -6
- data/app/models/katello/trace_status.rb +1 -1
- data/app/services/katello/content_unit_indexer.rb +9 -0
- data/app/services/katello/pulp3/alternate_content_source.rb +4 -6
- data/app/services/katello/pulp3/api/core.rb +21 -0
- data/app/services/katello/pulp3/api/docker.rb +4 -0
- data/app/services/katello/pulp3/api/yum.rb +11 -0
- data/app/services/katello/pulp3/docker_manifest.rb +5 -1
- data/app/services/katello/pulp3/repository/generic.rb +1 -1
- data/app/services/katello/pulp3/repository/yum.rb +1 -6
- data/app/services/katello/pulp3/repository.rb +26 -6
- data/app/services/katello/pulp3/repository_mirror.rb +13 -12
- data/app/services/katello/pulp3/service_common.rb +2 -10
- data/app/services/katello/pulp3/smart_proxy_repository.rb +0 -2
- data/app/services/katello/ui_notifications/subscriptions/manifest_expire_soon_warning.rb +75 -0
- data/app/views/foreman/job_templates/update_package_-_katello_ansible_default.erb +5 -1
- data/app/views/foreman/job_templates/update_packages_by_search_query_-_katello_ansible_default.erb +2 -2
- data/app/views/foreman/job_templates/upload_profile.erb +16 -0
- data/app/views/foreman/smart_proxies/_content_tab.html.erb +3 -1
- data/app/views/katello/api/v2/content_view_filter_rules/show.json.rabl +9 -0
- data/app/views/katello/api/v2/docker_manifests/show.json.rabl +1 -0
- data/app/views/katello/api/v2/organizations/show.json.rabl +9 -1
- data/app/views/overrides/activation_keys/_host_environment_select.html.erb +1 -1
- data/app/views/overrides/activation_keys/_host_media_type_select.html.erb +15 -5
- data/config/routes/api/registry.rb +4 -8
- data/config/routes/api/v2.rb +2 -0
- data/db/migrate/20240423112842_add_fields_to_katello_docker_manifest.rb +8 -0
- data/db/migrate/20240502192021_change_katello_repository_rpms_id_seq_to_big_int.rb +9 -0
- data/db/migrate/20240520142245_add_container_push_props_to_repo.rb +7 -0
- data/db/migrate/20240531193030_remove_sha1_repository_checksum_type.rb +10 -0
- data/db/seeds.d/109-katello-notification-blueprints.rb +6 -0
- data/engines/bastion_katello/app/assets/javascripts/bastion_katello/activation-keys/details/activation-key-repository-sets.controller.js +3 -3
- data/engines/bastion_katello/app/assets/javascripts/bastion_katello/content-credentials/new/views/new-content-credential.html +2 -1
- data/engines/bastion_katello/app/assets/javascripts/bastion_katello/content-hosts/details/content-host-repository-sets.controller.js +3 -3
- data/engines/bastion_katello/app/assets/javascripts/bastion_katello/i18n/bastion_katello.pot +0 -15
- data/engines/bastion_katello/app/assets/javascripts/bastion_katello/products/details/repositories/checksum.service.js +6 -1
- data/engines/bastion_katello/app/assets/javascripts/bastion_katello/products/details/repositories/details/views/repository-info.html +8 -6
- data/engines/bastion_katello/app/assets/javascripts/bastion_katello/products/details/repositories/new/views/new-repository.html +12 -13
- data/lib/katello/permission_creator.rb +3 -3
- data/lib/katello/permissions/registry_permissions.rb +4 -7
- data/lib/katello/plugin.rb +21 -8
- data/lib/katello/repository_types/ostree.rb +7 -0
- data/lib/katello/scheduled_jobs.rb +7 -1
- data/lib/katello/tasks/clean_backend_objects.rake +1 -1
- data/lib/katello/tasks/repository.rake +22 -0
- data/lib/katello/version.rb +1 -1
- data/locale/action_names.rb +4 -3
- data/locale/bn/katello.po +166 -151
- data/locale/bn_IN/katello.po +166 -151
- data/locale/ca/katello.po +166 -151
- data/locale/cs/katello.po +166 -151
- data/locale/cs_CZ/LC_MESSAGES/katello.mo +0 -0
- data/locale/cs_CZ/katello.po +172 -157
- data/locale/de/LC_MESSAGES/katello.mo +0 -0
- data/locale/de/katello.po +178 -163
- data/locale/de_AT/katello.po +166 -151
- data/locale/de_DE/katello.po +166 -151
- data/locale/el/katello.po +166 -151
- data/locale/en/katello.po +166 -151
- data/locale/en_GB/katello.po +166 -151
- data/locale/en_US/katello.po +166 -151
- data/locale/es/LC_MESSAGES/katello.mo +0 -0
- data/locale/es/katello.po +178 -163
- data/locale/et_EE/katello.po +166 -151
- data/locale/fr/LC_MESSAGES/katello.mo +0 -0
- data/locale/fr/katello.po +179 -164
- data/locale/gl/katello.po +166 -151
- data/locale/gu/katello.po +166 -151
- data/locale/he_IL/katello.po +166 -151
- data/locale/hi/katello.po +166 -151
- data/locale/id/katello.po +166 -151
- data/locale/it/LC_MESSAGES/katello.mo +0 -0
- data/locale/it/katello.po +169 -154
- data/locale/ja/LC_MESSAGES/katello.mo +0 -0
- data/locale/ja/katello.po +179 -164
- data/locale/ka/LC_MESSAGES/katello.mo +0 -0
- data/locale/ka/katello.po +177 -162
- data/locale/katello.pot +1119 -1062
- data/locale/kn/katello.po +166 -151
- data/locale/ko/LC_MESSAGES/katello.mo +0 -0
- data/locale/ko/katello.po +174 -159
- data/locale/ml_IN/katello.po +166 -151
- data/locale/mr/katello.po +166 -151
- data/locale/nl_NL/katello.po +166 -151
- data/locale/or/katello.po +166 -151
- data/locale/pa/katello.po +166 -151
- data/locale/pl/katello.po +166 -151
- data/locale/pl_PL/katello.po +166 -151
- data/locale/pt/katello.po +166 -151
- data/locale/pt_BR/LC_MESSAGES/katello.mo +0 -0
- data/locale/pt_BR/katello.po +178 -163
- data/locale/ro/katello.po +166 -151
- data/locale/ro_RO/katello.po +166 -151
- data/locale/ru/LC_MESSAGES/katello.mo +0 -0
- data/locale/ru/katello.po +171 -156
- data/locale/sl/katello.po +166 -151
- data/locale/sv_SE/katello.po +166 -151
- data/locale/ta/katello.po +166 -151
- data/locale/ta_IN/katello.po +166 -151
- data/locale/te/katello.po +166 -151
- data/locale/tr/katello.po +166 -151
- data/locale/vi/katello.po +166 -151
- data/locale/vi_VN/katello.po +166 -151
- data/locale/zh/katello.po +166 -151
- data/locale/zh_CN/LC_MESSAGES/katello.mo +0 -0
- data/locale/zh_CN/katello.po +179 -164
- data/locale/zh_TW/LC_MESSAGES/katello.mo +0 -0
- data/locale/zh_TW/katello.po +171 -156
- data/package.json +0 -1
- data/webpack/ForemanColumnExtensions/index.js +129 -0
- data/webpack/components/Content/ContentTable.js +0 -1
- data/webpack/components/Content/__tests__/__snapshots__/ContentTable.test.js.snap +0 -1
- data/webpack/components/Table/TableWrapper.js +14 -0
- data/webpack/components/extensions/HostDetails/ActionsBar/index.js +1 -1
- data/webpack/components/extensions/Hosts/ActionsBar/index.js +20 -1
- data/webpack/components/extensions/Hosts/BulkActions/BulkChangeHostCVModal/BulkChangeHostCVModal.js +220 -0
- data/webpack/components/extensions/Hosts/BulkActions/BulkChangeHostCVModal/actions.js +23 -0
- data/webpack/components/extensions/Hosts/BulkActions/BulkChangeHostCVModal/index.js +25 -0
- data/webpack/components/extensions/Hosts/BulkActions/__tests__/bulkChangeHostCVModal.test.js +133 -0
- data/webpack/global_index.js +9 -0
- data/webpack/scenes/Hosts/ChangeContentSource/actions.js +3 -1
- data/webpack/scenes/Hosts/ChangeContentSource/components/ContentSourceForm.js +62 -24
- data/webpack/scenes/Hosts/ChangeContentSource/index.js +24 -16
- data/webpack/scenes/ModuleStreams/ModuleStreamsPage.js +33 -39
- data/webpack/scenes/ModuleStreams/__tests__/ModuleStreamPage.test.js +4 -2
- data/webpack/scenes/ModuleStreams/__tests__/__snapshots__/ModuleStreamsTable.test.js.snap +0 -1
- data/webpack/scenes/RedHatRepositories/__tests__/__snapshots__/RedHatRepositoriesPage.test.js.snap +1 -0
- data/webpack/scenes/Subscriptions/Manifest/ManageManifestModal.js +66 -5
- data/webpack/scenes/Subscriptions/UpstreamSubscriptions/UpstreamSubscriptionsPage.js +16 -13
- data/webpack/scenes/Subscriptions/UpstreamSubscriptions/__tests__/__snapshots__/UpstreamSubscriptionsPage.test.js.snap +14 -8
- data/webpack/scenes/Subscriptions/__tests__/__snapshots__/SubscriptionsPage.test.js.snap +1 -0
- data/webpack/scenes/Subscriptions/components/SubscriptionsToolbar/SubscriptionsToolbar.js +1 -1
- metadata +60 -42
- data/app/lib/actions/katello/host/upload_package_profile.rb +0 -45
- data/app/lib/actions/katello/host/upload_profiles.rb +0 -47
- data/webpack/utils/__tests__/useParamsWithHash.test.js +0 -22
- data/webpack/utils/paramsFromHash.js +0 -16
- data/webpack/utils/useUrlParams.js +0 -14
@@ -13,10 +13,13 @@ module Actions
|
|
13
13
|
|
14
14
|
org = repository.organization
|
15
15
|
sequence do
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
16
|
+
# Container push repositories will already be in pulp. The version_href is
|
17
|
+
# directly updated after a push.
|
18
|
+
unless root.is_container_push
|
19
|
+
create_action = plan_action(Pulp3::Orchestration::Repository::Create,
|
20
|
+
repository, SmartProxy.pulp_primary, force_repo_create)
|
21
|
+
return if create_action.error
|
22
|
+
end
|
20
23
|
|
21
24
|
# when creating a clone, the following actions are handled by the
|
22
25
|
# publish/promote process
|
@@ -32,13 +35,16 @@ module Actions
|
|
32
35
|
end
|
33
36
|
end
|
34
37
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
38
|
+
# Container push repos do not need metadata generation or ACS (they do not sync)
|
39
|
+
unless root.is_container_push
|
40
|
+
concurrence do
|
41
|
+
plan_self(:repository_id => repository.id, :clone => clone)
|
42
|
+
if !clone && repository.url.present?
|
43
|
+
repository.product.alternate_content_sources.with_type(repository.content_type).each do |acs|
|
44
|
+
acs.smart_proxies.each do |smart_proxy|
|
45
|
+
smart_proxy_acs = ::Katello::SmartProxyAlternateContentSource.create(alternate_content_source_id: acs.id, smart_proxy_id: smart_proxy.id, repository_id: repository.id)
|
46
|
+
plan_action(Pulp3::Orchestration::AlternateContentSource::Create, smart_proxy_acs)
|
47
|
+
end
|
42
48
|
end
|
43
49
|
end
|
44
50
|
end
|
@@ -2,13 +2,15 @@ module Actions
|
|
2
2
|
module Katello
|
3
3
|
module Repository
|
4
4
|
class CreateRoot < Actions::EntryAction
|
5
|
-
def plan(root)
|
5
|
+
def plan(root, relative_path = nil)
|
6
6
|
root.save!
|
7
7
|
repository = ::Katello::Repository.new(:environment => root.organization.library,
|
8
8
|
:content_view_version => root.organization.library.default_content_view_version,
|
9
9
|
:root => root)
|
10
|
-
repository.
|
10
|
+
repository.container_repository_name = relative_path
|
11
|
+
repository.relative_path = relative_path || repository.custom_repo_path
|
11
12
|
repository.save!
|
13
|
+
|
12
14
|
action_subject(repository)
|
13
15
|
plan_action(::Actions::Katello::Repository::Create, repository)
|
14
16
|
end
|
@@ -1,8 +1,10 @@
|
|
1
1
|
module Actions
|
2
2
|
module Katello
|
3
3
|
module Repository
|
4
|
-
class MetadataGenerate < Actions::
|
4
|
+
class MetadataGenerate < Actions::EntryAction
|
5
5
|
def plan(repository, options = {})
|
6
|
+
action_subject(repository)
|
7
|
+
repository.check_ready_to_act!
|
6
8
|
source_repository = options.fetch(:source_repository, nil)
|
7
9
|
source_repository ||= repository.target_repository if repository.link?
|
8
10
|
smart_proxy = options.fetch(:smart_proxy, SmartProxy.pulp_primary)
|
@@ -15,6 +17,10 @@ module Actions
|
|
15
17
|
:source_repository => source_repository,
|
16
18
|
:matching_content => matching_content)
|
17
19
|
end
|
20
|
+
|
21
|
+
def resource_locks
|
22
|
+
:link
|
23
|
+
end
|
18
24
|
end
|
19
25
|
end
|
20
26
|
end
|
@@ -5,6 +5,7 @@ module Actions
|
|
5
5
|
include Dynflow::Action::WithSubPlans
|
6
6
|
|
7
7
|
def plan(repository, content_units, options = {})
|
8
|
+
repository.check_ready_to_act!
|
8
9
|
sync_capsule = options.fetch(:sync_capsule, true)
|
9
10
|
if repository.redhat?
|
10
11
|
fail _("Cannot remove content from a non-custom repository")
|
@@ -20,6 +20,7 @@ module Actions
|
|
20
20
|
# of Katello and we just need to finish the rest of the orchestration
|
21
21
|
def plan(repo, options = {})
|
22
22
|
action_subject(repo)
|
23
|
+
repo.check_ready_to_act!
|
23
24
|
|
24
25
|
validate_contents = options.fetch(:validate_contents, false)
|
25
26
|
skip_metadata_check = options.fetch(:skip_metadata_check, false) || (validate_contents && (repo.yum? || repo.deb?))
|
@@ -100,7 +101,7 @@ module Actions
|
|
100
101
|
def notify_on_failure(_plan)
|
101
102
|
notification = MailNotification[:repository_sync_failure]
|
102
103
|
repo = ::Katello::Repository.find(input.fetch(:repository, {})[:id])
|
103
|
-
notification.users.
|
104
|
+
notification.users.with_enabled_email.each do |user|
|
104
105
|
notification.deliver(user: user, repo: repo, task: task)
|
105
106
|
end
|
106
107
|
end
|
@@ -8,6 +8,7 @@ module Actions
|
|
8
8
|
class UploadFiles < Actions::EntryAction
|
9
9
|
def plan(repository, files, content_type = nil, options = {})
|
10
10
|
action_subject(repository)
|
11
|
+
repository.check_ready_to_act!
|
11
12
|
repository.clear_smart_proxy_sync_histories
|
12
13
|
tmp_files = prepare_tmp_files(files)
|
13
14
|
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Actions
|
2
|
+
module Pulp3
|
3
|
+
module CapsuleContent
|
4
|
+
class VerifyChecksum < Pulp3::AbstractAsyncTask
|
5
|
+
def plan(repository, smart_proxy)
|
6
|
+
plan_self(:repository_id => repository.id, :smart_proxy_id => smart_proxy.id)
|
7
|
+
end
|
8
|
+
|
9
|
+
def invoke_external_task
|
10
|
+
repo = ::Katello::Repository.find(input[:repository_id])
|
11
|
+
output[:pulp_tasks] = repo.backend_service(smart_proxy).with_mirror_adapter.repair
|
12
|
+
end
|
13
|
+
|
14
|
+
def repos_to_repair(smart_proxy, environment, content_view, repository)
|
15
|
+
smart_proxy_helper = ::Katello::SmartProxyHelper.new(smart_proxy)
|
16
|
+
smart_proxy_helper.lifecycle_environment_check(environment, repository)
|
17
|
+
if repository
|
18
|
+
[repository]
|
19
|
+
else
|
20
|
+
repositories = smart_proxy_helper.repositories_available_to_capsule(environment, content_view).by_rpm_count
|
21
|
+
repositories
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -3,26 +3,24 @@ module Actions
|
|
3
3
|
module Orchestration
|
4
4
|
module ContentViewVersion
|
5
5
|
class ExportRepository < Actions::EntryAction
|
6
|
-
def plan(repository,
|
7
|
-
|
8
|
-
from_history: nil,
|
9
|
-
format: ::Katello::Pulp3::ContentViewVersion::Export::IMPORTABLE)
|
6
|
+
def plan(repository, opts = {})
|
7
|
+
opts[:format] ||= ::Katello::Pulp3::ContentViewVersion::Export::IMPORTABLE
|
10
8
|
action_subject(repository)
|
11
9
|
validate_repositories_immediate!(repository)
|
12
|
-
validate_export_types!(repository, format)
|
10
|
+
validate_export_types!(repository, opts[:format])
|
13
11
|
content_view = ::Katello::Pulp3::ContentViewVersion::Export.find_repository_export_view(
|
14
12
|
repository: repository,
|
15
13
|
create_by_default: true,
|
16
|
-
format: format)
|
14
|
+
format: opts[:format])
|
17
15
|
content_view.update!(repository_ids: [repository.library_instance_or_self.id])
|
18
16
|
|
19
17
|
sequence do
|
20
18
|
publish_action = plan_action(::Actions::Katello::ContentView::Publish, content_view, '')
|
21
19
|
export_action = plan_action(Actions::Katello::ContentViewVersion::Export,
|
22
20
|
content_view_version: publish_action.version,
|
23
|
-
chunk_size: chunk_size,
|
24
|
-
from_history: from_history,
|
25
|
-
format: format)
|
21
|
+
chunk_size: opts[:chunk_size],
|
22
|
+
from_history: opts[:from_history],
|
23
|
+
format: opts[:format])
|
26
24
|
plan_self(export_action_output: export_action.output)
|
27
25
|
end
|
28
26
|
end
|
@@ -16,10 +16,11 @@ module Actions
|
|
16
16
|
param :export_history_id, Integer
|
17
17
|
end
|
18
18
|
|
19
|
-
def plan(
|
20
|
-
|
21
|
-
|
22
|
-
|
19
|
+
def plan(opts = {})
|
20
|
+
content_view_version = opts[:content_view_version]
|
21
|
+
smart_proxy = opts[:smart_proxy]
|
22
|
+
destination_server = opts[:destination_server]
|
23
|
+
from_content_view_version = opts[:from_content_view_version]
|
23
24
|
format = ::Katello::Pulp3::ContentViewVersion::Export::SYNCABLE
|
24
25
|
sequence do
|
25
26
|
export_service = ::Katello::Pulp3::ContentViewVersion::Export.create(
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Actions
|
2
|
+
module Pulp3
|
3
|
+
module OrphanCleanup
|
4
|
+
class PurgeCompletedTasks < Pulp3::AbstractAsyncTask
|
5
|
+
def plan(smart_proxy)
|
6
|
+
plan_self(:smart_proxy_id => smart_proxy.id)
|
7
|
+
end
|
8
|
+
|
9
|
+
def run
|
10
|
+
output[:pulp_tasks] = ::Katello::Pulp3::Api::Core.new(smart_proxy).purge_completed_tasks
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -216,9 +216,14 @@ module Katello
|
|
216
216
|
keyword :includes, Array, of: [String, Symbol], desc: 'An array of associations represented by strings or symbols, to be included in the SQL query. The list can be extended
|
217
217
|
from plugins and can not be fully documented here. Most used associations are :subscription, :products, :organization', default: nil
|
218
218
|
returns array_of: 'Pool', desc: 'The collection that can be iterated over using each_record'
|
219
|
+
keyword :expiring_in_days, String, desc: "Return subscriptions expiring in the given number of days. Leave blank to return all subscriptions.", default: nil
|
219
220
|
end
|
220
|
-
def load_pools(search: '', includes: nil)
|
221
|
-
|
221
|
+
def load_pools(search: '', includes: nil, expiring_in_days: nil)
|
222
|
+
pools = Pool.readable
|
223
|
+
if expiring_in_days
|
224
|
+
pools = pools.expiring_in_days(expiring_in_days)
|
225
|
+
end
|
226
|
+
load_resource(klass: pools, search: search, permission: nil, includes: includes)
|
222
227
|
end
|
223
228
|
|
224
229
|
apipie :method, 'Returns the last time the host checked in via RHSM' do
|
@@ -38,6 +38,7 @@ module Katello
|
|
38
38
|
get: Net::HTTP::Get,
|
39
39
|
post: Net::HTTP::Post,
|
40
40
|
put: Net::HTTP::Put,
|
41
|
+
patch: Net::HTTP::Patch,
|
41
42
|
delete: Net::HTTP::Delete
|
42
43
|
}.freeze
|
43
44
|
|
@@ -84,7 +85,11 @@ module Katello
|
|
84
85
|
def issue_request(method:, path:, headers: {}, payload: nil)
|
85
86
|
logger.debug("Resource #{method.upcase} request: #{path}")
|
86
87
|
logger.debug "Headers: #{headers.to_json}"
|
87
|
-
|
88
|
+
begin
|
89
|
+
logger.debug "Body: #{filter_sensitive_data(payload.to_json)}"
|
90
|
+
rescue JSON::GeneratorError, Encoding::UndefinedConversionError
|
91
|
+
logger.debug "Body: Error: could not render payload as json"
|
92
|
+
end
|
88
93
|
|
89
94
|
client = rest_client(REQUEST_MAP[method], method, path)
|
90
95
|
args = [method, payload, headers].compact
|
@@ -7,7 +7,7 @@ module Katello
|
|
7
7
|
class << self
|
8
8
|
def all_uuids
|
9
9
|
cp_consumers = Organization.all.map do |org|
|
10
|
-
::Katello::Resources::Candlepin::Consumer.get('owner' => org.label, :include_only => [:uuid])
|
10
|
+
::Katello::Resources::Candlepin::Consumer.get('owner' => org.label, :include_only => [:uuid], :sort_by => "uuid")
|
11
11
|
end
|
12
12
|
cp_consumers.flatten!
|
13
13
|
cp_consumers.map { |consumer| consumer["uuid"] }
|
@@ -40,19 +40,31 @@ module Katello
|
|
40
40
|
raise ::Katello::Errors::UpstreamEntitlementGone
|
41
41
|
end
|
42
42
|
|
43
|
-
def
|
43
|
+
def start_upstream_export(url, client_cert, client_key, ca_file)
|
44
44
|
logger.debug "Sending GET request to upstream Candlepin: #{url}"
|
45
|
-
|
45
|
+
resource(url: url, client_cert: client_cert, client_key: client_key, ca_file: ca_file).get
|
46
46
|
rescue RestClient::Exception => e
|
47
47
|
raise e
|
48
48
|
end
|
49
49
|
|
50
|
+
alias_method :retrieve_upstream_export, :start_upstream_export
|
51
|
+
|
50
52
|
def update(url, client_cert, client_key, ca_file, attributes)
|
51
53
|
logger.debug "Sending PUT request to upstream Candlepin: #{url} #{attributes.to_json}"
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
54
|
+
resource(
|
55
|
+
url: url,
|
56
|
+
client_cert: client_cert,
|
57
|
+
client_key: client_key,
|
58
|
+
ca_file: ca_file).put(
|
59
|
+
attributes.to_json,
|
60
|
+
'accept' => 'application/json',
|
61
|
+
'accept-language' => I18n.locale,
|
62
|
+
'content-type' => 'application/json')
|
63
|
+
end
|
64
|
+
|
65
|
+
def regenerate_upstream_identity(url, client_cert, client_key, ca_file)
|
66
|
+
logger.debug "Sending POST request to upstream Candlepin: #{url}"
|
67
|
+
resource(url: url, client_cert: client_cert, client_key: client_key, ca_file: ca_file).post(nil)
|
56
68
|
end
|
57
69
|
|
58
70
|
def bind_entitlement(**pool)
|
@@ -12,7 +12,7 @@ module Katello
|
|
12
12
|
|
13
13
|
def get(id, upstream)
|
14
14
|
url = API_URL
|
15
|
-
response = Resources::Candlepin::UpstreamConsumer.
|
15
|
+
response = Resources::Candlepin::UpstreamConsumer.start_upstream_export("#{url}#{path(id)}", upstream['idCert']['cert'],
|
16
16
|
upstream['idCert']['key'], nil)
|
17
17
|
job = JSON.parse(response)
|
18
18
|
job.with_indifferent_access
|
@@ -18,10 +18,35 @@ module Katello
|
|
18
18
|
client.options.merge!(options)
|
19
19
|
client.get(headers)
|
20
20
|
end
|
21
|
+
|
22
|
+
def self.put(path, body, headers)
|
23
|
+
logger.debug "Sending PUT request to Registry: #{path}"
|
24
|
+
resource = RegistryResource.load_class
|
25
|
+
joined_path = resource.prefix.chomp("/") + path
|
26
|
+
resource.issue_request(method: :put, path: joined_path, headers: headers, payload: body)
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.patch(path, body, headers)
|
30
|
+
logger.debug "Sending PATCH request to Registry: #{path}"
|
31
|
+
resource = RegistryResource.load_class
|
32
|
+
joined_path = resource.prefix.chomp("/") + path
|
33
|
+
resource.issue_request(method: :patch, path: joined_path, headers: headers, payload: body)
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.post(path, body, headers)
|
37
|
+
logger.debug "Sending PUT request to Registry: #{path}"
|
38
|
+
resource = RegistryResource.load_class
|
39
|
+
joined_path = resource.prefix.chomp("/") + path
|
40
|
+
resource.issue_request(method: :post, path: joined_path, headers: headers, payload: body)
|
41
|
+
end
|
21
42
|
end
|
22
43
|
|
23
44
|
class RegistryResource < HttpResource
|
24
45
|
class << self
|
46
|
+
def logger
|
47
|
+
::Foreman::Logging.logger('katello/registry_proxy')
|
48
|
+
end
|
49
|
+
|
25
50
|
def load_class
|
26
51
|
pulp_primary = ::SmartProxy.pulp_primary
|
27
52
|
content_app_url = pulp_primary.setting(SmartProxy::PULP3_FEATURE, 'content_app_url')
|
@@ -10,14 +10,11 @@ module Katello
|
|
10
10
|
|
11
11
|
::User.as(user.login) do
|
12
12
|
@pools = Katello::Pool.readable.expiring_in_days(days_from_now)
|
13
|
-
@affected_hosts = ::Host::Managed.with_pools_expiring_in_days(days_from_now)
|
14
13
|
end
|
15
14
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
@report_link = report_link
|
20
|
-
end
|
15
|
+
start_report_task(days_from_now)
|
16
|
+
@report_url = report_url
|
17
|
+
@report_link = report_link
|
21
18
|
|
22
19
|
set_locale_for(user) do
|
23
20
|
mail(:to => user.mail, :subject => _("You have subscriptions expiring within %s days") % days_from_now)
|
@@ -100,16 +100,55 @@ module Katello
|
|
100
100
|
end
|
101
101
|
end
|
102
102
|
|
103
|
-
def
|
104
|
-
|
103
|
+
def manifest_expiration_date(cached: true)
|
104
|
+
Rails.cache.fetch("#{self.label}_manifest_expiration_date", expires_in: 1.minute, force: !cached) do
|
105
|
+
unless manifest_imported?(cached: cached)
|
106
|
+
Rails.logger.error "Manifest not imported for organization #{self.label}"
|
107
|
+
return nil
|
108
|
+
end
|
109
|
+
manifest_expiry = owner_details.dig(:upstreamConsumer, :idCert, :serial, :expiration)
|
110
|
+
|
111
|
+
if manifest_expiry.present?
|
112
|
+
DateTime.parse(manifest_expiry)
|
113
|
+
else
|
114
|
+
Rails.logger.error "Unable to parse manifest expiration date from owner details"
|
115
|
+
nil
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def manifest_expired?(cached: true)
|
121
|
+
manifest_expiry = manifest_expiration_date(cached: cached)
|
105
122
|
|
106
123
|
if manifest_expiry
|
107
|
-
|
124
|
+
manifest_expiry < DateTime.now
|
108
125
|
else
|
109
126
|
false
|
110
127
|
end
|
111
128
|
end
|
112
129
|
|
130
|
+
def manifest_expiring_soon?(days = Setting[:expire_soon_days])
|
131
|
+
return false if !manifest_imported? || manifest_expired?
|
132
|
+
manifest_expiry = manifest_expiration_date
|
133
|
+
|
134
|
+
if manifest_expiry
|
135
|
+
manifest_expiry < DateTime.now + days.days
|
136
|
+
else
|
137
|
+
false
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
def manifest_expire_days_remaining
|
142
|
+
manifest_expiry = manifest_expiration_date
|
143
|
+
return 0 if manifest_expired?
|
144
|
+
|
145
|
+
if manifest_expiry
|
146
|
+
(manifest_expiry - DateTime.now).to_i
|
147
|
+
else
|
148
|
+
0
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
113
152
|
def manifest_history
|
114
153
|
imports.map { |i| OpenStruct.new(i) }
|
115
154
|
end
|
@@ -454,6 +454,8 @@ module Katello
|
|
454
454
|
).each do |facet|
|
455
455
|
facet.update_applicability_counts
|
456
456
|
facet.update_errata_status
|
457
|
+
rescue NoMethodError
|
458
|
+
Rails.logger.warn _('Errata statuses not updated for deleted content facet with UUID %s') % facet.uuid
|
457
459
|
end
|
458
460
|
end
|
459
461
|
end
|
@@ -634,6 +636,7 @@ module Katello
|
|
634
636
|
check_ready_to_import!
|
635
637
|
else
|
636
638
|
fail _("Import-only content views can not be published directly") if import_only? && !syncable
|
639
|
+
check_repositories_blocking_publish!
|
637
640
|
check_composite_action_allowed!(organization.library)
|
638
641
|
check_docker_repository_names!([organization.library])
|
639
642
|
check_orphaned_content_facets!(environments: self.environments)
|
@@ -642,6 +645,16 @@ module Katello
|
|
642
645
|
true
|
643
646
|
end
|
644
647
|
|
648
|
+
def check_repositories_blocking_publish!
|
649
|
+
blocking_tasks = repositories&.map { |repo| repo.blocking_task }&.compact
|
650
|
+
|
651
|
+
if blocking_tasks&.any?
|
652
|
+
errored_tasks = blocking_tasks.uniq.map { |task| "- #{Setting['foreman_url']}/foreman_tasks/tasks/#{task&.id}" }.join("\n")
|
653
|
+
fail _("Pending tasks detected in repositories of this content view. Please wait for the tasks: " +
|
654
|
+
errored_tasks + " before publishing.")
|
655
|
+
end
|
656
|
+
end
|
657
|
+
|
645
658
|
def check_docker_repository_names!(environments)
|
646
659
|
environments.each do |environment|
|
647
660
|
repositories = []
|
@@ -789,6 +802,10 @@ module Katello
|
|
789
802
|
repositories.any? { |repo| repo.last_indexed && repo.last_indexed > latest_version_object.created_at }
|
790
803
|
end
|
791
804
|
|
805
|
+
def unpublishable?
|
806
|
+
default? || import_only? || generated?
|
807
|
+
end
|
808
|
+
|
792
809
|
def needs_publish?
|
793
810
|
#Returns
|
794
811
|
# True:
|
@@ -808,7 +825,9 @@ module Katello
|
|
808
825
|
# a) No changes were detected via audits *and*
|
809
826
|
# Audit for CV publish exists (Audits haven't been cleaned up)
|
810
827
|
# *and* applied_filters field is set(Published after upgrade)
|
828
|
+
# b) Default, import only and generated CVs can not be published, hence these will always return false.
|
811
829
|
#
|
830
|
+
return false if unpublishable?
|
812
831
|
return true unless latest_version_object
|
813
832
|
return nil unless last_publish_task_success?
|
814
833
|
return composite_cv_components_changed? if composite?
|
@@ -868,6 +887,17 @@ module Katello
|
|
868
887
|
filters.present?
|
869
888
|
end
|
870
889
|
|
890
|
+
def blocking_task
|
891
|
+
blocking_task_labels = [
|
892
|
+
::Actions::Katello::ContentView::Publish.name
|
893
|
+
]
|
894
|
+
ForemanTasks::Task::DynflowTask.where(:label => blocking_task_labels)
|
895
|
+
.where.not(state: 'stopped')
|
896
|
+
.for_resource(self)
|
897
|
+
.order(:started_at)
|
898
|
+
.last
|
899
|
+
end
|
900
|
+
|
871
901
|
protected
|
872
902
|
|
873
903
|
def remove_repository(repository)
|
@@ -5,7 +5,7 @@ module Katello
|
|
5
5
|
|
6
6
|
validates :content_view_environment_id, presence: true
|
7
7
|
validates :content_facet_id, presence: true, unless: :new_record?
|
8
|
-
validate :ensure_valid_content_source
|
8
|
+
validate :ensure_valid_content_source, if: proc { Setting['validate_host_lce_content_source_coherence'] }
|
9
9
|
|
10
10
|
def ensure_valid_content_source
|
11
11
|
source = self.content_facet&.content_source
|
@@ -14,7 +14,9 @@ module Katello
|
|
14
14
|
hostname = self.content_facet&.host&.name
|
15
15
|
return unless [source, env].all? { |x| x.present? }
|
16
16
|
unless source.lifecycle_environments.include?(env)
|
17
|
-
|
17
|
+
error_msg = _("Host %{hostname}: Cannot add content view environment to content facet. The host's content source '%{content_source}' does not sync lifecycle environment '%{lce}'.") % { hostname: hostname, content_source: source.name, lce: env.name }
|
18
|
+
Rails.logger.warn error_msg
|
19
|
+
errors.add(:base, error_msg)
|
18
20
|
end
|
19
21
|
end
|
20
22
|
end
|
@@ -32,6 +32,10 @@ module Katello
|
|
32
32
|
|
33
33
|
module InstanceMethods
|
34
34
|
API_URL = 'https://subscription.rhsm.redhat.com/subscription/consumers/'.freeze
|
35
|
+
def api_url(upstream = {})
|
36
|
+
# Default to Red Hat
|
37
|
+
upstream['apiUrl'] || API_URL
|
38
|
+
end
|
35
39
|
|
36
40
|
def sync
|
37
41
|
Rails.logger.debug "Syncing provider #{name}"
|
@@ -62,34 +66,37 @@ module Katello
|
|
62
66
|
fail _("Upstream identity certificate not available")
|
63
67
|
end
|
64
68
|
|
65
|
-
# Default to Red Hat
|
66
|
-
url = upstream['apiUrl'] || API_URL
|
67
|
-
|
68
69
|
params = {}
|
69
70
|
params[:capabilities] = Resources::Candlepin::CandlepinPing.ping['managerCapabilities'].inject([]) do |result, element|
|
70
71
|
result << {'name' => element}
|
71
72
|
end
|
72
73
|
params[:facts] = {:distributor_version => DISTRIBUTOR_VERSION }
|
73
|
-
Resources::Candlepin::UpstreamConsumer.update("#{
|
74
|
+
Resources::Candlepin::UpstreamConsumer.update("#{api_url(upstream)}#{upstream['uuid']}", upstream['idCert']['cert'],
|
74
75
|
upstream['idCert']['key'], ca_file, params)
|
75
76
|
end
|
76
77
|
|
77
|
-
def
|
78
|
+
def owner_upstream_regenerate_identity_cert(upstream)
|
78
79
|
validate_upstream_identity_cert!(upstream)
|
79
|
-
|
80
|
+
Rails.logger.debug "Sending request to regenerate identity certificate for upstream consumer: #{upstream['uuid']}"
|
81
|
+
response = Resources::Candlepin::UpstreamConsumer.regenerate_upstream_identity("#{api_url(upstream)}#{upstream['uuid']}", upstream['idCert']['cert'],
|
82
|
+
upstream['idCert']['key'], ca_file)
|
83
|
+
JSON.parse(response)
|
84
|
+
end
|
80
85
|
|
81
|
-
|
86
|
+
def start_owner_upstream_export(upstream)
|
87
|
+
validate_upstream_identity_cert!(upstream)
|
88
|
+
response = Resources::Candlepin::UpstreamConsumer.start_upstream_export("#{api_url(upstream)}#{upstream['uuid']}/export/async", upstream['idCert']['cert'],
|
82
89
|
upstream['idCert']['key'], ca_file)
|
83
90
|
JSON.parse(response)
|
84
91
|
end
|
85
92
|
|
86
93
|
def retrieve_owner_upstream_export(upstream, zip_file_path, export_id)
|
87
94
|
validate_upstream_identity_cert!(upstream)
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
95
|
+
data = Resources::Candlepin::UpstreamConsumer.retrieve_upstream_export(
|
96
|
+
"#{api_url(upstream)}#{upstream['uuid']}/export/#{export_id}",
|
97
|
+
upstream['idCert']['cert'],
|
98
|
+
upstream['idCert']['key'], ca_file
|
99
|
+
)
|
93
100
|
File.write(zip_file_path, data, mode: 'wb')
|
94
101
|
|
95
102
|
true
|
@@ -50,7 +50,7 @@ module Katello
|
|
50
50
|
end
|
51
51
|
|
52
52
|
def last_sync_audit
|
53
|
-
Audited::Audit.where(:auditable_id => self.repositories, :auditable_type => Katello::Repository.name).order(:created_at).last
|
53
|
+
Audited::Audit.where(:auditable_id => self.repositories, :auditable_type => Katello::Repository.name, :action => "sync").order(:created_at).last
|
54
54
|
end
|
55
55
|
|
56
56
|
def last_sync
|
@@ -58,13 +58,14 @@ module Katello
|
|
58
58
|
end
|
59
59
|
|
60
60
|
def last_repo_sync_task
|
61
|
-
@last_sync_task ||= last_repo_sync_tasks
|
61
|
+
@last_sync_task ||= last_repo_sync_tasks&.first
|
62
62
|
end
|
63
63
|
|
64
64
|
def last_repo_sync_tasks
|
65
65
|
ids = repos(self.library, nil, false).pluck(:id).join(',')
|
66
66
|
label = ::Actions::Katello::Repository::Sync.name
|
67
67
|
type = ::Katello::Repository.name
|
68
|
+
return nil if ids.empty?
|
68
69
|
ForemanTasks::Task.search_for("label = #{label} and resource_type = #{type} and resource_id ^ (#{ids})")
|
69
70
|
.order("started_at desc")
|
70
71
|
end
|
@@ -132,7 +133,14 @@ module Katello
|
|
132
133
|
|
133
134
|
repo_param[:mirroring_policy] = Katello::RootRepository::MIRRORING_POLICY_ADDITIVE if repo_param[:mirroring_policy].blank?
|
134
135
|
|
135
|
-
|
136
|
+
repo_param = repo_param.merge(:product_id => self.id)
|
137
|
+
|
138
|
+
# Container push may concurrently call root add several times before the db can update.
|
139
|
+
if repo_param[:is_container_push]
|
140
|
+
RootRepository.create_or_find_by!(repo_param)
|
141
|
+
else
|
142
|
+
RootRepository.new(repo_param)
|
143
|
+
end
|
136
144
|
end
|
137
145
|
end
|
138
146
|
end
|